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
+36 -37
View File
@@ -20,21 +20,22 @@ Tag code with `// TRACES: JR-012 | SR-002`.
| ID | Asserts | Covers | Status |
|---|---|---|---|
| UT-001 | Marked tag **and its trailing newline** removed — no blank line accumulates per upgrade cycle | JR-022 | Written |
| UT-002 | Marked tag with no trailing newline removed | JR-022 | Written |
| UT-003 | Document without the marker left byte-identical | JR-022 | Written |
| UT-004 | Removal is idempotent — startup runs it on every boot forever after | JR-022 | Written |
| UT-005 | **Another plugin's injection left intact** — it is their file too | JR-022 | Written |
| UT-006 | An unmarked look-alike script tag is left alone — JRay did not write it | JR-022 | Written |
| UT-007 | No matching rule resolves to `null` | JR-016 | Written |
| UT-008 | Item rule beats Series rule | JR-016 | Written |
| UT-009 | **Prioritised series inside an ignored genre — series wins** | JR-016 | Written |
| UT-010 | Genre matching is case-insensitive | JR-016 | Written |
| UT-011 | A Series rule valued `Guid.Empty` does not swallow every movie | JR-016 | 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 | **Passing** |
| 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 | **Passing** |
| 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 | **Passing** |
| UT-007 | No matching rule resolves to `null` | JR-016 | **Passing** |
| UT-008 | Item rule beats Series rule | JR-016 | **Passing** |
| UT-009 | **Prioritised series inside an ignored genre — series wins** | JR-016 | **Passing** |
| 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 | **Passing** |
`Written` rather than `Passing`: see the execution blocker below. A test whose
result nobody has seen is not evidence, and recording it as passing would be the
same failure as counting a GPU-only test as covered.
All 11 execute and pass. The suite was also checked to **fail** on a mutation —
removing the newline-stripping from `RemoveInjection` fails UT-001 and nothing
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
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 |
|---|---|---|---|---|
| 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-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 |
@@ -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-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-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.
`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
`Jellyfin.Controller`, so the test host demands that shared framework even for
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:
`dotnet test` from the repo root. Two properties on the test project make that
work anywhere, and both are load-bearing rather than incidental:
```
Framework: 'Microsoft.AspNetCore.App', version '9.0.0' — No frameworks were found.
```
- **`RollForward=LatestMajor`.** The plugin targets `net9.0` to match Jellyfin's
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*
half — the plugin targets `net9.0` to match Jellyfin's ABI, and no .NET 9
runtime is installed either — but roll-forward cannot conjure a framework of
which no version exists.
**Fix:** install the ASP.NET Core runtime (`aspnet-runtime` on Arch). It is
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.
**This is a T1-tier decision, not a workaround to unwind.** These tests exercise
string and rule logic; requiring an ASP.NET Core runtime to run them would be
incidental coupling. **T2 — controllers and authorisation — genuinely needs that
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
that project will lean on `RollForward` too.
### Per-requirement verification plan