Commit Graph
4 Commits
Author SHA1 Message Date
dtourolleandClaude Opus 5 3d210b5bd3 Manifest fetch across the configured servers (JR-025 … JR-037)
🏗️ Build Plugin / build (push) Successful in 44s
Latest Release / latest-release (push) Successful in 40s
🧪 Test Plugin / test (push) Successful in 26s
Satisfies JRay-public-server UR-007. Servers are tried in configured order and
the first result clearing the configured tier wins; first-match rather than
best-match because querying every server for every item multiplies egress and
leaks the library to more parties, and the ordering already encodes which
source the admin prefers.

Every server is untrusted, including the pre-configured community one, so a
fetched manifest is re-validated against the same rules the server applies on
upload: envelope version refused if unknown, identifiers format-checked,
windows bounds-checked against the *local* file's runtime, belief bounded to
[0, 1], control and bidi characters refused in names. Responses are capped
while streaming rather than after buffering, since a hostile server can declare
any Content-Length it likes. HTTPS is required away from loopback. A failing
server is skipped with exponential backoff so one dead server cannot stall a
sweep.

The audio-tier offset is applied once, at store time, so stored truth is always
in the local file's own timebase and no read path needs offset awareness.
Windows are shifted, never reshaped — merging adjacent ones would answer "was a
face visible" rather than "was the actor present" (SR-002).

Also records why there is no `exact` tier, which was missing and led me to
re-add one. The file-hash tier is withdrawn on legal grounds: a TMDB id
discloses "some copy of this film", but an OpenSubtitles hash discloses "this
exact release", which turns a catalogue lookup into a release-identification
service and a server's database into a mapping from file fingerprints to the
instances holding them. The reason now lives on MatchTier and in SPEC.md §JR-036,
`TitleQuery` has no VideoHash property so there is nothing to send, and a test
asserts the enum has no Exact member — the spec had still listed `exact` as a
configurable tier, which is what made the removal look like an oversight.

42 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: JR-025, JR-027, JR-028, JR-029, JR-030, JR-031, JR-036, JR-037 | PR-005, PR-006
2026-07-31 10:03:49 +02:00
dtourolleandClaude Opus 5 2926740d03 JR-023: correct the missing-dependency logs, and test them
🏗️ Build Plugin / build (push) Successful in 29s
Latest Release / latest-release (push) Successful in 39s
🧪 Test Plugin / test (push) Successful in 26s
Both failure paths still told admins the overlay was "falling back to patching
index.html on disk". JR-021 deleted that fallback, so the message was false --
and it was false in the worst place, since an admin reads it precisely when
debugging a missing overlay and would go hunting for a patch that no longer
exists. They now name the install URL and say the overlay is disabled while
every other feature is unaffected. The not-found case is a Warning, not
Information: a headline feature being off should not sit among startup chatter.

UT-012..015 cover the branch. The File Transformation assembly is genuinely
absent from the test host, so TryRegister exercises its real not-found path
rather than a seam invented for the test. UT-015 pins that a null payload
returns empty rather than throwing -- this callback runs inside another
plugin's request path on every page served, so throwing would break the web
client itself, not just JRay's overlay.

Making those runnable needed the test project to reference Jellyfin.Controller
and Jellyfin.Model without the plugin's ExcludeAssets=runtime. My earlier claim
that DisableTransitiveFrameworkReferences alone sufficed was too narrow: it
drops the demand for the web *framework*, but ILogger and MediaBrowser.Common
live in the excluded assets, so anything past dependency-free logic failed at
run time with FileNotFoundException. Both settings are needed, and the register
now says so.

Second mutation check: downgrading the warning to Information fails UT-013 and
nothing else. Source restored and re-verified.

JR-023 reaches Done for the detection half. The config-page banner stays T4 --
verifiable only against a live server.

TRACES: UT-012, UT-013, UT-014, UT-015 | JR-023

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:18:40 +02:00
dtourolleandClaude Opus 5 cc973fd7c5 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>
2026-07-31 09:10:40 +02:00
dtourolleandClaude Opus 5 5159692364 Test project: UT-001..011 for JR-016 and JR-022
🏗️ Build Plugin / build (push) Successful in 39s
Latest Release / latest-release (push) Successful in 38s
🧪 Test Plugin / test (push) Successful in 26s
The register defined 46 requirements and zero tests, so every Done in it rested
on inspection. This adds the T1 tier: an xUnit project in the solution,
covering the two units whose logic is pure enough to test without a Jellyfin
host.

JR-022 (UT-001..006) covers the cases where removal could reach too far --
another plugin's injection, and an unmarked look-alike script tag -- because
index.html is a file JRay shares. UT-001 pins the trailing newline to the tag,
without which every install cycle leaves another blank line behind.

JR-016 (UT-007..011) covers specificity resolution, including the prioritised
series inside an ignored genre that motivated the rule, and a Series rule
valued Guid.Empty, which would otherwise swallow every movie in the library.

RemoveInjection is reached through InternalsVisibleTo rather than being made
public: it is factored out for testability, not part of the surface.

The suite BUILDS but does not RUN here. Jellyfin.Controller framework-
references Microsoft.AspNetCore.App, so the test host demands it even for pure
logic, and this machine has no ASP.NET Core runtime at any version --
RollForward cannot substitute for a framework that is absent entirely. Fix is
to install aspnet-runtime, which the CI image needs for the same reason.

So every UT here is recorded as Written, not Passing, and no requirement is
promoted to Done on their strength. A test whose result nobody has seen is not
evidence.

TRACES: UT-001, UT-002, UT-003, UT-004, UT-005, UT-006 | JR-022
TRACES: UT-007, UT-008, UT-009, UT-010, UT-011 | JR-016

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 19:12:24 +02:00