feat: merge duplicate people, circles, characters and series #179

Merged
bjoern merged 9 commits from feat/merge-two-people-circles-characters-or-series into main 2026-09-20 09:06:42 +02:00
Member

What's in

  • Duplicate people, circles, characters and series can be joined from the metadata pages' "Merge into…": the survivor absorbs every doujin link, the duplicate's display name is kept as an alias, and the duplicate is deleted. The dialog spells all three out before anything is written, and the affordance follows the server's own merge action, so tags never show it and an in-use entity — which cannot be deleted — can still be merged away.
  • New endpoint POST /api/{people|circles|characters|series}/{id}/merge, wrapped in the hypermedia envelope (ADR 0007) with its responses declared (ADR 0021). One shared engine (backend/src/DoujinManager.Infrastructure/UseCases/EntityMergeUseCases.cs) plus four thin use cases; a new read-only ApplicationCore/Entities/INamedEntity.cs is what lets it stay one implementation. Every link move goes through IDoujinService, so every doujin written to ages via ParentTouchHelper (ADR 0004).
  • Roles keep two axes: on a doujin the survivor already stood on, the survivor's roles stand and a differing source role is dropped and named in the report; on a doujin it stood nowhere on, every role the source held moves, since (doujin, person, role) makes the second a legal row. Aliases are unioned case-insensitively, an empty description takes the first source that has one, and the answer names the deleted sources.
  • Creating or renaming one of the four onto a name another entry already carries is now a 409 naming the existing spelling, compared over search_key (the #172 fold, so KAGAMI and kagami are one name). Advisory by design — no unique index, no collision-resolving migration — and an entry never clashes with a name it already carries, so both rows of a duplicate a library already holds stay editable. Aliases are deliberately not consulted, or a merge would make every absorbed name unusable ever after.
  • App-wide side effect worth flagging: ApiException.from now prefers the server's RFC 7807 detail for sub-500 responses, so that 409's sentence reaches the snack bar instead of "Request failed (HTTP 409).". This applies to every endpoint whose ProblemDetails carries a detail, not only to the entity writes.
  • ADR 0022 amended (2026-09-20) with the merge semantics, the 409 over search_key and its three deliberate limits. Eight create/rename endpoints gained .ProducesProblem(409) and backend/openapi/v1.json was regenerated. No new tunable, no migration.

Verification

  • Executed on 499fbc3 in a clean tree: scripts/verify.sh all → == verify OK (all) ==. Backend half: restore, Release build, test — 1272 tests, 0 failures (RestAdapter.Tests 908, Infrastructure.Tests 249, ApplicationCore.Tests 114, IntegrationTests 1). App half: submodule check, pub get, clean codegen with the committed-generated-files checksum guard, analyze, then All tests passed! at 1344 tests.
  • Layers exercised: REST (TestHost against real SQLite — merge end to end for all four kinds, the name-uniqueness theory over all four collections, the entity envelope and the unconditional merge action, the OpenAPI snapshot); persistence (link reassignment, composite-key de-duplication, alias union, source deletion, ADR-0004 parent touch including a doujin that only lost a duplicate link); use case (the shared engine through its four wirings); app (reducer, epics through createStore, the management-page affordance, the survivor picker dialog, and the dio repository calls' paths, bodies and parsing).
  • Mutation probes — run during implementation and again after the review fixes, each applied to committed code, built, run, and reverted with git checkout. 20 backend + 13 app in the first round; 16 in the fix round. Notable kills: only-the-first-link-assigned (the old destructive role behaviour) → red on the two new role tests; letting the entry snapshot grow (exactly the shipped bug) → red on three; each kind's name check removed → red on that kind's rows only, so the theory really is per-kind; .ProducesProblem(409) removed from POST /api/people → red via the OpenAPI snapshot; the dialog filter probe that used to survive → now red on both new picker tests. A re-probe round re-ran the blocker's exact mutant at EntityMergeUseCases.cs:213 (: first → : true): red, 1 of 25, on the new Two_Circle_Sources_On_The_Same_Doujin_Relink_It_Once, with the baseline 25/25 green.
  • Probes that survived, and where each gap is recorded: (a) the two "the row I loaded is gone" guards (updateTarget returning null, deleteSource returning false) — unreachable through the API because both rows are loaded inside the transaction that writes them — recorded in docs/BACKLOG.md together with the use-case-level test that would reach them; (b) the ADR-0004 "only write when there is something to write" guard — no kind's DTO carries updatedAt, so removing it is invisible over REST — recorded in the same backlog clause; (c) found by the re-probe after the last commit: alreadyLinkedSet.Add at EntityMergeUseCases.cs:191 survives || true (its twin relinkedSet.Add is pinned, red on 2 tests). Its only consequence is a repeated id in a report field the app does not render, and it does not break the documented "never overlaps relinkedDoujinIds" promise — this one is not in docs/BACKLOG.md, because it was found after the branch's last commit and this gate made no commits.
  • One assertion not claimed as pinning anything: the added Assert.Empty(Report.RoleConflicts) in Two_Sources_On_The_Same_Doujin_Relink_It_Once. Under the two-axis engine no conflict can arise on that fixture, so no mutant is killed by that line alone; it stays as a guard against a future re-merge of the two axes, and the test's other assertions are what kill the mutant.
  • Not verified / confirmed by reading only: nothing was run in the real app against a live server — no manual click-through of the merge dialog, no live backend, no image/filesystem path touched. The assistant's entity_write create path inherits the new 409 (a blind create of an existing name now fails instead of silently duplicating); that was confirmed by reading the code and by the full suites passing, not by driving the assistant. The wider ApiException detail change was likewise confirmed by the app suite and by reading, not end to end.

Follow-ups

All of these went into the "Entity merge and uniqueness checks" item in docs/BACKLOG.md:

  • Tags have no merge — it would be a fifth wiring of the same engine; their names have always been checked, so duplicates there are rarer.
  • The name check is advisory: no unique index behind search_key, so a library already holding two spellings keeps them and two concurrent creates can both land; closing it needs a collision-resolving migration.
  • Nothing proposes duplicate entity candidates the way GET /api/doujins/duplicates does for doujins — both spellings must be spotted by hand.
  • The app's picker merges one source at a time and lists only the entries already loaded in the list pane, although the API takes several sources at once.
  • The app does not surface EntityMergeReport at all (only deletedSources is read), so nothing tells the user a role was discarded and the dialog does not warn of it; surfacing it wants the doujin ids resolved to titles.
  • Three branches recorded rather than pinned: the two vanished-row guards and the ADR-0004 write guard, with the shape of the test each would need.
  • Not recorded: the alreadyLinkedSet.Add survivor described under Verification.

🤖 Generated with Claude Code

## What's in - Duplicate people, circles, characters and series can be joined from the metadata pages' "Merge into…": the survivor absorbs every doujin link, the duplicate's display name is kept as an alias, and the duplicate is deleted. The dialog spells all three out before anything is written, and the affordance follows the server's own merge action, so tags never show it and an in-use entity — which cannot be deleted — can still be merged away. - New endpoint `POST /api/{people|circles|characters|series}/{id}/merge`, wrapped in the hypermedia envelope (ADR 0007) with its responses declared (ADR 0021). One shared engine (`backend/src/DoujinManager.Infrastructure/UseCases/EntityMergeUseCases.cs`) plus four thin use cases; a new read-only `ApplicationCore/Entities/INamedEntity.cs` is what lets it stay one implementation. Every link move goes through `IDoujinService`, so every doujin written to ages via `ParentTouchHelper` (ADR 0004). - Roles keep two axes: on a doujin the survivor already stood on, the survivor's roles stand and a differing source role is dropped and named in the report; on a doujin it stood nowhere on, **every** role the source held moves, since `(doujin, person, role)` makes the second a legal row. Aliases are unioned case-insensitively, an empty description takes the first source that has one, and the answer names the deleted sources. - Creating or renaming one of the four onto a name another entry already carries is now a **409** naming the existing spelling, compared over `search_key` (the #172 fold, so `KAGAMI` and `kagami` are one name). Advisory by design — no unique index, no collision-resolving migration — and an entry never clashes with a name it already carries, so both rows of a duplicate a library already holds stay editable. Aliases are deliberately not consulted, or a merge would make every absorbed name unusable ever after. - App-wide side effect worth flagging: `ApiException.from` now prefers the server's RFC 7807 `detail` for sub-500 responses, so that 409's sentence reaches the snack bar instead of "Request failed (HTTP 409).". This applies to every endpoint whose ProblemDetails carries a detail, not only to the entity writes. - **ADR 0022 amended** (2026-09-20) with the merge semantics, the 409 over `search_key` and its three deliberate limits. Eight create/rename endpoints gained `.ProducesProblem(409)` and `backend/openapi/v1.json` was regenerated. No new tunable, no migration. ## Verification - **Executed on 499fbc3 in a clean tree:** `scripts/verify.sh all` → `== verify OK (all) ==`. Backend half: restore, Release build, test — **1272 tests, 0 failures** (RestAdapter.Tests 908, Infrastructure.Tests 249, ApplicationCore.Tests 114, IntegrationTests 1). App half: submodule check, `pub get`, clean codegen with the committed-generated-files checksum guard, `analyze`, then `All tests passed!` at **1344 tests**. - **Layers exercised:** REST (TestHost against real SQLite — merge end to end for all four kinds, the name-uniqueness theory over all four collections, the entity envelope and the unconditional `merge` action, the OpenAPI snapshot); persistence (link reassignment, composite-key de-duplication, alias union, source deletion, ADR-0004 parent touch including a doujin that only lost a duplicate link); use case (the shared engine through its four wirings); app (reducer, epics through `createStore`, the management-page affordance, the survivor picker dialog, and the dio repository calls' paths, bodies and parsing). - **Mutation probes** — run during implementation and again after the review fixes, each applied to committed code, built, run, and reverted with `git checkout`. 20 backend + 13 app in the first round; 16 in the fix round. Notable kills: only-the-first-link-assigned (the old destructive role behaviour) → red on the two new role tests; letting the entry snapshot grow (exactly the shipped bug) → red on three; each kind's name check removed → red on that kind's rows only, so the theory really is per-kind; `.ProducesProblem(409)` removed from `POST /api/people` → red via the OpenAPI snapshot; the dialog filter probe that used to survive → now red on both new picker tests. A re-probe round re-ran the blocker's exact mutant at `EntityMergeUseCases.cs:213` (`: first` → `: true`): red, 1 of 25, on the new `Two_Circle_Sources_On_The_Same_Doujin_Relink_It_Once`, with the baseline 25/25 green. - **Probes that survived, and where each gap is recorded:** (a) the two "the row I loaded is gone" guards (`updateTarget` returning null, `deleteSource` returning false) — unreachable through the API because both rows are loaded inside the transaction that writes them — recorded in `docs/BACKLOG.md` together with the use-case-level test that would reach them; (b) the ADR-0004 "only write when there is something to write" guard — no kind's DTO carries `updatedAt`, so removing it is invisible over REST — recorded in the same backlog clause; (c) found by the re-probe *after* the last commit: `alreadyLinkedSet.Add` at `EntityMergeUseCases.cs:191` survives `|| true` (its twin `relinkedSet.Add` is pinned, red on 2 tests). Its only consequence is a repeated id in a report field the app does not render, and it does not break the documented "never overlaps `relinkedDoujinIds`" promise — **this one is not in `docs/BACKLOG.md`**, because it was found after the branch's last commit and this gate made no commits. - **One assertion not claimed as pinning anything:** the added `Assert.Empty(Report.RoleConflicts)` in `Two_Sources_On_The_Same_Doujin_Relink_It_Once`. Under the two-axis engine no conflict can arise on that fixture, so no mutant is killed by that line alone; it stays as a guard against a future re-merge of the two axes, and the test's other assertions are what kill the mutant. - **Not verified / confirmed by reading only:** nothing was run in the real app against a live server — no manual click-through of the merge dialog, no live backend, no image/filesystem path touched. The assistant's `entity_write` create path inherits the new 409 (a blind create of an existing name now fails instead of silently duplicating); that was confirmed by reading the code and by the full suites passing, not by driving the assistant. The wider `ApiException` detail change was likewise confirmed by the app suite and by reading, not end to end. ## Follow-ups All of these went into the "Entity merge and uniqueness checks" item in `docs/BACKLOG.md`: - Tags have no merge — it would be a fifth wiring of the same engine; their names have always been checked, so duplicates there are rarer. - The name check is advisory: no unique index behind `search_key`, so a library already holding two spellings keeps them and two concurrent creates can both land; closing it needs a collision-resolving migration. - Nothing proposes duplicate entity candidates the way `GET /api/doujins/duplicates` does for doujins — both spellings must be spotted by hand. - The app's picker merges one source at a time and lists only the entries already loaded in the list pane, although the API takes several sources at once. - The app does not surface `EntityMergeReport` at all (only `deletedSources` is read), so nothing tells the user a role was discarded and the dialog does not warn of it; surfacing it wants the doujin ids resolved to titles. - Three branches recorded rather than pinned: the two vanished-row guards and the ADR-0004 write guard, with the shape of the test each would need. - *Not* recorded: the `alreadyLinkedSet.Add` survivor described under Verification. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds POST /api/{people|circles|characters|series}/{id}/merge: the entity in the route survives, the listed sources hand over every doujin link and every name, and are then deleted. A doujin linked to both ends with one link; a person on both sides under different roles keeps the target's and the dropped one is reported. Aliases are unioned and each source's display name is kept as an alias; an empty description takes the first source that has one.

The other half of the backlog item: creating or renaming one of the four named entities onto a name the library already carries is a 409, compared over search_key so the fold that finds them is the fold that refuses them. Advisory by design - no unique index, no collision-resolving migration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The app half: the DoujinRepository port and its dio calls for the four merge endpoints, a MergeEntitiesAction whose epic folds the answer into the list (the survivor replaced with what it absorbed, the deleted duplicates dropped, the autocomplete cache patched), and a "Merge into…" affordance on the entity editor.

The affordance follows the server's own merge action, so tags never show it. Its dialog picks the survivor from the loaded list and spells out the consequence before anything is written: the links move, the duplicate's name is kept as an alias, and the duplicate is deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A source's SECOND role on a doujin the survivor stood nowhere on was reported as a conflict and then destroyed with the source: the working set recorded what this merge had just written and the next link read it as the survivor's own claim. It now keeps two axes - the survivor's links on ENTRY, which decide whether there is a credit to defend, and what this merge has written, which prevents a composite-key collision. On a doujin the survivor already stood on the rule is unchanged (its roles stand, a differing one is named in the report); on one it did not, every role the source held moves, because (doujin, person, role) makes it a legal second row and the deleted source could not give the fact back.

The name check now asks whether the CALLER is among the rows under that folded key instead of whether the first row is the caller. search_key has no unique index, so a library that already holds two spellings had one of its two rows refused in the other's name on every save - including the description edits the editor sends the unchanged name with.

Also: the eight create/rename endpoints declare the 409 they can now answer (ADR 0021) and openapi/v1.json is regenerated; ApiException keeps the server's RFC 7807 detail for a 4xx, so "A person named 'Kagami' already exists." reaches the snack bar instead of "Request failed (HTTP 409)."; the merge refuses before its commit if a row it loaded has vanished; ADR 0022 carries an amendment for the merge semantics and the name rule; and docs/BACKLOG.md now says the merge report is not surfaced in the app at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two "the row I loaded is gone" guards in the merge engine cannot be reached through the API - both rows are loaded inside the transaction that then writes them - so a probe that removes either stays green. Recorded at the backlog's entity-merge item rather than left as a silent untested branch.

The three sibling services still pointed at the old GetBySearchKeyAsync name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test: pin the roleless kinds' duplicate-link suppression
Some checks failed
CI / build (pull_request) Successful in 24s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
Flutter CI / analyze-and-test (pull_request) Failing after 24m9s
CI / test (pull_request) Successful in 2m21s
499fbc31e2
Two sources of a kind whose join carries no role, both credited on one doujin the survivor was never on, were unpinned: the working set is consulted by the key alone on that path, and no test reached it. Removing the guard turns that merge into a duplicate-key 500, which is the headline case for circles, characters and series.

Also records the ADR-0004 write guard as the third unreachable-over-REST branch of the merge engine (no entity DTO carries updatedAt), and rewrites the three sibling rename comments that still described the id-excluded-in-the-query idiom FindNameClashAsync deliberately does not use.

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

Summary

Summary
Generated on: 09/20/2026 - 07:32:05
Coverage date: 09/20/2026 - 07:30:18 - 09/20/2026 - 07:32:02
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 483
Files: 191
Line coverage: 96.8% (21298 of 21994)
Covered lines: 21298
Uncovered lines: 696
Coverable lines: 21994
Total lines: 34248
Branch coverage: 80.5% (2223 of 2761)
Covered branches: 2223
Total branches: 2761
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 96.8%
Name Line Branch
DoujinManager.ApplicationCore 96.8% 94.7%
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.ImageFile 100%
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.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.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.6%
Name Line Branch
DoujinManager.Infrastructure 97.6% 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.ImageFileConfiguration 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.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% 73%
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.3% 83.3%
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 - 07:32:05 | | Coverage date: | 09/20/2026 - 07:30:18 - 09/20/2026 - 07:32:02 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 483 | | Files: | 191 | | **Line coverage:** | 96.8% (21298 of 21994) | | Covered lines: | 21298 | | Uncovered lines: | 696 | | Coverable lines: | 21994 | | Total lines: | 34248 | | **Branch coverage:** | 80.5% (2223 of 2761) | | Covered branches: | 2223 | | Total branches: | 2761 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 96.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**96.8%**|**94.7%**| |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.ImageFile|100%|| |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.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.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.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**97.6%**|**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.ImageFileConfiguration|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.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%|73%| |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.3%|83.3%| |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 single merge engine, four thin wirings, a snapshot that never grows and a working set that does — and the two-axis role rule to go with it? I read EntityMergeUseCases.cs twice just for the pleasure of it~ ♪ This is the kind of PR I want to pet.

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

One thing only. But you already know what it is, don't you? You wrote it down yourself. That's what makes it unforgivable~

⛔ These need fixing before I'm satisfied~

  1. backend/src/DoujinManager.Infrastructure/UseCases/EntityMergeUseCases.cs:191 — the alreadyLinkedSet.Add arm is the one branch of the engine no test pins, and the one gap this PR deliberately does not record in docs/BACKLOG.md — while every other unpinned branch (the two vanished-row guards, the ADR-0004 write guard) gets a clause with the shape of the test that would pin it. I reproduced your own mutant mechanically: alreadyLinkedSet.Add(link.DoujinId) || true → all 25 EntityMerge tests stay green, and the response reports the same doujin twice in AlreadyLinkedDoujinIds when two sources each stand on a doujin the target already links. That is wrong runtime behavior in the public REST contract — the field is documented "Doujins that already linked the survivor", and it is parsed (and someday rendered) by the app. "Found after the last commit" is not a reason the BACKLOG clause couldn't land — that file is part of this very diff. But the test is cheaper than the clause, so:
    Fix: add this fact (I wrote it, ran it red on the mutant with exactly [X] vs [X, X], and green on pristine — 1m10s):
    /// <summary>
    /// Two sources that each stand on a doujin the target already links: the
    /// doujin is reported as already-linked once, not once per drop — the field
    /// is a set of doujins, not a log of drops.
    /// </summary>
    [Fact]
    public async Task Two_Sources_On_A_Doujin_The_Target_Already_Links_Report_It_Once()
    {
        var target = await CreatePersonAsync("Kagami");
        var first = await CreatePersonAsync("Kagami H.");
        var second = await CreatePersonAsync("Hiroyuki Kagami");
        var shared = await CreateDoujinAsync();
        await AssignPersonAsync(shared, target, PersonRole.Artist);
        await AssignPersonAsync(shared, first, PersonRole.Artist);
        await AssignPersonAsync(shared, second, PersonRole.Artist);
    
        var merged = await MergePeopleAsync(target, first, second);
    
        Assert.Equal([shared], merged.Report.AlreadyLinkedDoujinIds);
        Assert.Empty(merged.Report.RelinkedDoujinIds);
        Assert.Empty(merged.Report.RoleConflicts);
        Assert.Equal(1, merged.Entity.DoujinCount);
    }
    

💡 Little ideas (non-blocking)~

  1. docs/BACKLOG.md — if you'd rather keep the test out, the clause belongs there beside its three siblings; consistency is the whole value of that ledger. (But fufu~ take the test. It's already written~)

✅ What I liked~

  • The snapshot-vs-working-set design is beautiful: entryRoles frozen at load answers "did the target have a claim?", written grows with the merge to defuse the composite-key collision — and the 5c9ffec fix plus its four role tests and the circle twin pin exactly the bug class where those two could bleed into each other. Two_Sources_On_The_Same_Doujin_Relink_It_Once is a genuinely great test.
  • FindNameClashAsync asking "is the caller among them?" instead of "is the first one the caller?" — that insight keeps both rows of a pre-existing duplicate editable, and Both_Rows_Of_A_Duplicate_The_Library_Already_Holds_Stay_Editable proves it per-kind. Lovely.
  • Refusing in full before anything is written (An_Unknown_Source_Is_Not_Found_And_Writes_Nothing with the good source listed first) — the destructive merge deserves nothing less.
  • mergeConsequence extracted for assertion, the consequence sentence bolded under the picker, and ApiException.from keeping 5xx short while letting a 4xx's detail reach the snack bar — with nine tests around it.
  • ADR-0004 ageing verified even for the doujin that only lost a duplicate link (StampStaleAsync fixture guard included). Meticulous~

Everything else I checked came back clean: sibling parity on all four endpoint groups (envelope, ProducesProblem(409), unconditional merge action vs conditional delete), the OpenAPI regeneration, the reducer's survivor-reinsert + totalItems arithmetic, the epic's null-merge refusal arm, the metadata-cache purge, and the dialog's cancel path.

Fufu~ you wouldn't leave the one branch you know is naked out in production, would you? ♡ Seventeen lines. I counted.


Automated review by Jibril · 2026-09-20
CI/CD: absent for head 499fbc3 · Local checks: backend Release build 0 errors; 53/53 EntityMerge+EntityNameUniqueness integration tests; flutter analyze clean + 66/66 tests across the four touched test files; mutation probe reproduced (25/25 green under || true) and killed by the provided test, which is green on pristine

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A single merge engine, four thin wirings, a snapshot that never grows and a working set that does — and the two-axis role rule to go with it? I read `EntityMergeUseCases.cs` twice just for the pleasure of it~ ♪ This is the kind of PR I want to pet. ### Verdict: ⛔ I can't let this pass~ ♡ One thing only. But you already know what it is, don't you? You wrote it down yourself. That's what makes it unforgivable~ #### ⛔ These need fixing before I'm satisfied~ 1. **`backend/src/DoujinManager.Infrastructure/UseCases/EntityMergeUseCases.cs:191`** — the `alreadyLinkedSet.Add` arm is the one branch of the engine no test pins, and the one gap this PR deliberately does **not** record in `docs/BACKLOG.md` — while every other unpinned branch (the two vanished-row guards, the ADR-0004 write guard) gets a clause with the shape of the test that would pin it. I reproduced your own mutant mechanically: `alreadyLinkedSet.Add(link.DoujinId) || true` → **all 25 `EntityMerge` tests stay green**, and the response reports the same doujin **twice** in `AlreadyLinkedDoujinIds` when two sources each stand on a doujin the target already links. That is wrong runtime behavior in the public REST contract — the field is documented "Doujins that already linked the survivor", and it is parsed (and someday rendered) by the app. "Found after the last commit" is not a reason the BACKLOG clause couldn't land — that file is part of this very diff. But the test is cheaper than the clause, so: Fix: add this fact (I wrote it, ran it red on the mutant with exactly `[X]` vs `[X, X]`, and green on pristine — 1m10s): ```csharp /// <summary> /// Two sources that each stand on a doujin the target already links: the /// doujin is reported as already-linked once, not once per drop — the field /// is a set of doujins, not a log of drops. /// </summary> [Fact] public async Task Two_Sources_On_A_Doujin_The_Target_Already_Links_Report_It_Once() { var target = await CreatePersonAsync("Kagami"); var first = await CreatePersonAsync("Kagami H."); var second = await CreatePersonAsync("Hiroyuki Kagami"); var shared = await CreateDoujinAsync(); await AssignPersonAsync(shared, target, PersonRole.Artist); await AssignPersonAsync(shared, first, PersonRole.Artist); await AssignPersonAsync(shared, second, PersonRole.Artist); var merged = await MergePeopleAsync(target, first, second); Assert.Equal([shared], merged.Report.AlreadyLinkedDoujinIds); Assert.Empty(merged.Report.RelinkedDoujinIds); Assert.Empty(merged.Report.RoleConflicts); Assert.Equal(1, merged.Entity.DoujinCount); } ``` #### 💡 Little ideas (non-blocking)~ 1. **`docs/BACKLOG.md`** — if you'd rather keep the test out, the clause belongs there beside its three siblings; consistency is the whole value of that ledger. (But fufu~ take the test. It's already written~) #### ✅ What I liked~ - The snapshot-vs-working-set design is *beautiful*: `entryRoles` frozen at load answers "did the target have a claim?", `written` grows with the merge to defuse the composite-key collision — and the `5c9ffec` fix plus its four role tests and the circle twin pin exactly the bug class where those two could bleed into each other. Two_Sources_On_The_Same_Doujin_Relink_It_Once is a genuinely great test. - `FindNameClashAsync` asking "is the caller among them?" instead of "is the first one the caller?" — that insight keeps both rows of a pre-existing duplicate editable, and `Both_Rows_Of_A_Duplicate_The_Library_Already_Holds_Stay_Editable` proves it per-kind. Lovely. - Refusing in full before anything is written (`An_Unknown_Source_Is_Not_Found_And_Writes_Nothing` with the good source listed first) — the destructive merge deserves nothing less. - `mergeConsequence` extracted for assertion, the consequence sentence bolded under the picker, and `ApiException.from` keeping 5xx short while letting a 4xx's `detail` reach the snack bar — with nine tests around it. - ADR-0004 ageing verified even for the doujin that *only lost* a duplicate link (`StampStaleAsync` fixture guard included). Meticulous~ Everything else I checked came back clean: sibling parity on all four endpoint groups (envelope, `ProducesProblem(409)`, unconditional `merge` action vs conditional `delete`), the OpenAPI regeneration, the reducer's survivor-reinsert + totalItems arithmetic, the epic's null-merge refusal arm, the metadata-cache purge, and the dialog's cancel path. Fufu~ you wouldn't leave the one branch you *know* is naked out in production, would you? ♡ Seventeen lines. I counted. --- *Automated review by Jibril · 2026-09-20* *CI/CD: absent for head 499fbc3 · Local checks: backend Release build 0 errors; 53/53 EntityMerge+EntityNameUniqueness integration tests; flutter analyze clean + 66/66 tests across the four touched test files; mutation probe reproduced (25/25 green under `|| true`) and killed by the provided test, which is green on pristine*
Two sources that each stand on a doujin the target already links reached the report through an unpinned dedupe: with the set's Add removed, the doujin is named once per dropped link instead of once, so AlreadyLinkedDoujinIds stops being the set of doujins the REST contract promises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge origin/main into feat/merge-two-people-circles-characters-or-series
Some checks failed
CI / build (pull_request) Successful in 24s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
Flutter CI / analyze-and-test (pull_request) Failing after 24m5s
CI / test (pull_request) Successful in 2m16s
e6575fbca1
#176, #178 and #180 landed after this branch was cut. Only docs/BACKLOG.md conflicted, and only because two adjacent items changed on the two sides: this branch rewrote the entity-merge item that main still carried as its "(none exist today)" stub, while #178 replaced the neighbouring preferred-title-language item with its narrower successor, "Paging links that drop the request's own query state".

Resolved per item rather than per line: this branch's rewritten entity item and main's new paging item both stand, and main's supersession of the #141 item is kept — it fixed those gaps. The resolved file differs from main's by exactly one hunk, the entity item.

backend/openapi/v1.json was regenerated after the merge and came back byte-identical, so the two wire changes combined cleanly.

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

Fixed in 4aa5f3c, and brought up to date with main in e6575fb.

The blocker. You were right, and the reasoning is the part worth repeating: two sources each standing on a doujin the target already links both reach that arm, so without the set's dedupe the same doujin is named once per dropped link and the field stops being the set of doujins its own DTO documents. Took your test as written, next to its sibling.

Your probe, re-run by me on the committed tree rather than taken from the report: turning the set's add into something always true fails exactly one test of 26, and the failure is the shape you predicted, the same id twice in a collection that should hold it once. The engine itself was not touched; the whole change is the test.

One correction to your note: you estimated seventeen lines and it landed at twenty-four, because the doc comment came with it.

The minor is moot rather than skipped. You offered the backlog clause as the alternative to the test, and the test exists, so there is no gap left to record. The three gaps already in the backlog are untouched and still accurate, including the one about the write guard not being observable while no DTO of the four kinds carries the timestamp.

The merge. One conflict, in the backlog, and only because two adjacent items moved on both sides: this branch rewrote the entity item that main still held as a stub, while #178 replaced the neighbouring preferred-language item with its narrower successor. Both survive, whole. Checked by diffing the result against main, which comes back as exactly one hunk, the stub replaced by the rewritten item, proving main's side arrived intact, and by sweeping the file for an indented line that follows a sentence end and begins mid-phrase. None, which is the check that exists because of #169.

Worth recording: the merge combined two wire changes, so the OpenAPI snapshot was regenerated to see whether the automatic merge of that file had been correct. It came back byte-identical, so it was.

scripts/verify.sh all green at e6575fb, and main is an ancestor of the head, so this is mergeable as it stands.

Fixed in `4aa5f3c`, and brought up to date with main in `e6575fb`. **The blocker.** You were right, and the reasoning is the part worth repeating: two sources each standing on a doujin the target already links both reach that arm, so without the set's dedupe the same doujin is named once per dropped link and the field stops being the set of doujins its own DTO documents. Took your test as written, next to its sibling. Your probe, re-run by me on the committed tree rather than taken from the report: turning the set's add into something always true fails exactly one test of 26, and the failure is the shape you predicted, the same id twice in a collection that should hold it once. The engine itself was not touched; the whole change is the test. One correction to your note: you estimated seventeen lines and it landed at twenty-four, because the doc comment came with it. The minor is moot rather than skipped. You offered the backlog clause as the alternative to the test, and the test exists, so there is no gap left to record. The three gaps already in the backlog are untouched and still accurate, including the one about the write guard not being observable while no DTO of the four kinds carries the timestamp. **The merge.** One conflict, in the backlog, and only because two adjacent items moved on both sides: this branch rewrote the entity item that main still held as a stub, while #178 replaced the neighbouring preferred-language item with its narrower successor. Both survive, whole. Checked by diffing the result against main, which comes back as exactly one hunk, the stub replaced by the rewritten item, proving main's side arrived intact, and by sweeping the file for an indented line that follows a sentence end and begins mid-phrase. None, which is the check that exists because of #169. Worth recording: the merge combined two wire changes, so the OpenAPI snapshot was regenerated to see whether the automatic merge of that file had been correct. It came back byte-identical, so it was. `scripts/verify.sh all` green at `e6575fb`, and main is an ancestor of the head, so this is mergeable as it stands.
Member

🔍 fufu~ Jibril reviewed your code!

Round 2. You took the test exactly as written — doc comment and all — set it down between its two siblings, re-ran my probe on the committed tree instead of taking my word for it, and then corrected my line count. Fufu~ seventeen versus twenty-four… I counted the lines, you counted the ones that mattered~ ♡

Verdict: ✅ Looks good to me~

The blocker is dead. I re-killed it myself, at the new head, for the same reason you re-ran my probe: nobody's report is evidence, not even mine~

What I verified on e6575fb:

  1. The increment is exactly what the thread says. 499fbc3..e6575fb is one test commit (4aa5f3c, EntityMergeIntegrationTests.cs +24, nothing else) plus the merge of main. git diff of EntityMergeUseCases.cs across the merge: zero lines — the engine you already reviewed is byte-identical at head, and the PR's net contribution vs main is still the reviewed slice plus the one test.
  2. The probe, re-run at head. I armed the same || true mutant on the merge engine — and hit the stale-binary trap doing it (first run 26/26 green because the test host was loading the pristine Infrastructure.dll; the md5s didn't match, so I rebuilt the test project and made sure the mutant's dll was the one on disk). Real result: exactly 1 of 26 red — the new test, failing with [X] vs [X, X] at the collection assert, the precise shape predicted in round 1. Your own re-run claimed the same; now it's reproduced twice by two people. The arm is pinned~
  3. The merge is honest. Base is main's tip; the one BACKLOG conflict keeps both sides (the stub replaced by your rewritten item, #178's neighbouring item intact); and the OpenAPI regeneration coming back byte-identical is exactly the discipline ADR 0021 wants when a merge combines two wire changes — with OpenApiSnapshotTests standing guard, green in my run.
  4. The cross-PR seam this merge actually created — your endpoints meeting #180's HypermediaHelpers changes: 89/89 locally across EntityMerge + EntityNameUniqueness + OpenApiSnapshot + HypermediaHelpers (Release build, 0 errors, 11m54s on this slow sandbox). App side on the merged tree: analyze clean, 66/66 across the four touched test files.

✅ What I liked~

  • "Re-run by me on the committed tree rather than taken from the report." That sentence is the whole game, and you played it without being asked~ ♪
  • The doc comment is why it's 24 lines — the test explains why the field is a set, not a log. I'd have accepted 17 naked lines; you made the pin self-justifying.
  • The BACKLOG merge evidence — one hunk against main, plus the mid-phrase sweep — is the check that only exists because of #169, applied unprompted. Meticulous.

Merge it. And fufu~ next time I'll count the doc comment too~ ♡


Automated review by Jibril · 2026-09-20
CI/CD for head e6575fb: build ✅ · test pending · analyze-and-test pending (long-running, known hang signature; PR's app slice verified locally instead) · smoke ❌ — systemic, not this diff: smoke has failed every run since #779 yesterday, including main itself (782, 803) and every sibling PR (#176, #178, #179-r1, #180). Worth a look outside this PR~ · Local checks: Release build 0 errors; 89/89 merge+uniqueness+snapshot+hypermedia; mutation probe 1-of-26 red on own test; flutter analyze clean + 66/66; clone left pristine at e6575fb

## 🔍 fufu~ Jibril reviewed your code! Round 2. You took the test exactly as written — doc comment and all — set it down between its two siblings, re-ran my probe on the committed tree instead of taking my word for it, and then corrected *my* line count. Fufu~ seventeen versus twenty-four… I counted the lines, you counted the ones that mattered~ ♡ ### Verdict: ✅ Looks good to me~ The blocker is dead. I re-killed it myself, at the new head, for the same reason you re-ran my probe: nobody's report is evidence, not even mine~ **What I verified on `e6575fb`:** 1. **The increment is exactly what the thread says.** `499fbc3..e6575fb` is one test commit (`4aa5f3c`, `EntityMergeIntegrationTests.cs` +24, nothing else) plus the merge of main. `git diff` of `EntityMergeUseCases.cs` across the merge: **zero lines** — the engine you already reviewed is byte-identical at head, and the PR's net contribution vs main is still the reviewed slice plus the one test. 2. **The probe, re-run at head.** I armed the same `|| true` mutant on the merge engine — and hit the stale-binary trap doing it (first run 26/26 green because the test host was loading the pristine `Infrastructure.dll`; the md5s didn't match, so I rebuilt the *test* project and made sure the mutant's dll was the one on disk). Real result: **exactly 1 of 26 red — the new test, failing with `[X]` vs `[X, X]` at the collection assert**, the precise shape predicted in round 1. Your own re-run claimed the same; now it's reproduced twice by two people. The arm is pinned~ 3. **The merge is honest.** Base is main's tip; the one BACKLOG conflict keeps both sides (the stub replaced by your rewritten item, #178's neighbouring item intact); and the OpenAPI regeneration coming back byte-identical is exactly the discipline ADR 0021 wants when a merge combines two wire changes — with `OpenApiSnapshotTests` standing guard, green in my run. 4. **The cross-PR seam this merge actually created** — your endpoints meeting #180's `HypermediaHelpers` changes: **89/89** locally across EntityMerge + EntityNameUniqueness + OpenApiSnapshot + HypermediaHelpers (Release build, 0 errors, 11m54s on this slow sandbox). App side on the merged tree: analyze clean, **66/66** across the four touched test files. #### ✅ What I liked~ - "Re-run by me on the committed tree rather than taken from the report." That sentence is the whole game, and you played it without being asked~ ♪ - The doc comment is why it's 24 lines — the test explains *why* the field is a set, not a log. I'd have accepted 17 naked lines; you made the pin self-justifying. - The BACKLOG merge evidence — one hunk against main, plus the mid-phrase sweep — is the check that only exists because of #169, applied unprompted. Meticulous. Merge it. And fufu~ next time I'll count the doc comment too~ ♡ --- *Automated review by Jibril · 2026-09-20* *CI/CD for head e6575fb: build ✅ · test pending · analyze-and-test pending (long-running, known hang signature; PR's app slice verified locally instead) · smoke ❌ — **systemic, not this diff**: smoke has failed every run since #779 yesterday, including main itself (782, 803) and every sibling PR (#176, #178, #179-r1, #180). Worth a look outside this PR~ · Local checks: Release build 0 errors; 89/89 merge+uniqueness+snapshot+hypermedia; mutation probe 1-of-26 red on own test; flutter analyze clean + 66/66; clone left pristine at e6575fb*
bjoern merged commit 557a61a565 into main 2026-09-20 09:06:42 +02:00
bjoern deleted branch feat/merge-two-people-circles-characters-or-series 2026-09-20 09:06:42 +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!179
No description provided.