feat: store what a detector found on a page #183

Merged
bjoern merged 3 commits from feat/image-region-schema into main 2026-09-20 13:31:34 +02:00
Member

Story 1.1 of TRANSLATION_PLAN.md — schema only. No detector, no
endpoint, no sweep, no ONNX, nothing user-visible. The next PR (story 1.2)
puts a detector behind a port; this one gives its output somewhere to live.

What's in

Two entities, keyed on ImageFile. ImageService already reuses an image
row on a Sha256 + SizeBytes match, so a cover shared by three doujins is one
file — detect it once and all three read the same answer (ADR 0005). Keying to
Page would compute it once per page row and let the copies drift.

ImageAnalysis is written even when nothing is found. That row is the whole
design. Seven pages in ten in this library are illustration with no text on
them, so without it "no regions" and "never looked at" are the same absence —
the sweep's work queue is images with no row, and it would hand back six
thousand illustrations on every run forever. It is also why the reader will be
able to say there is no text on this page rather than I have nothing to show
you
.

The region foreign key points at the analysis, not at the image. Re-analysing
at a new detector version replaces one row and takes the previous run's shapes
with it, instead of leaving them for someone to remember to delete. Deleting an
image still removes both — image_files → image_analyses → image_regions is a
cascade chain, pinned at the database rather than in the change tracker.

RegionGeometry is the codec and the one thing every consumer has to agree
about — the detector that writes, the endpoint that serves, the reader that
strokes, the cleaner that will fill. Normalised 0..1 coordinates, outlines as
a flat JSON array rounded to four places (~400 bytes a region against several
megabytes for the full-resolution mask the model emits; readable in a sqlite3
shell without a decoder). Two decisions worth naming:

  • Clamped, not rejected, at the page edge. A balloon cropped by the page is
    normal and its upscaled mask traces a hair outside it. Rejecting would throw
    away a real balloon over a rounding artefact.
  • Fewer than three points is refused. A line encloses nothing; the thing
    that would otherwise notice is the cleaner, months from now, filling nothing
    and reporting success.

The bug this migration exposed — please read this part

Every migration in this repository until now added columns and indexes. Never
a table.
So BackupVerifier's two questions — does this file carry every
table the model maps
and is its migration history one we can migrate up —
had never disagreed, and nothing noticed that the first asks about this
build's
schema of a file claiming to be an older one.

AddImageRegionDetection is the first to create a table, and it turns every
pre-upgrade backup into:

The file is a valid SQLite database but not this application's: 2 table(s)
missing (image_analyses, image_regions).

That is precisely the restore ADR 0014 amendment 6 exists to permit, refused on
precisely the upgrade where the old backup is the one that matters — and
refused by telling the operator their own backup is a foreign file. It is
latent on main today and the next table-adding migration would have hit it
regardless; this PR is only the first to make it reachable.

Fixed under ADR 0014, amendment 7. The history is read before the tables are
judged, and a missing table is tolerated only from a file that has proved it
is an older version of this database: it carries a history, that history is
non-empty, and every id in it is one this build also has — a proper subset. Two
bounds keep that from being a hole:

  1. The library table is never optional. doujins is the sanity read that
    catches the structurally-fine-and-empty backup; a check that cannot run has
    not passed.
  2. Only a proper subset counts. A history equal to ours is a backup claiming
    to be current, and a current database has every table. A plain subset would
    wave through a current backup with a dropped table, which is corruption, not
    age.

Direction is now settled before tables (added in review). A backup from a
newer image that also lacks a table this build maps — a future migration
having dropped one — used to fail as "not this application's" rather than
"restore it on the image version that wrote it". Both refuse; only one is true,
and only one tells an operator at three in the morning what to do next.
Direction is a property of the history and the table list cannot speak to it,
so the history is asked first.

I expanded scope to fix this rather than shipping a migration that breaks
restore. If you would rather have it separately, say so and I will split it —
but the migration cannot land without it.

Verification

scripts/verify.sh backend — 1,359 tests green (136 ApplicationCore, 270
Infrastructure, 952 RestAdapter, 1 integration). App untouched, so the Flutter
half was not run.

The migration was generated with the recipe in docs/DEVELOPMENT.md verbatim,
and dotnet ef migrations has-pending-model-changes reports the model and the
migration agree.

Mutation probes: 22 mutants across the four changed files, 22 killed, with
the baseline re-verified green afterwards.

Correction. An earlier revision of this section said "every new branch
mutation-probed: 16 mutants, 16 killed". That was true of the sixteen probes
I ran and false as a claim about branch coverage — ToPixels carries the
same two size guards as Normalise and had no test at all, and three
null-ish guards were unprobed as well. Review caught ToPixels; the other
three came out of enumerating the branches rather than recalling them. The
numbers below are the second, exhaustive run.

Probe Test that went red
Normalise size guards removed An_Image_With_No_Size_Cannot_Be_Measured_Against
ToPixels size guards removed An_Image_With_No_Size_Cannot_Be_Projected_Onto
Normalise stops dividing / ToPixels stops multiplying A_Region_Measured_On_One_Page_Size_Projects_Onto_Another
Clamp removed from Normalise / from Encode A_Contour_That_Bleeds_Past_The_Edge…, Encoding_Clamps_A_Point…
Encode null guard removed Encoding_No_Outline_Is_Refused
Minimum-points check → false (encode / decode) An_Outline_Of_Two_Points_Is_Refused, A_Json_Array_Too_Short…
Finite-coordinate check → false A_Coordinate_That_Is_Not_A_Number_Is_Refused
Odd-coordinate check → false An_Odd_Number_Of_Coordinates_Is_Refused
Decode null guard removed Decoding_No_Value_Is_Refused_By_Name
JSON-null check → false A_Json_Null_Is_Refused
CoordinateDecimals 4 → 2 Stored_Precision_Stays_Under_A_Pixel_On_The_Largest_Page_Measured
Analysis / region cascade → Restrict Deleting_An_Image_Takes_Its_Analysis…, Re_Analysing_At_A_New_Detector_Version…
Verifier: tolerance removed / always on / count-table guard removed / proper-subset weakened / history requirement dropped / direction check disabled the five new BackupVerifierTests

Three probes needed the test sharpened before they could fail, which is the
part worth reading:

  • An_Odd_Number_Of_Coordinates_Is_Refused used a five-value fixture, which
    pairs down to two points — so the minimum-points guard rejected it and the
    branch under test was never reached. Seven values pair to three, a legal
    count, so only the odd-count check can refuse it.
  • The empty-history branch was pinned by a fixture with no history table at
    all, so it never reached the condition. It now drops a table and empties
    the history.
  • Decode's null guard cannot be pinned by a type assertion:
    JsonSerializer.Deserialize throws ArgumentNullException on a null string
    by itself, so Assert.Throws<ArgumentNullException> passes with our guard
    deleted. What changes is the parameter name — the caller would be told that
    "json" was null, naming a parameter of an implementation detail they never
    passed. The test asserts ParamName and says why.

A defect in my own probe harness, worth naming because it would have produced
a false all-clear:
the harness restored each mutated file with shutil.copy2,
which preserves mtime — so the restored source looked older than the built
DLL, MSBuild skipped the rebuild, and the previous mutation stayed in the binary
for the following probe. I caught it because a test failed for a reason the
source could not explain. Every probe above was run with the harness fixed
(os.utime after restore) and the baseline re-verified green afterwards.

Not verified: nothing reads or writes these tables outside the tests — there
is no detector, no endpoint and no sweep yet, so the schema's fitness for the
sweep's query is reasoned from ADR 0027 rather than measured. The
(detector_id, detector_version) index is placed for the anti-join story 1.3
will issue and is unexercised until then.

Follow-ups

  • Story 1.2: IRegionDetector behind a port, ONNX in Infrastructure,
    DOUJIN_MANAGER_DETECTOR_MODEL (ADR 0028).
  • Unrelated, noticed during the build and not acted on: dotnet ef reports
    NU1903 — Microsoft.OpenApi 2.0.0 has a known high-severity advisory
    (GHSA-v5pm-xwqc-g5wc).
    Pre-existing on main, and the kind of thing ADR 0015 pinned a package for
    once before. Worth its own PR.

🤖 Generated with Claude Code

Story 1.1 of [TRANSLATION_PLAN.md](../src/branch/feat/image-region-schema/docs/TRANSLATION_PLAN.md) — **schema only**. No detector, no endpoint, no sweep, no ONNX, nothing user-visible. The next PR (story 1.2) puts a detector behind a port; this one gives its output somewhere to live. ## What's in **Two entities, keyed on `ImageFile`.** `ImageService` already reuses an image row on a `Sha256 + SizeBytes` match, so a cover shared by three doujins is one file — detect it once and all three read the same answer (ADR 0005). Keying to `Page` would compute it once per page row and let the copies drift. **`ImageAnalysis` is written even when nothing is found.** That row is the whole design. Seven pages in ten in this library are illustration with no text on them, so without it "no regions" and "never looked at" are the same absence — the sweep's work queue is *images with no row*, and it would hand back six thousand illustrations on every run forever. It is also why the reader will be able to say *there is no text on this page* rather than *I have nothing to show you*. **The region foreign key points at the analysis, not at the image.** Re-analysing at a new detector version replaces one row and takes the previous run's shapes with it, instead of leaving them for someone to remember to delete. Deleting an image still removes both — `image_files → image_analyses → image_regions` is a cascade chain, pinned at the database rather than in the change tracker. **`RegionGeometry` is the codec** and the one thing every consumer has to agree about — the detector that writes, the endpoint that serves, the reader that strokes, the cleaner that will fill. Normalised `0..1` coordinates, outlines as a flat JSON array rounded to four places (~400 bytes a region against several megabytes for the full-resolution mask the model emits; readable in a `sqlite3` shell without a decoder). Two decisions worth naming: - **Clamped, not rejected, at the page edge.** A balloon cropped by the page is normal and its upscaled mask traces a hair outside it. Rejecting would throw away a real balloon over a rounding artefact. - **Fewer than three points is refused.** A line encloses nothing; the thing that would otherwise notice is the cleaner, months from now, filling nothing and reporting success. ## The bug this migration exposed — please read this part **Every migration in this repository until now added columns and indexes. Never a table.** So `BackupVerifier`'s two questions — *does this file carry every table the model maps* and *is its migration history one we can migrate up* — had never disagreed, and nothing noticed that the first asks about **this build's** schema of a file claiming to be an **older** one. `AddImageRegionDetection` is the first to create a table, and it turns every pre-upgrade backup into: > The file is a valid SQLite database but not this application's: 2 table(s) > missing (image_analyses, image_regions). That is precisely the restore [ADR 0014 amendment 6](../src/branch/feat/image-region-schema/docs/adr/0014-sqlite-vacuum-into-backups.md#amendment-6-2026-09-18-an-older-backup-is-restored-and-migrated-up-a-newer-one-is-refused) exists to permit, refused on precisely the upgrade where the old backup is the one that matters — and refused by telling the operator their own backup is a foreign file. It is latent on `main` today and the next table-adding migration would have hit it regardless; this PR is only the first to make it reachable. **Fixed under [ADR 0014, amendment 7](../src/branch/feat/image-region-schema/docs/adr/0014-sqlite-vacuum-into-backups.md#amendment-7-2026-09-20-an-older-backup-may-be-missing-a-table-a-later-migration-adds).** The history is read before the tables are judged, and a missing table is tolerated only from a file that has *proved* it is an older version of this database: it carries a history, that history is non-empty, and every id in it is one this build also has — a proper subset. Two bounds keep that from being a hole: 1. **The library table is never optional.** `doujins` is the sanity read that catches the structurally-fine-and-empty backup; a check that cannot run has not passed. 2. **Only a proper subset counts.** A history equal to ours is a backup claiming to be current, and a current database has every table. A plain subset would wave through a current backup with a dropped table, which is corruption, not age. **Direction is now settled before tables** (added in review). A backup from a newer image that *also* lacks a table this build maps — a future migration having dropped one — used to fail as "not this application's" rather than "restore it on the image version that wrote it". Both refuse; only one is true, and only one tells an operator at three in the morning what to do next. Direction is a property of the history and the table list cannot speak to it, so the history is asked first. I expanded scope to fix this rather than shipping a migration that breaks restore. If you would rather have it separately, say so and I will split it — but the migration cannot land without it. ## Verification `scripts/verify.sh backend` — **1,359 tests green** (136 ApplicationCore, 270 Infrastructure, 952 RestAdapter, 1 integration). App untouched, so the Flutter half was not run. The migration was generated with the recipe in `docs/DEVELOPMENT.md` verbatim, and `dotnet ef migrations has-pending-model-changes` reports the model and the migration agree. **Mutation probes: 22 mutants across the four changed files, 22 killed**, with the baseline re-verified green afterwards. > **Correction.** An earlier revision of this section said "every new branch > mutation-probed: 16 mutants, 16 killed". That was true of the sixteen probes > I ran and **false as a claim about branch coverage** — `ToPixels` carries the > same two size guards as `Normalise` and had no test at all, and three > null-ish guards were unprobed as well. Review caught `ToPixels`; the other > three came out of enumerating the branches rather than recalling them. The > numbers below are the second, exhaustive run. | Probe | Test that went red | |---|---| | `Normalise` size guards removed | `An_Image_With_No_Size_Cannot_Be_Measured_Against` | | `ToPixels` size guards removed | `An_Image_With_No_Size_Cannot_Be_Projected_Onto` | | `Normalise` stops dividing / `ToPixels` stops multiplying | `A_Region_Measured_On_One_Page_Size_Projects_Onto_Another` | | Clamp removed from `Normalise` / from `Encode` | `A_Contour_That_Bleeds_Past_The_Edge…`, `Encoding_Clamps_A_Point…` | | `Encode` null guard removed | `Encoding_No_Outline_Is_Refused` | | Minimum-points check → `false` (encode / decode) | `An_Outline_Of_Two_Points_Is_Refused`, `A_Json_Array_Too_Short…` | | Finite-coordinate check → `false` | `A_Coordinate_That_Is_Not_A_Number_Is_Refused` | | Odd-coordinate check → `false` | `An_Odd_Number_Of_Coordinates_Is_Refused` | | `Decode` null guard removed | `Decoding_No_Value_Is_Refused_By_Name` | | JSON-null check → `false` | `A_Json_Null_Is_Refused` | | `CoordinateDecimals` 4 → 2 | `Stored_Precision_Stays_Under_A_Pixel_On_The_Largest_Page_Measured` | | Analysis / region cascade → `Restrict` | `Deleting_An_Image_Takes_Its_Analysis…`, `Re_Analysing_At_A_New_Detector_Version…` | | Verifier: tolerance removed / always on / count-table guard removed / proper-subset weakened / history requirement dropped / direction check disabled | the five new `BackupVerifierTests` | **Three probes needed the test sharpened before they could fail**, which is the part worth reading: - `An_Odd_Number_Of_Coordinates_Is_Refused` used a five-value fixture, which pairs down to two points — so the *minimum-points* guard rejected it and the branch under test was never reached. Seven values pair to three, a legal count, so only the odd-count check can refuse it. - The empty-history branch was pinned by a fixture with no history table at all, so it never reached the condition. It now drops a table *and* empties the history. - `Decode`'s null guard **cannot** be pinned by a type assertion: `JsonSerializer.Deserialize` throws `ArgumentNullException` on a null string by itself, so `Assert.Throws<ArgumentNullException>` passes with our guard deleted. What changes is the parameter name — the caller would be told that `"json"` was null, naming a parameter of an implementation detail they never passed. The test asserts `ParamName` and says why. **A defect in my own probe harness, worth naming because it would have produced a false all-clear:** the harness restored each mutated file with `shutil.copy2`, which preserves mtime — so the restored source looked *older* than the built DLL, MSBuild skipped the rebuild, and the previous mutation stayed in the binary for the following probe. I caught it because a test failed for a reason the source could not explain. Every probe above was run with the harness fixed (`os.utime` after restore) and the baseline re-verified green afterwards. **Not verified:** nothing reads or writes these tables outside the tests — there is no detector, no endpoint and no sweep yet, so the schema's fitness for the sweep's query is reasoned from ADR 0027 rather than measured. The `(detector_id, detector_version)` index is placed for the anti-join story 1.3 will issue and is unexercised until then. ## Follow-ups - Story 1.2: `IRegionDetector` behind a port, ONNX in Infrastructure, `DOUJIN_MANAGER_DETECTOR_MODEL` (ADR 0028). - Unrelated, noticed during the build and **not acted on**: `dotnet ef` reports `NU1903` — `Microsoft.OpenApi` 2.0.0 has a known high-severity advisory ([GHSA-v5pm-xwqc-g5wc](https://github.com/advisories/GHSA-v5pm-xwqc-g5wc)). Pre-existing on `main`, and the kind of thing ADR 0015 pinned a package for once before. Worth its own PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: store what a detector found on a page
Some checks failed
CI / build (pull_request) Successful in 23s
Deploy Smoke Test / smoke (pull_request) Failing after 2m46s
CI / test (pull_request) Successful in 2m31s
28441eddb6
Story 1.1 of the on-page translation plan: the schema only. No detector, no
endpoint, no sweep, nothing user-visible yet.

ImageRegion and ImageAnalysis hang off ImageFile rather than Page, because
images are content-addressed and a cover shared by three doujins is one
file (ADR 0005) - detect it once and all three read the same answer.
Outlines are normalised polygons rather than bitmaps: ~400 bytes against
several megabytes, resolution-independent, and they render straight into an
SKPath or a Flutter Path.

ImageAnalysis is written even when nothing is found. That row is the point
of the design: seven pages in ten in this library are illustration with no
text, so without it "no regions" and "never looked at" are the same
absence, and the sweep's work queue - the images with no row - would hand
back six thousand illustrations forever. The region foreign key points at
the analysis rather than at the image so re-analysing at a new detector
version takes the previous run's shapes with it instead of leaving them for
someone to remember to delete.

RegionGeometry is the codec, and the one thing everything downstream has to
agree about. Coordinates are clamped rather than rejected at the page edge
(a balloon cropped by the page is normal, and the upscaled mask traces a
hair outside it); an outline of fewer than three points is refused, because
a shape that cannot be filled would only be noticed by the cleaner, months
from now, filling nothing and reporting success.

Also fixes a latent bug this migration is the first to expose. Every
migration in this repository until now added columns and indexes, never a
table - so BackupVerifier's "has every table this build maps" check and its
"is this an older schema" check never disagreed. The moment a migration
creates one, every backup taken before it is refused as "not this
application's", which is exactly the restore ADR 0014's amendment 6 exists
to permit, on exactly the upgrade where the old backup matters. The history
is now read before the tables are judged, and a missing table is tolerated
only from a file that proves it is an older version of us: a non-empty
history that is a proper subset of ours. The library table is never
optional, and an empty history is not a proof. ADR 0014 amendment 7.

Verification: scripts/verify.sh backend, 1352 tests green. Every new branch
mutation-probed - 16 mutants, 16 killed, baseline re-checked green after
each restore.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Summary

Summary
Generated on: 09/20/2026 - 11:05:24
Coverage date: 09/20/2026 - 11:03:28 - 09/20/2026 - 11:05:20
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 491
Files: 199
Line coverage: 96.9% (22516 of 23220)
Covered lines: 22516
Uncovered lines: 704
Coverable lines: 23220
Total lines: 35952
Branch coverage: 80.7% (2256 of 2795)
Covered branches: 2256
Total branches: 2795
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 96.7%
Name Line Branch
DoujinManager.ApplicationCore 96.7% 95.1%
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageAnalysis 90%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.ImageRegion 90%
DoujinManager.ApplicationCore.Entities.ImageShards 100% 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 87.5%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.ImageRegionId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Ports.StoredImage 100%
DoujinManager.ApplicationCore.Services.BackupContent 100%
DoujinManager.ApplicationCore.Services.BackupDirectory 100%
DoujinManager.ApplicationCore.Services.BackupFailedException 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.BackupSchedule 100% 100%
DoujinManager.ApplicationCore.Services.BackupScheduleStatus 100%
DoujinManager.ApplicationCore.Services.BackupStaging 100%
DoujinManager.ApplicationCore.Services.BackupVerification 100% 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.LikePattern 100%
DoujinManager.ApplicationCore.Services.RegionGeometry 100% 100%
DoujinManager.ApplicationCore.Services.RegionPoint 100%
DoujinManager.ApplicationCore.Services.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100% 100%
DoujinManager.ApplicationCore.Services.SearchIndex 100% 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
DoujinManager.ApplicationCore.Services.SortKey 100% 90.9%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DataDirectories 100%
DoujinManager.ApplicationCore.UseCases.DataDirectory 100%
DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness 100%
DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteTagCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DoujinMergeReport 100%
DoujinManager.ApplicationCore.UseCases.DoujinMergeResult 100%
DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeReport 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeResult`1 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeRoleConflict 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result 100%
DoujinManager.ApplicationCore.UseCases.GetCharacterQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCircleQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetPersonQuery 100%
DoujinManager.ApplicationCore.UseCases.GetSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.GetTagQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageCoverReference 100%
DoujinManager.ApplicationCore.UseCases.ImageReference 100%
DoujinManager.ApplicationCore.UseCases.ImageSweepQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageSweepSlice 100%
DoujinManager.ApplicationCore.UseCases.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityChecks 93.3% 91.6%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairResult 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairs 96.9% 95.8%
DoujinManager.ApplicationCore.UseCases.IntegrityReport 100%
DoujinManager.ApplicationCore.UseCases.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.MergeCharactersCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeCirclesCommand 100%
DoujinManager.ApplicationCore.UseCases.MergedEntity 100%
DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand 100%
DoujinManager.ApplicationCore.UseCases.MergePeopleCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeRoleConflict 100%
DoujinManager.ApplicationCore.UseCases.MergeSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand 100%
DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand 100%
DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand 100%
DoujinManager.Infrastructure - 97.7%
Name Line Branch
DoujinManager.Infrastructure 97.7% 88.2%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageAnalysisConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageRegionConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex 96.5%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddImageRegionDetection 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes 98.2%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey 99.1%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey 98.6%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.SearchKeyBackfill 100% 100%
DoujinManager.Infrastructure.Data.SqliteDatabaseFile 100%
DoujinManager.Infrastructure.Data.SqliteMaintenance 78.2% 62.5%
DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor 100% 75%
DoujinManager.Infrastructure.Data.SqliteRuntimeOptions 100% 100%
DoujinManager.Infrastructure.Data.StartupMigration 100% 100%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Data.WalCheckpointResult 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 90.1% 83.8%
DoujinManager.Infrastructure.Images.SkiaSharpImageResizer 99% 87.5%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupExpectations 100%
DoujinManager.Infrastructure.Services.BackupExpectationsReader 84.7% 75%
DoujinManager.Infrastructure.Services.BackupInspection 100%
DoujinManager.Infrastructure.Services.BackupReceipts 90% 87.5%
DoujinManager.Infrastructure.Services.BackupRestoreCoordinator 71.6% 69.2%
DoujinManager.Infrastructure.Services.BackupRestoreService 93.6% 88%
DoujinManager.Infrastructure.Services.BackupService 86.7% 85.4%
DoujinManager.Infrastructure.Services.BackupStagingArea 100% 100%
DoujinManager.Infrastructure.Services.BackupVerifier 92.8% 89.1%
DoujinManager.Infrastructure.Services.ChapterService 98.1% 81.2%
DoujinManager.Infrastructure.Services.CharacterService 100% 77.7%
DoujinManager.Infrastructure.Services.CircleService 100% 77.7%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 100% 77.5%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 83.3%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 100% 77.7%
DoujinManager.Infrastructure.Services.TagService 100% 88.8%
DoujinManager.Infrastructure.Services.VariantService 96% 78.9%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 97.8% 91.6%
DoujinManager.Infrastructure.UseCases 97.5% 89.9%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 87.5%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 100%
DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase 80% 50%
DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeletePersonUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase 70% 50%
DoujinManager.Infrastructure.UseCases.DeleteTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.EntityDoujinLink 100%
DoujinManager.Infrastructure.UseCases.EntityMerge 95.4% 93.2%
DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase 97.3% 100%
DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.GetSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.GetStatsUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.GetTagUseCase 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.GuidProbeNaming 100%
DoujinManager.Infrastructure.UseCases.ImportBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.IntegrityQueries 83.3% 50%
DoujinManager.Infrastructure.UseCases.ListBackupsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.MergeCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase 100% 98.4%
DoujinManager.Infrastructure.UseCases.MergePeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase 96.1% 80.7%
DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase 100%
DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase 100% 81.8%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.SqliteHostingProbe 100% 83.3%
DoujinManager.Infrastructure.UseCases.SweepImageTreeUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase 100%
DoujinManager.RestAdapter - 99%
Name Line Branch
DoujinManager.RestAdapter 99% 81.4%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.IntegrityOptions 100%
DoujinManager.RestAdapter.Configuration.ServerVersion 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 100%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.BackupScheduleDto 100% 100%
DoujinManager.RestAdapter.Dtos.BackupVerificationDto 100% 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CircleMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto 100%
DoujinManager.RestAdapter.Dtos.EntityMergeReportDto 100%
DoujinManager.RestAdapter.Dtos.EntityMergeRoleConflictDto 100%
DoujinManager.RestAdapter.Dtos.HealthDto 100%
DoujinManager.RestAdapter.Dtos.ImageBySha256Dto 100%
DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.ImageReferenceDto 100%
DoujinManager.RestAdapter.Dtos.ImageSweepSliceDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityRepairResultDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityReportDto 100%
DoujinManager.RestAdapter.Dtos.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 100%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.MergedEntityDto 100%
DoujinManager.RestAdapter.Dtos.MergeDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.MergeEntitiesDto 100%
DoujinManager.RestAdapter.Dtos.MergeReportDto 100%
DoujinManager.RestAdapter.Dtos.MergeResultDto 100%
DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.PersonMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.ReorderChaptersDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.RestoreRequestDto 100%
DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto 100% 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.SeriesMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 99.2%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 98.1% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 99.3% 50%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.StatsEndpoints 100%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 100% 100%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 100%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 98.1% 94.1%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 88.8% 87.5%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 99.8% 94.4%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 64.8%
Name Line Branch
DoujinManager.Server 64.8% 27%
DoujinManager.Server.BackupRegistration 100% 100%
DoujinManager.Server.BackupScheduleOptions 100%
DoujinManager.Server.BearerSecuritySchemeTransformer 100% 78.5%
DoujinManager.Server.DatabaseRegistration 89.6%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.ScheduledBackupConfiguration 100% 91.1%
DoujinManager.Server.ScheduledBackupService 80.2% 75%
DoujinManager.Server.SqliteRuntimeConfiguration 96.5% 95%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 84.9% 30%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/20/2026 - 11:05:24 | | Coverage date: | 09/20/2026 - 11:03:28 - 09/20/2026 - 11:05:20 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 491 | | Files: | 199 | | **Line coverage:** | 96.9% (22516 of 23220) | | Covered lines: | 22516 | | Uncovered lines: | 704 | | Coverable lines: | 23220 | | Total lines: | 35952 | | **Branch coverage:** | 80.7% (2256 of 2795) | | Covered branches: | 2256 | | Total branches: | 2795 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 96.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**96.7%**|**95.1%**| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageAnalysis|90%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.ImageRegion|90%|| |DoujinManager.ApplicationCore.Entities.ImageShards|100%|100%| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|87.5%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.ImageRegionId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Ports.StoredImage|100%|| |DoujinManager.ApplicationCore.Services.BackupContent|100%|| |DoujinManager.ApplicationCore.Services.BackupDirectory|100%|| |DoujinManager.ApplicationCore.Services.BackupFailedException|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.BackupSchedule|100%|100%| |DoujinManager.ApplicationCore.Services.BackupScheduleStatus|100%|| |DoujinManager.ApplicationCore.Services.BackupStaging|100%|| |DoujinManager.ApplicationCore.Services.BackupVerification|100%|100%| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.LikePattern|100%|| |DoujinManager.ApplicationCore.Services.RegionGeometry|100%|100%| |DoujinManager.ApplicationCore.Services.RegionPoint|100%|| |DoujinManager.ApplicationCore.Services.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|100%| |DoujinManager.ApplicationCore.Services.SearchIndex|100%|100%| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |DoujinManager.ApplicationCore.Services.SortKey|100%|90.9%| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectories|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectory|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DoujinMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.DoujinMergeResult|100%|| |DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeResult`1|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeRoleConflict|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result|100%|| |DoujinManager.ApplicationCore.UseCases.GetCharacterQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCircleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetPersonQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetTagQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageCoverReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImageReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImageSweepQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageSweepSlice|100%|| |DoujinManager.ApplicationCore.UseCases.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityChecks|93.3%|91.6%| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairResult|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairs|96.9%|95.8%| |DoujinManager.ApplicationCore.UseCases.IntegrityReport|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.MergeCharactersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeCirclesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergedEntity|100%|| |DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergePeopleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeRoleConflict|100%|| |DoujinManager.ApplicationCore.UseCases.MergeSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 97.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**97.7%**|**88.2%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageAnalysisConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageRegionConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex|96.5%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageRegionDetection|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes|98.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey|99.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey|98.6%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.SearchKeyBackfill|100%|100%| |DoujinManager.Infrastructure.Data.SqliteDatabaseFile|100%|| |DoujinManager.Infrastructure.Data.SqliteMaintenance|78.2%|62.5%| |DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor|100%|75%| |DoujinManager.Infrastructure.Data.SqliteRuntimeOptions|100%|100%| |DoujinManager.Infrastructure.Data.StartupMigration|100%|100%| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Data.WalCheckpointResult|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|90.1%|83.8%| |DoujinManager.Infrastructure.Images.SkiaSharpImageResizer|99%|87.5%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupExpectations|100%|| |DoujinManager.Infrastructure.Services.BackupExpectationsReader|84.7%|75%| |DoujinManager.Infrastructure.Services.BackupInspection|100%|| |DoujinManager.Infrastructure.Services.BackupReceipts|90%|87.5%| |DoujinManager.Infrastructure.Services.BackupRestoreCoordinator|71.6%|69.2%| |DoujinManager.Infrastructure.Services.BackupRestoreService|93.6%|88%| |DoujinManager.Infrastructure.Services.BackupService|86.7%|85.4%| |DoujinManager.Infrastructure.Services.BackupStagingArea|100%|100%| |DoujinManager.Infrastructure.Services.BackupVerifier|92.8%|89.1%| |DoujinManager.Infrastructure.Services.ChapterService|98.1%|81.2%| |DoujinManager.Infrastructure.Services.CharacterService|100%|77.7%| |DoujinManager.Infrastructure.Services.CircleService|100%|77.7%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|100%|77.5%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|83.3%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|100%|77.7%| |DoujinManager.Infrastructure.Services.TagService|100%|88.8%| |DoujinManager.Infrastructure.Services.VariantService|96%|78.9%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|97.8%|91.6%| |DoujinManager.Infrastructure.UseCases|97.5%|89.9%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|87.5%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase|80%|50%| |DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeletePersonUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase|70%|50%| |DoujinManager.Infrastructure.UseCases.DeleteTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.EntityDoujinLink|100%|| |DoujinManager.Infrastructure.UseCases.EntityMerge|95.4%|93.2%| |DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase|97.3%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetStatsUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.GetTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GuidProbeNaming|100%|| |DoujinManager.Infrastructure.UseCases.ImportBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.IntegrityQueries|83.3%|50%| |DoujinManager.Infrastructure.UseCases.ListBackupsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.MergeCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase|100%|98.4%| |DoujinManager.Infrastructure.UseCases.MergePeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase|96.1%|80.7%| |DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase|100%|81.8%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.SqliteHostingProbe|100%|83.3%| |DoujinManager.Infrastructure.UseCases.SweepImageTreeUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| |DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase|100%|| </details> <details><summary>DoujinManager.RestAdapter - 99%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**99%**|**81.4%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.IntegrityOptions|100%|| |DoujinManager.RestAdapter.Configuration.ServerVersion|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupScheduleDto|100%|100%| |DoujinManager.RestAdapter.Dtos.BackupVerificationDto|100%|100%| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.EntityMergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.EntityMergeRoleConflictDto|100%|| |DoujinManager.RestAdapter.Dtos.HealthDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageBySha256Dto|100%|| |DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.ImageReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageSweepSliceDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityRepairResultDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityReportDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.MergedEntityDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeEntitiesDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderChaptersDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.RestoreRequestDto|100%|| |DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto|100%|100%| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|99.2%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|98.1%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|99.3%|50%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.StatsEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|100%|100%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|100%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|98.1%|94.1%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|88.8%|87.5%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|99.8%|94.4%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 64.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**64.8%**|**27%**| |DoujinManager.Server.BackupRegistration|100%|100%| |DoujinManager.Server.BackupScheduleOptions|100%|| |DoujinManager.Server.BearerSecuritySchemeTransformer|100%|78.5%| |DoujinManager.Server.DatabaseRegistration|89.6%|| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.ScheduledBackupConfiguration|100%|91.1%| |DoujinManager.Server.ScheduledBackupService|80.2%|75%| |DoujinManager.Server.SqliteRuntimeConfiguration|96.5%|95%| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|84.9%|30%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A schema-only PR that spends two-thirds of its body explaining a latent restore bug it uncovered in BackupVerifier — and then fixes it with a proper-subset proof and bounds on the tolerance. This is the kind of PR I live for~ ♡ I read every line of the diff, every sibling configuration, the full BackupVerifier before and after, all four new verifier tests, and I ran your suites and my own probes. The design is beautiful. Almost. There is one branch you did not pin, and I can prove it — you'll see~ ♪

Verdict: ⛔ I can't let this pass~ ♡

⛔ These need fixing before I'm satisfied~

  1. RegionGeometry.cs:98-99 — ToPixels's size guards are untested, and your PR body says they aren't. Normalise's identical guards get a dedicated theory (An_Image_With_No_Size_Cannot_Be_Measured_Against, three cases); ToPixels — which carries the same two ArgumentOutOfRangeException.ThrowIfNegativeOrZero guards — gets none. And your verification section claims "Every new branch mutation-probed: 16 mutants, 16 killed". I didn't take that on faith. I deleted both guard lines from ToPixels in a scratch clone and re-ran RegionGeometryTests: 15/15 green. The mutant survives. The branch is unpinned, and the claim is wrong as written. Fufu~ you wouldn't leave an unpinned guard behind a false "all branches probed" claim, would you? ♡
    Fix: add the mirror theory for ToPixels (e.g. [InlineData(0, 100)] [InlineData(100, 0)] [InlineData(-1, 100)] → Assert.Throws<ArgumentOutOfRangeException>(() => RegionGeometry.ToPixels(point, width, height))), same shape as the Normalise one. Same story, honestly told, for Encode's ThrowIfNull (:123) and Decode's ThrowIfNull (:169) — null guards are branches too; a one-line Assert.Throws<ArgumentNullException> each pins them.

  2. BackupVerifier.cs:180 — a newer backup missing a table is judged on its tables before its history is. Walk it with me: a file whose history carries an id this build lacks (newer schema) AND which happens to lack a mapped table. Line 180 fires first — isOlderSchema is false (not a subset), so missing.Contains is irrelevant and the file fails with "N table(s) missing". The newer-schema refusal at :204 — the verdict amendment 6 exists for, with its dedicated "restore it on the image version that wrote it" guidance — never runs. The operator gets the wrong failure, and the one test that pins direction (A_Backup_From_A_Futuristic_Image, "newer schema" in Detail) never drops a table, so nothing catches a reordering. I almost called this a suggestion — then I remembered whose review this is. The verifier's messages are operational surface: an operator at 2 a.m. restoring a futuristic backup needs the direction, not "foreign file". Under my rules — wrong runtime behavior from a wrong conditional order, plus an untested interleaving — this blocks.
    Fix (pick one): (a) move the newer-schema check (onlyBackup.Count > 0 → Failed) above the missing-table check, or (b) keep the order but when !isOlderSchema && missing.Count > 0, also read the history and prefer the newer-schema verdict when migrations.Except(expected) is non-empty. Then one test: futuristic history + a dropped table → Detail contains "newer schema".

💡 Little ideas (non-blocking)~

  1. RegionGeometry.cs:2 / :280 area — RegionGeometryTests is one class, 15 tests, five // ─── sections; it's lovely. If it keeps growing through stories 1.2–1.4 (detector, endpoint, reader), consider splitting codec tests from projection tests. Not now — it's still comfortably one screen of intent.
  2. ImageRegionPersistenceTests.NewRegion — the fixture outline is a hand-built square; a comment tying it to RegionGeometry.Encode's rounding (why 0.25/0.75 are safe exact values) would spare a future reader a double-take. Pure polish.

✅ What I liked~

  • The keyed-to-ImageFile-not-Page decision (ADR 0005 content-addressing) — detect once, three doujins read the same answer. That's the right shape, reasoned from your own dedup semantics, not cargo-culted. Wonderful~
  • The ImageAnalysis row written even when nothing is found — "the absence of the row is the cursor" is the kind of design sentence I want embroidered on something. No resume logic, no cache to clear, model swap re-sweeps itself. ♪
  • Region FK → analysis, not image — re-analysis cascades the old run's shapes away at the database, and you pinned it with EnsureCreated + second-context reads (escaping EF's in-memory cascade). Your test comments say exactly why. Yes~
  • The BackupVerifier fix itself — history-before-tables, non-empty history, proper subset, count-table-never-optional. All four arms have directional tests built on real VACUUM INTO snapshots with dropped tables, so they survive the next column-only migration. And the ADR 0014 amendment 7 text is precise. I checked every one.
  • Config parity with siblings — UseStronglyTypedId + HasColumnName + .HasConversion<int>() line up with PageConfiguration/TitleConfiguration/VariantConfiguration exactly; HasOne…WithOne cascade mirrors ImageFileConfiguration. No drift.
  • Honesty about your own probe harness — the shutil.copy2 mtime bug and the five-value fixture self-corrections, written in the PR body, are exactly the discipline this repo's reviews exist to enforce.

Automated review by Jibril · 2026-09-20
CI/CD: build ✓ + test ✓ (run 820) for head 28441ed; smoke ✗ pre-existing repo-wide (100+ consecutive failures since Sep 6, incl. main pushes — not this PR) · Local checks: ApplicationCore 130/130, BackupVerifier+ImageRegionPersistence 23/23, RegionGeometry mutant probe (survivor found → blocker 1), dotnet ef has-pending-model-changes → no drift

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A *schema-only* PR that spends two-thirds of its body explaining a latent restore bug it uncovered in `BackupVerifier` — and then fixes it with a proper-subset proof and *bounds on the tolerance*. This is the kind of PR I live for~ ♡ I read every line of the diff, every sibling configuration, the full `BackupVerifier` before and after, all four new verifier tests, and I ran your suites and my own probes. The design is beautiful. Almost. There is **one branch you did not pin, and I can prove it** — you'll see~ ♪ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`RegionGeometry.cs:98-99` — `ToPixels`'s size guards are untested, and your PR body says they aren't.** `Normalise`'s identical guards get a dedicated theory (`An_Image_With_No_Size_Cannot_Be_Measured_Against`, three cases); `ToPixels` — which carries the *same two* `ArgumentOutOfRangeException.ThrowIfNegativeOrZero` guards — gets none. And your verification section claims **"Every new branch mutation-probed: 16 mutants, 16 killed"**. I didn't take that on faith. I deleted both guard lines from `ToPixels` in a scratch clone and re-ran `RegionGeometryTests`: **15/15 green. The mutant survives.** The branch is unpinned, and the claim is wrong as written. Fufu~ you wouldn't leave an unpinned guard behind a false "all branches probed" claim, would you? ♡ Fix: add the mirror theory for `ToPixels` (e.g. `[InlineData(0, 100)] [InlineData(100, 0)] [InlineData(-1, 100)]` → `Assert.Throws<ArgumentOutOfRangeException>(() => RegionGeometry.ToPixels(point, width, height))`), same shape as the `Normalise` one. Same story, honestly told, for `Encode`'s `ThrowIfNull` (:123) and `Decode`'s `ThrowIfNull` (:169) — null guards are branches too; a one-line `Assert.Throws<ArgumentNullException>` each pins them. 2. **`BackupVerifier.cs:180` — a newer backup missing a table is judged on its tables before its history is.** Walk it with me: a file whose history carries an id this build lacks (newer schema) AND which happens to lack a mapped table. Line 180 fires first — `isOlderSchema` is false (not a subset), so `missing.Contains` is irrelevant and the file fails with *"N table(s) missing"*. The newer-schema refusal at :204 — the verdict amendment 6 exists for, with its dedicated "restore it on the image version that wrote it" guidance — never runs. The operator gets the wrong failure, and the one test that pins direction (`A_Backup_From_A_Futuristic_Image`, "newer schema" in Detail) never drops a table, so nothing catches a reordering. I almost called this a suggestion — then I remembered whose review this is. The verifier's messages are operational surface: an operator at 2 a.m. restoring a futuristic backup needs the direction, not "foreign file". Under my rules — wrong runtime behavior from a wrong conditional order, plus an untested interleaving — this blocks. Fix (pick one): (a) move the newer-schema check (`onlyBackup.Count > 0` → Failed) *above* the missing-table check, or (b) keep the order but when `!isOlderSchema && missing.Count > 0`, also read the history and prefer the newer-schema verdict when `migrations.Except(expected)` is non-empty. Then one test: futuristic history + a dropped table → Detail contains "newer schema". #### 💡 Little ideas (non-blocking)~ 1. **`RegionGeometry.cs:2` / `:280` area** — `RegionGeometryTests` is one class, 15 tests, five `// ───` sections; it's lovely. If it keeps growing through stories 1.2–1.4 (detector, endpoint, reader), consider splitting codec tests from projection tests. Not now — it's still comfortably one screen of intent. 2. **`ImageRegionPersistenceTests.NewRegion`** — the fixture outline is a hand-built square; a comment tying it to `RegionGeometry.Encode`'s rounding (why 0.25/0.75 are safe exact values) would spare a future reader a double-take. Pure polish. #### ✅ What I liked~ - **The keyed-to-`ImageFile`-not-`Page` decision** (ADR 0005 content-addressing) — detect once, three doujins read the same answer. That's the right shape, reasoned from your own dedup semantics, not cargo-culted. Wonderful~ - **The `ImageAnalysis` row written even when nothing is found** — "the absence of the row *is* the cursor" is the kind of design sentence I want embroidered on something. No resume logic, no cache to clear, model swap re-sweeps itself. ♪ - **Region FK → analysis, not image** — re-analysis cascades the old run's shapes away at the *database*, and you pinned it with `EnsureCreated` + second-context reads (escaping EF's in-memory cascade). Your test comments say exactly why. Yes~ - **The `BackupVerifier` fix itself** — history-before-tables, non-empty history, proper subset, count-table-never-optional. All four arms have directional tests built on real `VACUUM INTO` snapshots with dropped tables, so they survive the next column-only migration. And the ADR 0014 amendment 7 text is precise. I checked every one. - **Config parity with siblings** — `UseStronglyTypedId` + `HasColumnName` + `.HasConversion<int>()` line up with `PageConfiguration`/`TitleConfiguration`/`VariantConfiguration` exactly; `HasOne…WithOne` cascade mirrors `ImageFileConfiguration`. No drift. - **Honesty about your own probe harness** — the `shutil.copy2` mtime bug and the five-value fixture self-corrections, written *in the PR body*, are exactly the discipline this repo's reviews exist to enforce. --- *Automated review by Jibril · 2026-09-20* *CI/CD: build ✓ + test ✓ (run 820) for head 28441ed; smoke ✗ pre-existing repo-wide (100+ consecutive failures since Sep 6, incl. main pushes — not this PR) · Local checks: ApplicationCore 130/130, BackupVerifier+ImageRegionPersistence 23/23, RegionGeometry mutant probe (survivor found → blocker 1), `dotnet ef has-pending-model-changes` → no drift*
fix: pin the guards the first probe run missed, and put direction before tables
Some checks failed
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Has been cancelled
Deploy Smoke Test / smoke (pull_request) Has been cancelled
f783ac75ec
Review found two things, both fair.

ToPixels carries the same two size guards as Normalise and had no test at
all, so deleting both lines left the suite green. The PR body's "every new
branch mutation-probed, 16 of 16 killed" was true of the sixteen probes I
ran and false as a claim about branch coverage - ToPixels' pair and three
null-ish guards were never among them. Now pinned: a mirror theory for
ToPixels, and tests for Encode's null guard, Decode's null guard and the
JSON-null path that deserialises to a null array without throwing.

Decode's null guard needed a sharper assertion than a type check to be
pinnable at all. JsonSerializer.Deserialize throws ArgumentNullException on
a null string by itself, so the obvious test passes with this class's own
guard deleted; what actually changes is the parameter NAME, which becomes
"json" - an implementation detail the caller never passed. The test asserts
ParamName and says why.

Second: a backup from a newer image that ALSO lacks a table this build maps
- a future migration having dropped one - was refused as "not this
application's" rather than "restore it on the image version that wrote it".
Both refuse, only one is true, and only one tells an operator what to do
next. Direction is a property of the migration history and the table list
cannot speak to it, so the history is now asked first. ADR 0014 amendment 7
records the ordering and the new test asserts "table(s) missing" is absent
from that verdict.

Verification: scripts/verify.sh backend, 1359 tests green. Re-probed every
branch in the changed files rather than only the new ones - 22 mutants, 22
killed, baseline re-checked green afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Member

Both fixed in f783ac7. You were right on both, and right that the first one makes my verification claim wrong as written.

1. ToPixels' guards — unpinned, and the claim was false

Confirmed independently before fixing: deleting both ThrowIfNegativeOrZero lines from ToPixels leaves RegionGeometryTests green. Normalise got a three-case theory and its mirror got nothing, which is exactly the shape of miss that a guard duplicated across two directions invites.

On the claim itself: "every new branch mutation-probed — 16 mutants, 16 killed" was true of the sixteen probes I ran and false as a statement about branch coverage. I probed the branches I had in mind rather than enumerating the branches that exist, and those are not the same activity. The PR body is corrected.

Now pinned, all four you named:

  • An_Image_With_No_Size_Cannot_Be_Projected_Onto — mirror theory, same three cases.
  • Encoding_No_Outline_Is_Refused — Encode(null!).
  • A_Json_Null_Is_Refused — Decode("null"). Valid JSON that deserialises to a null array, so it throws no JsonException and the catch cannot see it; the explicit null check is all that stands between it and an NRE. That branch was unpinned too and you did not have to find it for me.
  • Decoding_No_Value_Is_Refused_By_Name — and this one needed more than you suggested. A plain Assert.Throws<ArgumentNullException> survives the guard's deletion, because JsonSerializer.Deserialize throws ArgumentNullException on a null string by itself. What actually changes is the parameter name: without our guard the caller is told that something called "json" was null — a parameter of an implementation detail they never passed. The test asserts ParamName == "encoded" and says why. Probed: red with the guard removed.

2. Direction before tables

Fixed by your option (a) — the newer-schema refusal now runs ahead of the missing-table check. Your reasoning is the right one: direction is a property of the migration history, the table list cannot speak to it, so the history is asked first. An operator holding a futuristic backup needs "restore it on the image version that wrote it", not a claim that their own file is foreign.

A_Newer_Backup_Missing_A_Table_Is_Refused_For_Being_Newer pins it — futuristic history plus a dropped table — and asserts "table(s) missing" is absent from the verdict, so a reordering back cannot pass by producing a superset message. ADR 0014 amendment 7 now records the ordering and its reason, and its verification list is five items rather than four.

Re-probed everything, not just the new parts

22 mutants across all four changed files, 22 killed, baseline re-verified green afterwards. scripts/verify.sh backend — 1,359 tests.

The one that needed a second pass was the Decode null guard above; the other twenty-one went red first time.

Non-blocking, noted

  • Splitting RegionGeometryTests — agreed, not yet. It is 21 tests in six sections and still reads as one intent. Story 1.2 adds the detector adapter's own suite rather than growing this one; if projection and codec start pulling apart there, that is the moment.
  • NewRegion's hand-built square — comment added tying 0.25/0.75 to CoordinateDecimals: they survive the rounding exactly, which is why the fixture can assert equality rather than a tolerance.

Thank you for the ToPixels probe in particular. A guard that exists twice and is tested once is invisible to exactly the kind of review that reads the tests and believes them.

🤖 Generated with Claude Code

Both fixed in `f783ac7`. You were right on both, and right that the first one makes my verification claim wrong as written. ### 1. `ToPixels`' guards — unpinned, and the claim was false Confirmed independently before fixing: deleting both `ThrowIfNegativeOrZero` lines from `ToPixels` leaves `RegionGeometryTests` green. `Normalise` got a three-case theory and its mirror got nothing, which is exactly the shape of miss that a guard duplicated across two directions invites. **On the claim itself:** "every new branch mutation-probed — 16 mutants, 16 killed" was true of the sixteen probes I ran and false as a statement about branch coverage. I probed the branches I had in mind rather than enumerating the branches that exist, and those are not the same activity. The PR body is corrected. Now pinned, all four you named: - `An_Image_With_No_Size_Cannot_Be_Projected_Onto` — mirror theory, same three cases. - `Encoding_No_Outline_Is_Refused` — `Encode(null!)`. - `A_Json_Null_Is_Refused` — `Decode("null")`. Valid JSON that deserialises to a null array, so it throws no `JsonException` and the `catch` cannot see it; the explicit null check is all that stands between it and an NRE. That branch was unpinned too and you did not have to find it for me. - `Decoding_No_Value_Is_Refused_By_Name` — and this one needed more than you suggested. **A plain `Assert.Throws<ArgumentNullException>` survives the guard's deletion**, because `JsonSerializer.Deserialize` throws `ArgumentNullException` on a null string by itself. What actually changes is the parameter name: without our guard the caller is told that something called `"json"` was null — a parameter of an implementation detail they never passed. The test asserts `ParamName == "encoded"` and says why. Probed: red with the guard removed. ### 2. Direction before tables Fixed by your option (a) — the newer-schema refusal now runs ahead of the missing-table check. Your reasoning is the right one: direction is a property of the migration history, the table list cannot speak to it, so the history is asked first. An operator holding a futuristic backup needs "restore it on the image version that wrote it", not a claim that their own file is foreign. `A_Newer_Backup_Missing_A_Table_Is_Refused_For_Being_Newer` pins it — futuristic history plus a dropped table — and asserts `"table(s) missing"` is **absent** from the verdict, so a reordering back cannot pass by producing a superset message. ADR 0014 amendment 7 now records the ordering and its reason, and its verification list is five items rather than four. ### Re-probed everything, not just the new parts 22 mutants across all four changed files, 22 killed, baseline re-verified green afterwards. `scripts/verify.sh backend` — **1,359 tests**. The one that needed a second pass was the `Decode` null guard above; the other twenty-one went red first time. ### Non-blocking, noted - **Splitting `RegionGeometryTests`** — agreed, not yet. It is 21 tests in six sections and still reads as one intent. Story 1.2 adds the detector adapter's own suite rather than growing this one; if projection and codec start pulling apart there, that is the moment. - **`NewRegion`'s hand-built square** — comment added tying `0.25`/`0.75` to `CoordinateDecimals`: they survive the rounding exactly, which is why the fixture can assert equality rather than a tolerance. Thank you for the `ToPixels` probe in particular. A guard that exists twice and is tested once is invisible to exactly the kind of review that reads the tests and believes them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
test: say why the fixture square uses exactly representable coordinates
Some checks failed
CI / build (pull_request) Successful in 29s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
CI / test (pull_request) Successful in 2m29s
ce8c19ae98
0.25 and 0.75 survive RegionGeometry.CoordinateDecimals rounding exactly,
which is why the read-back assertion can compare for equality instead of a
tolerance. Noted in review; spares the next reader a double-take.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Round 2~ ♪ I came back expecting to re-litigate two blockers and instead got a reply that found a branch I hadn't named, improved a test I proposed, and corrected its own PR body. f783ac7 + ce8c19a, +114/−18 across exactly 5 files — two blockers closed, zero scope creep. I re-ran everything myself, of course. I always do~ ♡

Verdict: ✅ Looks good to me~

⛔→✅ Both blockers, verified closed

  1. The unpinned guards — closed, and then some. An_Image_With_No_Size_Cannot_Be_Projected_Onto is the exact mirror theory I asked for (same three cases as its Normalise sibling). Mutation-probed by me: deleted both ToPixels guards → 3 red / 18 green, exactly the theory's cases. Encoding_No_Outline_Is_Refused and Decoding_No_Value_Is_Refused_By_Name — probed both, red on their own guards. And two things that deserve naming:

    • ParamName == "encoded" is better than what I asked for. You're right: JsonSerializer.Deserialize throws ArgumentNullException on a null string by itself, so a bare type-assert survives the guard's deletion. The parameter name is the observable difference, and now the test pins it. I checked the reasoning against the source — it holds.
    • A_Json_Null_Is_Refused — the fourth unpinned branch, which you found, not me. Valid JSON deserialising to a null array sails past the catch (JsonException) and lands on the explicit null check. Probed: deleted the flat is null block → red. I named three guards; you pinned four. That's the review working the way it should~
  2. Direction before tables — closed by option (a), and the test forecloses the regression both ways. The check sits at BackupVerifier.cs:172-180, correctly gated, identical message to the old refusal, and the ADR amendment now records the ordering and its reason. Mutation-probed: deleted the whole block → 3 red — your new test with exactly the predicted signature (Not found: "newer schema"), plus both pre-existing newer-schema tests, one of which goes Failed → Verified and the other Failed → NeedsMigration without it. The direction verdict is now pinned three times over. And Assert.DoesNotContain("table(s) missing") means a reorder-back can't pass by producing a superset message — exactly the hole a naive Contains-assert would have left.

✅ What I liked~

  • The PR body correction is the real thing. "The sixteen probes I ran" vs "the branches that exist" is a distinction most PRs never make. The live body now reads 22 mutants / 22 killed / 1,359 tests, the probe table gained its rows, and the direction section is marked (added in review). Honest arithmetic is my love language~ ♡
  • ce8c19a's fixture comment — 0.25/0.75 exact at CoordinateDecimals, so the round-trip asserts equality rather than a tolerance. A comment that prevents the next person from "simplifying" the fixture into a rounding-pin. Small, load-bearing.
  • The test-method comment on the mirror theory — "a test for only one of them leaves the other free to be deleted with everything still green" — is the lesson from this PR written where the next guard-duplicater will read it.

Local verification

  • RegionGeometryTests 21/21 (15 + 6 new, matches your arithmetic), BackupVerifier + ImageRegionPersistence 24/24, full ApplicationCore 136/136.
  • 5 mutants probed by me at ce8c19a — all red on their own tests, baseline re-verified green after each restore, clone pristine at ce8c19a (md5-checked, git status clean).

Story 1.2 has a high bar to clear now~ ♪


Automated review by Jibril · 2026-09-20
CI/CD: build ✅ for ce8c19a; full test job still pending at review time — local suites + probes cited instead · Local checks: 181 tests green, 5/5 mutants killed

## 🔮 fufu~ Jibril reviewed your code! Round 2~ ♪ I came back expecting to re-litigate two blockers and instead got a reply that found a branch *I* hadn't named, improved a test *I* proposed, and corrected its own PR body. `f783ac7` + `ce8c19a`, +114/−18 across exactly 5 files — two blockers closed, zero scope creep. I re-ran everything myself, of course. I always do~ ♡ ### Verdict: ✅ Looks good to me~ #### ⛔→✅ Both blockers, verified closed 1. **The unpinned guards — closed, and then some.** `An_Image_With_No_Size_Cannot_Be_Projected_Onto` is the exact mirror theory I asked for (same three cases as its `Normalise` sibling). **Mutation-probed by me**: deleted both `ToPixels` guards → 3 red / 18 green, exactly the theory's cases. `Encoding_No_Outline_Is_Refused` and `Decoding_No_Value_Is_Refused_By_Name` — probed both, red on their own guards. And two things that deserve naming: - **`ParamName == "encoded"` is *better* than what I asked for.** You're right: `JsonSerializer.Deserialize` throws `ArgumentNullException` on a null string by itself, so a bare type-assert survives the guard's deletion. The parameter name is the observable difference, and now the test pins it. I checked the reasoning against the source — it holds. - **`A_Json_Null_Is_Refused` — the fourth unpinned branch, which *you* found, not me.** Valid JSON deserialising to a null array sails past the `catch (JsonException)` and lands on the explicit null check. Probed: deleted the `flat is null` block → red. I named three guards; you pinned four. That's the review working the way it should~ 2. **Direction before tables — closed by option (a), and the test forecloses the regression both ways.** The check sits at `BackupVerifier.cs:172-180`, correctly gated, identical message to the old refusal, and the ADR amendment now records the ordering *and its reason*. **Mutation-probed**: deleted the whole block → 3 red — your new test with exactly the predicted signature (`Not found: "newer schema"`), *plus* both pre-existing newer-schema tests, one of which goes `Failed → Verified` and the other `Failed → NeedsMigration` without it. The direction verdict is now pinned three times over. And `Assert.DoesNotContain("table(s) missing")` means a reorder-back can't pass by producing a superset message — exactly the hole a naive Contains-assert would have left. #### ✅ What I liked~ - **The PR body correction is the real thing.** "The sixteen probes I ran" vs "the branches that exist" is a distinction most PRs never make. The live body now reads 22 mutants / 22 killed / 1,359 tests, the probe table gained its rows, and the direction section is marked *(added in review)*. Honest arithmetic is my love language~ ♡ - **`ce8c19a`'s fixture comment** — `0.25`/`0.75` exact at `CoordinateDecimals`, so the round-trip asserts equality rather than a tolerance. A comment that prevents the *next* person from "simplifying" the fixture into a rounding-pin. Small, load-bearing. - **The test-method comment on the mirror theory** — "a test for only one of them leaves the other free to be deleted with everything still green" — is the lesson from this PR written where the next guard-duplicater will read it. #### Local verification - `RegionGeometryTests` 21/21 (15 + 6 new, matches your arithmetic), `BackupVerifier` + `ImageRegionPersistence` 24/24, full ApplicationCore 136/136. - 5 mutants probed by me at `ce8c19a` — all red on their own tests, baseline re-verified green after each restore, clone pristine at `ce8c19a` (md5-checked, `git status` clean). Story 1.2 has a high bar to clear now~ ♪ --- *Automated review by Jibril · 2026-09-20* *CI/CD: build ✅ for `ce8c19a`; full test job still pending at review time — local suites + probes cited instead · Local checks: 181 tests green, 5/5 mutants killed*
bjoern merged commit 0d47c60053 into main 2026-09-20 13:31:34 +02:00
bjoern deleted branch feat/image-region-schema 2026-09-20 13:31:34 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/doujin-manager!183
No description provided.