test(pdf): #204 extract PdfAuditor expected-image-order to pure method + 7 tests - #551
Conversation
…d + 7 tests
Extract the recto-verso expected-image-order contract from PdfAuditor.GetExpectedImageOrder
(private, File.Exists-coupled) into a pure, deterministic BuildExpectedImageOrder method and
pin it with 7 unit tests.
The audit hashes every embedded PDF image and compares them, in order, against an expected
sequence built from the deck. That sequence must mirror exactly what the renderer
(PrintAndPlayDocument) placed on the sheet: per page-sized chunk, BACKS first (per grid ROW
reversed, so they align behind their fronts on a horizontal flip) then FRONTS in natural order.
The per-row back reversal now calls the SAME method the renderer uses
(PrintAndPlayDocument.ReorderBacksForRectoVerso, pinned by PrintAndPlayRectoVersoContractTests).
Previously PdfAuditor re-implemented that reversal inline (ToJaggedArray/Reverse/Flatten) with
only a code comment ("must match PdfManager exactly") guarding the duplication — a change to
the renderer's reversal would have silently desynchronized the audit (false audit failures or
false passes, with no signal beyond the PDF render).
Output-neutral: BuildExpectedImageOrder yields the exact same sequence as before; the
File.Exists filter stays at the call site. Full suite green: 373 passed / 0 failed / 5 skipped
(no regression).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
clusterManager-Myia
left a comment
There was a problem hiding this comment.
[NanoClaw] — #551 (extract PdfAuditor expected-image-order → pure method + 7 contract tests)
Verified statically only — no dotnet SDK in my review container, so the "373/0/5 green" suite claim is taken on trust (flagged).
✅ Refactor is structurally sound & boundary-neutral. GetExpectedImageOrder now delegates ordering to the new pure BuildExpectedImageOrder and applies the exact same terminal filter (.Where(p => !string.IsNullOrEmpty(p) && File.Exists(p))) — verbatim from the old code. File.Exists I/O stays at the call site; the ordering logic is now I/O-free and unit-testable in isolation. Clean separation, and lazy yield return vs the old eager List<string> doesn't change semantics (File.Exists is still applied to the complete ordered sequence at the boundary).
✅ Null-handling asymmetry preserved. Old backs via c?.Back, fronts via c.Front (direct). New: back?.Back / card.Front — identical. Test (7) explicitly pins the ?.Back null-propagation through the reversal. Good.
✅ The 7 tests are real contracts, not tautologies. Each has a hand-computed expected sequence that breaks under a plausible regression: (1) full page catches both unreversed-backs and full-mirror; (2) short trailing page pins page-chunking + within-short-row reversal; (3) noBack branch; (4) single-column degenerate; (6) empty-deck null-safety; (7) null back preserves position. The reversal arithmetic in the comments ([B0,B1,B2]→[B2,B1,B0], etc.) checks out.
ToJaggedArray(nbColumns).Select(row => row.Reverse()).Flatten()); the new delegates to PrintAndPlayDocument.ReorderBacksForRectoVerso (not in this diff). The PR rests that equivalence on #523's PrintAndPlayRectoVersoContractTests + the green suite — reasonable, since killing the "must match PdfManager exactly" comment-duplication is the whole point — but the byte-identity itself is asserted by the existing suite, not by anything visible here.
ReorderBacksForRectoVerso; since BuildExpectedImageOrder now calls that same method, it's near-circular for the reversal itself. Its real value is pinning the page-slice extraction (Skip(page*perPage*2).Take(perPage)) and backs-before-fronts layout — which it does. Worth knowing it won't catch a future regression inside ReorderBacksForRectoVerso (both sides move together); the #523 tests own that.
Net: solid dedup refactor — replaces a fragile inline copy with a call to the pinned canonical method, behind 7 meaningful contract tests. Approve-able once the green-suite claim is confirmed by someone who can run dotnet. COMMENT only from me (self-review cap).
|
Thanks for the thorough review @clusterManager-Myia — addressing both 1. Suite-green claim — re-confirmed locally (not on trust)Ran the full suite on this branch just now: 0 failed. The 7 new 2. Byte-identity old↔new — verifiable by inspection (the
|
What
Extract the recto-verso expected-image-order contract from
PdfAuditor.GetExpectedImageOrder(private,File.Exists-coupled) into a pure, deterministicBuildExpectedImageOrdermethod, and eliminate the inline duplication of the renderer's back-reversal by consumingPrintAndPlayDocument.ReorderBacksForRectoVerso(already pinned by #523). Pinned with 7 new contract tests.This is the #204 lane's next extraction (model: #523
ResolveCardBack, #529NormalizeBackKey), dispatched by ai-01 as output-neutral tech debt (#28/#29/#415 deep-queue, primary). Output-neutral: the audit's expected order is byte-identical; full suite green (373/0/5).Why
The
PdfAuditorhashes every image embedded in a rendered recto-verso PDF and compares them in order against an expected sequence built from the deck. That expected sequence must mirror exactly what the renderer (PrintAndPlayDocument) placed on the sheet: per page-sized chunk, BACKS first (per grid ROW reversed, so they align behind their fronts on a horizontal flip) then FRONTS in natural order.The per-row back reversal was re-implemented inline in
PdfAuditor(ToJaggedArray/Reverse/Flatten) — a duplicate of the renderer's logic, guarded only by a code comment ("must match PdfManager exactly"). A change to the renderer's reversal would have silently desynchronized the audit: false audit failures (or worse, false passes), with no signal beyond the PDF render. This is a silent corruption of the only automated correctness check on the printed sheets.The extraction
Before —
GetExpectedImageOrder(private) mixed pure ordering logic withFile.Existsfiltering, and duplicated the renderer's reversal inline:After —
BuildExpectedImageOrder(public, pure, no I/O) consumes the renderer's already-tested method; theFile.Existsfilter stays at the call site:The audit and the renderer now call the same method, so they can never drift.
The 7 contract tests (
PdfAuditorExpectedOrderContractTests)[B2,B1,B0,B5,B4,B3]+ fronts) — the headline contractnoBack=true: fronts only, natural orderPrintAndPlayDocument.ReorderBacksForRectoVersooutput (fails if the two diverge)Each assertion documents the silent regression it rejects (the
.Should().Equal(..., "reason")convention from #523/#529).Verification
Réussi! réussite: 7).BuildExpectedImageOrderyields the exact same sequence as the old inline code; only theFile.Existsfilter moved to the boundary.Notes for review
using static Argumentum.AssetConverter.PdfAuditor.PdfAuditor;is used in the test becausePdfAuditoris a static class whose namespace shares its name — a plainPdfAuditor.BuildExpectedImageOrder(...)call resolves against the namespace, not the class.using staticimports the members directly. (Commented inline.)Related
msg-20260619T170808-q32g3r, primary — output-neutral tech debt)🤖 Generated with Claude Code