diff --git a/Jellyfin.Plugin.JRay.Tests/Jellyfin.Plugin.JRay.Tests.csproj b/Jellyfin.Plugin.JRay.Tests/Jellyfin.Plugin.JRay.Tests.csproj index 9b8a9bd..dbffd1c 100644 --- a/Jellyfin.Plugin.JRay.Tests/Jellyfin.Plugin.JRay.Tests.csproj +++ b/Jellyfin.Plugin.JRay.Tests/Jellyfin.Plugin.JRay.Tests.csproj @@ -20,6 +20,18 @@ and on CI without pinning either to a runtime that is not the plugin's. --> LatestMajor + + true diff --git a/docs/requirements.md b/docs/requirements.md index 65bf9f1..2c117cf 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -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 `` marker | PR-004 | High | In Progress | +| JR-022 | Migration: remove any on-disk patch left by an earlier jRay, identified by the `` 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. -`LatestMajor` 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