Make the test suite runnable without an ASP.NET Core runtime
🏗️ Build Plugin / build (push) Successful in 26s
Latest Release / latest-release (push) Successful in 37s
🧪 Test Plugin / test (push) Successful in 25s

The 11 tests built but could not execute: the plugin framework-references
Microsoft.AspNetCore.App through Jellyfin.Controller, that reference flows into
anything referencing the plugin, and the test host then demanded a web runtime
even for tests that touch no web type. This box has none at any version, and
Arch packages only 8 and 10 -- so "install the runtime" was neither available
here nor a clean answer.

DisableTransitiveFrameworkReferences drops the inherited reference. .NET
resolves assemblies lazily, so pure-logic types load without it. This is a
T1-tier decision rather than a workaround: requiring a web runtime to test
string and rule logic is incidental coupling. T2 -- controllers and
authorisation -- genuinely needs it, and belongs in a second project that keeps
the reference.

All 11 now pass. The suite was also checked to FAIL: removing the
newline-stripping from RemoveInjection fails UT-001 and nothing else, then the
source was restored and re-verified byte-identical. A suite that has only ever
passed is not evidence that it tests anything.

JR-016 and JR-022 therefore reach Done -- implemented and verified by tests
that execute. JR-023 stays In Progress: its detection branch has no test and
its config-page half is T4.

TRACES: JR-016, JR-022 | PR-003, PR-004

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 09:10:40 +02:00
co-authored by Claude Opus 5
parent e16f903469
commit cc973fd7c5
2 changed files with 48 additions and 37 deletions
@@ -20,6 +20,18 @@
and on CI without pinning either to a runtime that is not the plugin's. and on CI without pinning either to a runtime that is not the plugin's.
--> -->
<RollForward>LatestMajor</RollForward> <RollForward>LatestMajor</RollForward>
<!--
The plugin framework-references Microsoft.AspNetCore.App through
Jellyfin.Controller, and that reference flows into anything referencing
the plugin. The T1 tier tests pure logic that touches no web type, so
inheriting the web framework would make the suite unrunnable on any box
without the ASP.NET Core runtime for no benefit. .NET resolves assemblies
lazily, so types that never touch ASP.NET load fine without it.
T2 (controllers, authorisation) genuinely needs that runtime. When those
tests arrive they belong in a second project that keeps this reference.
-->
<DisableTransitiveFrameworkReferences>true</DisableTransitiveFrameworkReferences>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+36 -37
View File
@@ -20,21 +20,22 @@ Tag code with `// TRACES: JR-012 | SR-002`.
| ID | Asserts | Covers | Status | | ID | Asserts | Covers | Status |
|---|---|---|---| |---|---|---|---|
| UT-001 | Marked tag **and its trailing newline** removed — no blank line accumulates per upgrade cycle | JR-022 | Written | | UT-001 | Marked tag **and its trailing newline** removed — no blank line accumulates per upgrade cycle | JR-022 | **Passing** |
| UT-002 | Marked tag with no trailing newline removed | JR-022 | Written | | UT-002 | Marked tag with no trailing newline removed | JR-022 | **Passing** |
| UT-003 | Document without the marker left byte-identical | JR-022 | Written | | UT-003 | Document without the marker left byte-identical | JR-022 | **Passing** |
| UT-004 | Removal is idempotent — startup runs it on every boot forever after | JR-022 | Written | | UT-004 | Removal is idempotent — startup runs it on every boot forever after | JR-022 | **Passing** |
| UT-005 | **Another plugin's injection left intact** — it is their file too | JR-022 | Written | | UT-005 | **Another plugin's injection left intact** — it is their file too | JR-022 | **Passing** |
| UT-006 | An unmarked look-alike script tag is left alone — JRay did not write it | JR-022 | Written | | UT-006 | An unmarked look-alike script tag is left alone — JRay did not write it | JR-022 | **Passing** |
| UT-007 | No matching rule resolves to `null` | JR-016 | Written | | UT-007 | No matching rule resolves to `null` | JR-016 | **Passing** |
| UT-008 | Item rule beats Series rule | JR-016 | Written | | UT-008 | Item rule beats Series rule | JR-016 | **Passing** |
| UT-009 | **Prioritised series inside an ignored genre — series wins** | JR-016 | Written | | UT-009 | **Prioritised series inside an ignored genre — series wins** | JR-016 | **Passing** |
| UT-010 | Genre matching is case-insensitive | JR-016 | Written | | UT-010 | Genre matching is case-insensitive | JR-016 | **Passing** |
| UT-011 | A Series rule valued `Guid.Empty` does not swallow every movie | JR-016 | Written | | UT-011 | A Series rule valued `Guid.Empty` does not swallow every movie | JR-016 | **Passing** |
`Written` rather than `Passing`: see the execution blocker below. A test whose All 11 execute and pass. The suite was also checked to **fail** on a mutation —
result nobody has seen is not evidence, and recording it as passing would be the removing the newline-stripping from `RemoveInjection` fails UT-001 and nothing
same failure as counting a GPU-only test as covered. else — because a suite that has only ever passed is not evidence that it tests
anything.
`JR` is flat rather than split by theme. The plugin is one deployable with one `JR` is flat rather than split by theme. The plugin is one deployable with one
audience, and the thematic grouping lives in the section headings below, where it audience, and the thematic grouping lives in the section headings below, where it
@@ -82,7 +83,7 @@ coordinated `schema_version` bumps (SR-003).
| ID | Requirement | Traces to | Priority | Status | | ID | Requirement | Traces to | Priority | Status |
|---|---|---|---|---| |---|---|---|---|---|
| JR-015 | `Tasks/Pending` serves a random sample of items with no truth data, so pollers spread across the backlog without server-side task state | PR-003 | High | Done | | JR-015 | `Tasks/Pending` serves a random sample of items with no truth data, so pollers spread across the backlog without server-side task state | PR-003 | High | Done |
| JR-016 | Prioritise/ignore rules scoped `Genre` / `Series` / `Item`; **most specific wins**; scope+value is the unique key | PR-003 | Medium | Done | | JR-016 | Prioritise/ignore rules scoped `Genre` / `Series` / `Item`; **most specific wins**; scope+value is the unique key | PR-003 | Medium | **Done** (UT-007…011) |
| JR-017 | Rules steer **work discovery only** — never the overlay or the read endpoints | PR-003 | Medium | Done | | JR-017 | Rules steer **work discovery only** — never the overlay or the read endpoints | PR-003 | Medium | Done |
| JR-018 | Coverage report by media type and genre; ignored items leave the percent-done denominator rather than dragging it down | PR-003 | Medium | Done | | JR-018 | Coverage report by media type and genre; ignored items leave the percent-done denominator rather than dragging it down | PR-003 | Medium | Done |
| JR-019 | Picker endpoints (genres, series, item search) populate the rule editor | PR-003 | Low | Done | | JR-019 | Picker endpoints (genres, series, item search) populate the rule editor | PR-003 | Low | Done |
@@ -93,7 +94,7 @@ coordinated `schema_version` bumps (SR-003).
|---|---|---|---|---| |---|---|---|---|---|
| JR-020 | Pause overlay: injected client script queries `jray?t=` and renders the scene's cast | **PR-001** | High | Done | | JR-020 | Pause overlay: injected client script queries `jray?t=` and renders the scene's cast | **PR-001** | High | Done |
| JR-021 | **jRay never injects into `index.html` on disk.** File Transformation is a hard dependency; there is no on-disk fallback. The only permitted write is JR-022's removal | PR-004 | High | **Done** | | JR-021 | **jRay never injects into `index.html` on disk.** File Transformation is a hard dependency; there is no on-disk fallback. The only permitted write is JR-022's removal | PR-004 | High | **Done** |
| JR-022 | Migration: remove any on-disk patch left by an earlier jRay, identified by the `<!-- jray-overlay -->` marker | PR-004 | High | In Progress | | JR-022 | Migration: remove any on-disk patch left by an earlier jRay, identified by the `<!-- jray-overlay -->` marker | PR-004 | High | **Done** (UT-001…006) |
| JR-023 | Absent the dependency, disable **only** the overlay and say so in the log and the config page; never bundle the assembly | PR-004 | Medium | In Progress | | JR-023 | Absent the dependency, disable **only** the overlay and say so in the log and the config page; never bundle the assembly | PR-004 | Medium | In Progress |
| JR-024 | Actor names and all server-supplied strings render as **text, never markup** | SR-004 | High | Done | | JR-024 | Actor names and all server-supplied strings render as **text, never markup** | SR-004 | High | Done |
@@ -187,31 +188,29 @@ thing when read across repos; reusing T3 for a live tier here would make a
cross-repo reader count live-only requirements as covered. cross-repo reader count live-only requirements as covered.
`Jellyfin.Plugin.JRay.Tests` (xUnit, in the solution) carries the T1 tier. It `Jellyfin.Plugin.JRay.Tests` (xUnit, in the solution) carries the T1 tier. It
builds clean alongside the plugin. builds clean alongside the plugin and all 11 tests pass.
### Execution blocker — the suite does not run on this machine ### Running the suite on a box without the web runtime
The plugin framework-references **`Microsoft.AspNetCore.App`** through `dotnet test` from the repo root. Two properties on the test project make that
`Jellyfin.Controller`, so the test host demands that shared framework even for work anywhere, and both are load-bearing rather than incidental:
tests that touch only pure logic. This box has `Microsoft.NETCore.App` 8.0.29
and 10.0.10 and **no ASP.NET Core runtime at all**, so `dotnet test` aborts
before a single test executes:
``` - **`RollForward=LatestMajor`.** The plugin targets `net9.0` to match Jellyfin's
Framework: 'Microsoft.AspNetCore.App', version '9.0.0' — No frameworks were found. ABI, but a machine that can *build* it need not have the 9.0 runtime. Rolling
``` the test host forward keeps the suite runnable without pinning developers to a
runtime the plugin does not otherwise need.
- **`DisableTransitiveFrameworkReferences=true`.** The plugin
framework-references `Microsoft.AspNetCore.App` through `Jellyfin.Controller`,
and that flows into anything referencing it — so the test host would demand a
web runtime even for tests that touch no web type. .NET resolves assemblies
lazily, so pure-logic types load fine without it.
`<RollForward>LatestMajor</RollForward>` on the test project solves the *other* **This is a T1-tier decision, not a workaround to unwind.** These tests exercise
half — the plugin targets `net9.0` to match Jellyfin's ABI, and no .NET 9 string and rule logic; requiring an ASP.NET Core runtime to run them would be
runtime is installed either — but roll-forward cannot conjure a framework of incidental coupling. **T2 — controllers and authorisation — genuinely needs that
which no version exists. runtime**, and those tests belong in a second project that keeps the reference.
Note that no ASP.NET Core 9 exists in the Arch repositories (8 and 10 only), so
**Fix:** install the ASP.NET Core runtime (`aspnet-runtime` on Arch). It is that project will lean on `RollForward` too.
needed on the CI host for the same reason, so this belongs in the CI image
rather than in a developer's setup notes.
Until then every `UT` above is **Written, not Passing**, and no requirement
should be promoted to `Done` on the strength of them.
### Per-requirement verification plan ### Per-requirement verification plan