feat: merge metadata across duplicate doujins #173

Merged
bjoern merged 6 commits from feat/merge-metadata-across-duplicate-doujins into main 2026-09-20 00:41:46 +02:00
Member

What's in

  • The possible-duplicates page gets Merge metadata on every group card: the user picks which member survives, reads what the merge copies and that nothing is deleted, confirms, and the group refreshes with a one-line report under that card — Merged: copied 1 title, 2 tags. Dropped: kept Kaiji as Writer, Translator (dropped Editor). The per-member delete is untouched and is still how the leftovers go.
  • New endpoint POST /api/doujins/{id}/merge: takes the source ids, answers the updated target plus a report. 400 for an empty list, a repeated id or a source that is the target; 404 for any unknown id. Responses declared per ADR 0021, wrapped in the ADR 0007 envelope, and a new merge action is advertised on every doujin resource.
  • The rules: tags, circles, characters and series unioned; people unioned per person, with the target's roles standing and the discarded one named; titles unioned and de-duplicated by (language, kind, value) with the language compared case-insensitively; description, rating and cover the target's unless empty (first non-empty description, highest source rating, first usable source cover). Variants and pages never move, and nothing is deleted.
  • Two decisions the brief left open, both documented in the use case and reported to the user: reading direction is never copied (the column is non-nullable, so a target's direction can never be "empty"), and a source cover is taken only when that image is already one of the target's own page images — the ownership rule PUT /api/doujins/{id} enforces — with anything else listed in skippedCoverImageIds rather than becoming a foreign cover reference.
  • The target ages through the ordinary write paths (ADR 0004), and a merge that copies nothing writes nothing; the sources keep their own metadata and their UpdatedAt.
  • Housekeeping: roleLabel moved into the new app/lib/core/person_display.dart, replacing three copies of the same switch (detail page, associations tab, duplicates page) — two of them disagreed on PersonRole.scanners and "Scanner" won, so the editor's people dropdown changes wording. backend/openapi/v1.json regenerated (one endpoint plus the report shapes). No EF migration, no new tunable, no ADR amendment.

Verification

  • scripts/verify.sh all, run fresh from the worktree, unpiped, with the exit code captured: EXIT=0, == verify OK (all) ==. Backend 1055 tests green across four assemblies (RestAdapter 769, Infrastructure 229, ApplicationCore 56, IntegrationTests 1), 0 failed and 0 skipped; app side flutter analyze clean (infos fatal) and 1257 tests green.
  • Layers exercised by automated tests: UI — duplicates_page_test covers the dialog listing every member, the non-default survivor becoming the target, the report rendering under its own card, the in-flight spinner disabling every other write on the page, the failure message landing on the right group, and mergeSummary as a pure function (bounded pumps throughout). REST — DoujinMergeIntegrationTests drives the endpoint over TestHost for the envelope, the three 400s and both 404s. Use case — MergeDoujinsUseCaseTests pins the same guards at contract level, including that a refused request performs no write. Persistence — the integration tests read the target and the sources back out of the database rather than trusting the report, and ChildWriteTouchesParentIntegrationTests carries ChildWrite.MergeMetadata alongside every other ADR 0004 child write. Wire shape and routing are pinned in models_test and doujin_api_repository_test, and the committed OpenAPI snapshot test passes against the regenerated v1.json.
  • Mutation probes were run while the branch was written and then re-run independently afterwards — not in this final gate run, which executed scripts/verify.sh all and nothing else. First round: 14 backend and 12 app probes, each applied alone and reverted, all red. One of them found a real defect rather than confirming a test: the merge epic had copied the delete epic's request-sequence stamp, and removing the stamp changed no test because asyncExpand serialises the handlers and the merge clears its flag in the action it yields; the stamp was deleted instead of being papered over (84604f5). Second round, after review: 10 more probes, all red — multi-source accumulation for tags and for titles (where a double count is a duplicate-key 500, not merely a wrong report), the "copies nothing writes nothing" guard, [.. roles] against [roles.Min], the person-role bookkeeping line, the cover loop's break, the title key's ToLowerInvariant, the page's keptRoles join, the reducer's error clear on a first-page load, and the epic's on Object catch. The independent re-probe additionally drove circle, character and series accumulation red one at a time, plus both group-scoping ternaries on the page, the reducer's load-more guard and the epic's id gate.
  • Not verified: nothing was run in the real app against a live server — no manual end-to-end pass, no real images and no real duplicate group; every claim above rests on the automated suites. The merge is not exposed to the AI assistant (app/lib/agent/ is untouched), so no route allowlist changed. The reasoning behind the cover-ownership rule — that a foreign cover would be deleted out from under the target by ImageCleanupHelper, which decides orphanhood by surviving page references — was confirmed by reading ImageCleanupHelper and UpdateDoujinUseCase, not by executing that path.
  • Base note: this branch sits on 9de680b and main has since gained one docs-only commit (76081fb, #169) that dropped a stray line from the #141 backlog entry. git merge-tree against the current origin/main merges clean, and this branch does not touch that line — a two-dot diff will show it as an addition, a three-dot diff will not.

Follow-ups

Two items went into docs/BACKLOG.md under Product, at the anchor the completed "merge metadata across a group's members" item left behind:

  • Duplicates page: the merge dialog states the rules, not what this group would actually copy — closing it needs the members' details fetched before the dialog, or a ?dryRun=true arm on the endpoint.
  • Merge: a coverless survivor can only take a source's cover when that image is already one of its own pages, so for two doujins that merely happen to be the same work the cover is never copied and the user only sees "a cover that is not one of its own pages".

This branch adds no EF migration, so there is no model snapshot to regenerate against a sibling in this batch.

🤖 Generated with Claude Code

## What's in - The possible-duplicates page gets **Merge metadata** on every group card: the user picks which member survives, reads what the merge copies and that **nothing is deleted**, confirms, and the group refreshes with a one-line report under that card — `Merged: copied 1 title, 2 tags. Dropped: kept Kaiji as Writer, Translator (dropped Editor).` The per-member delete is untouched and is still how the leftovers go. - New endpoint `POST /api/doujins/{id}/merge`: takes the source ids, answers the updated target plus a report. 400 for an empty list, a repeated id or a source that is the target; 404 for any unknown id. Responses declared per ADR 0021, wrapped in the ADR 0007 envelope, and a new `merge` action is advertised on every doujin resource. - The rules: tags, circles, characters and series unioned; people unioned per person, with the target's roles standing and the discarded one named; titles unioned and de-duplicated by (language, kind, value) with the language compared case-insensitively; description, rating and cover the target's unless empty (first non-empty description, highest source rating, first usable source cover). Variants and pages never move, and nothing is deleted. - Two decisions the brief left open, both documented in the use case and reported to the user: reading direction is never copied (the column is non-nullable, so a target's direction can never be "empty"), and a source cover is taken only when that image is already one of the target's **own** page images — the ownership rule `PUT /api/doujins/{id}` enforces — with anything else listed in `skippedCoverImageIds` rather than becoming a foreign cover reference. - The target ages through the ordinary write paths (ADR 0004), and a merge that copies nothing writes nothing; the sources keep their own metadata and their `UpdatedAt`. - Housekeeping: `roleLabel` moved into the new `app/lib/core/person_display.dart`, replacing three copies of the same switch (detail page, associations tab, duplicates page) — two of them disagreed on `PersonRole.scanners` and "Scanner" won, so the editor's people dropdown changes wording. `backend/openapi/v1.json` regenerated (one endpoint plus the report shapes). **No EF migration, no new tunable, no ADR amendment.** ## Verification - `scripts/verify.sh all`, run fresh from the worktree, unpiped, with the exit code captured: **`EXIT=0`, `== verify OK (all) ==`**. Backend 1055 tests green across four assemblies (RestAdapter 769, Infrastructure 229, ApplicationCore 56, IntegrationTests 1), 0 failed and 0 skipped; app side `flutter analyze` clean (infos fatal) and 1257 tests green. - Layers exercised by automated tests: **UI** — `duplicates_page_test` covers the dialog listing every member, the non-default survivor becoming the target, the report rendering under its own card, the in-flight spinner disabling every other write on the page, the failure message landing on the right group, and `mergeSummary` as a pure function (bounded pumps throughout). **REST** — `DoujinMergeIntegrationTests` drives the endpoint over TestHost for the envelope, the three 400s and both 404s. **Use case** — `MergeDoujinsUseCaseTests` pins the same guards at contract level, including that a refused request performs no write. **Persistence** — the integration tests read the target and the sources back out of the database rather than trusting the report, and `ChildWriteTouchesParentIntegrationTests` carries `ChildWrite.MergeMetadata` alongside every other ADR 0004 child write. Wire shape and routing are pinned in `models_test` and `doujin_api_repository_test`, and the committed OpenAPI snapshot test passes against the regenerated `v1.json`. - Mutation probes were run while the branch was written and then re-run independently afterwards — **not** in this final gate run, which executed `scripts/verify.sh all` and nothing else. First round: 14 backend and 12 app probes, each applied alone and reverted, all red. One of them found a real defect rather than confirming a test: the merge epic had copied the delete epic's request-sequence stamp, and removing the stamp changed no test because `asyncExpand` serialises the handlers and the merge clears its flag in the action it yields; the stamp was deleted instead of being papered over (84604f5). Second round, after review: 10 more probes, all red — multi-source accumulation for tags and for titles (where a double count is a duplicate-key 500, not merely a wrong report), the "copies nothing writes nothing" guard, `[.. roles]` against `[roles.Min]`, the person-role bookkeeping line, the cover loop's `break`, the title key's `ToLowerInvariant`, the page's `keptRoles` join, the reducer's error clear on a first-page load, and the epic's `on Object` catch. The independent re-probe additionally drove circle, character and series accumulation red one at a time, plus both group-scoping ternaries on the page, the reducer's load-more guard and the epic's id gate. - **Not verified**: nothing was run in the real app against a live server — no manual end-to-end pass, no real images and no real duplicate group; every claim above rests on the automated suites. The merge is not exposed to the AI assistant (`app/lib/agent/` is untouched), so no route allowlist changed. The reasoning behind the cover-ownership rule — that a foreign cover would be deleted out from under the target by `ImageCleanupHelper`, which decides orphanhood by surviving *page* references — was confirmed by reading `ImageCleanupHelper` and `UpdateDoujinUseCase`, not by executing that path. - Base note: this branch sits on 9de680b and `main` has since gained one docs-only commit (76081fb, #169) that dropped a stray line from the #141 backlog entry. `git merge-tree` against the current `origin/main` merges clean, and this branch does not touch that line — a two-dot diff will show it as an addition, a three-dot diff will not. ## Follow-ups Two items went into `docs/BACKLOG.md` under Product, at the anchor the completed "merge metadata across a group's members" item left behind: - Duplicates page: the merge dialog states the rules, not what this group would actually copy — closing it needs the members' details fetched before the dialog, or a `?dryRun=true` arm on the endpoint. - Merge: a coverless survivor can only take a source's cover when that image is already one of its own pages, so for two doujins that merely happen to be the same work the cover is never copied and the user only sees "a cover that is not one of its own pages". This branch adds no EF migration, so there is no model snapshot to regenerate against a sibling in this batch. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
POST /api/doujins/{id}/merge copies the listed sources' tags, people, circles, characters, series and titles onto the target and answers it with a report of what was added and what was dropped. The target's description, rating and cover stand unless empty; reading direction is always the target's; variants and pages never move and nothing is deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The card gains a Merge metadata affordance: the user picks which member survives, sees what will be copied and that nothing is deleted, and the group refreshes with the report summarised under it. The per-member delete is unchanged and stays how the leftovers go.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flutter analyze treats an info as a failure, so the long finder line failed verify.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A mutation probe showed the stamp was an untested branch: asyncExpand serialises the handlers and a merge clears mergingDoujinId in the success/failure action it yields, so a dropped duplicate reaching the epic afterwards already fails the id check. The delete still needs its stamp, whose flag clears only on the later reload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: pin the merge's multi-source rules and report every kept role
Some checks failed
CI / build (pull_request) Successful in 23s
Deploy Smoke Test / smoke (pull_request) Failing after 2m46s
Flutter CI / analyze-and-test (pull_request) Failing after 24m4s
CI / test (pull_request) Successful in 1m50s
774d614488
Answers the review: tests for two sources contributing the same association, for a merge that copies nothing leaving UpdatedAt alone, for a person a source carries twice, for the first usable source cover, and for the case-insensitive title language fold. The role conflict now names every role the survivor keeps instead of the lowest one, the merge epic survives an Error, roleLabel moves to core, and the dead merge-error clear is gone.

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

Two notes from the owner-side integration pass, one of them a correction to the semantics I handed this branch.

The cover rule I specified was wrong, and the branch is right to narrow it. I wrote "an empty cover takes the first source that has one" without checking what a cover may legally be. It cannot be any image: UpdateDoujinUseCase refuses a cover that is not one of the doujin's own page images across all its variants, and the code says why in a comment. There is no foreign key and no ownership constraint on the column, so a cover borrowed from another doujin would render another work's image on this one. Copying a source cover blindly would have created exactly the reference the rest of the API rejects, and the next per-member delete would then strip it out from under the target.

So taking a source cover only when the target already owns that image as a page is not a narrowing, it is the only consistent reading. In the duplicate-group case that this feature exists for, members share identical images, so the rule fires where it matters. Reporting the skipped ones rather than dropping them silently is the right call, and the residual case, a non-duplicate pair never copying a cover, is tracked in the backlog. Rebuttal accepted; the fault was in my brief.

The other two rebuttals hold as well. The dry-run dialog would need either N detail fetches or a new endpoint arm, which is a feature rather than a fix, and nothing irreversible happens before the report. The person-under-two-roles case matches the rule as written, union per person rather than per person and role, and it is now pinned by a test and stated in the doc comment.

Integration. All six branches of this batch merged together pass scripts/verify.sh all: 114 application core, 248 infrastructure, 854 REST adapter, 1 integration, and 1314 app tests. This branch conflicted with the assistant branch on one README line, where each side had edited a different bullet, so both clauses survive.

Two notes from the owner-side integration pass, one of them a correction to the semantics I handed this branch. **The cover rule I specified was wrong, and the branch is right to narrow it.** I wrote "an empty cover takes the first source that has one" without checking what a cover may legally be. It cannot be any image: `UpdateDoujinUseCase` refuses a cover that is not one of the doujin's own page images across all its variants, and the code says why in a comment. There is no foreign key and no ownership constraint on the column, so a cover borrowed from another doujin would render another work's image on this one. Copying a source cover blindly would have created exactly the reference the rest of the API rejects, and the next per-member delete would then strip it out from under the target. So taking a source cover only when the target already owns that image as a page is not a narrowing, it is the only consistent reading. In the duplicate-group case that this feature exists for, members share identical images, so the rule fires where it matters. Reporting the skipped ones rather than dropping them silently is the right call, and the residual case, a non-duplicate pair never copying a cover, is tracked in the backlog. Rebuttal accepted; the fault was in my brief. The other two rebuttals hold as well. The dry-run dialog would need either N detail fetches or a new endpoint arm, which is a feature rather than a fix, and nothing irreversible happens before the report. The person-under-two-roles case matches the rule as written, union per person rather than per person and role, and it is now pinned by a test and stated in the doc comment. **Integration.** All six branches of this batch merged together pass `scripts/verify.sh all`: 114 application core, 248 infrastructure, 854 REST adapter, 1 integration, and 1314 app tests. This branch conflicted with the assistant branch on one README line, where each side had edited a different bullet, so both clauses survive.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ A merge that copies but never deletes, reports every dropped fact by name, and refuses to age a doujin it didn't touch? You even documented why the working sets are load-bearing BEFORE I could ask. This is the kind of PR I want to pin to my wall and stare at~ ♡

Verdict: ✅ Looks good to me~

✅ What I liked~

  • The multi-source working sets. personRoles[link.PersonId] = [link.Role] after each assign, the tag/circle/character/series HashSets growing as sources apply — and the comment explaining that the join writers add blind, so a second source re-counting a shared tag is a duplicate-key 500, not a wrong report. I probed it: deleted the tag guard → Two_Sources_Carrying_The_Same_Association_Contribute_It_Once went red with exactly that 500. Deleted the personRoles bookkeeping line → both person-accumulation tests red. Restored → green. The claim is real~
  • sortBy=updated honesty, both directions. The if (description is not null || rating is not null || cover is not null) guard (UpdateAsync's nulls mean "leave alone", not "don't write" — I read DoujinService, the comment is honest) and the source-side "only read, never aged". Probed: guard neutered → the copies-nothing test red. ChildWrite.MergeMetadata sits in the ADR-0004 table where it belongs, 37/37 green.
  • The cover ownership rule. A source cover only lands when the image is one of the target's OWN pages — traced against PUT /api/doujins/{id} and the dedup content-addressing; the foreign-cover case is reported, never linked. First-usable-wins with the later usable one correctly NOT counted as skipped — and the test distinguishes first from last with two distinct images. The fixture even guards against JPEG quantization collapsing near-identical fills. Fufu~ you thought of the seed collisions~
  • The no-stamp epic gate. mergingDoujinId needs no request stamp because it clears on the epic's own terminal action — I traced the choreography: the refresh dispatches BEFORE the success action, so DuplicatesErrorAction can never strand the flag, and the delete-during-merge gate is symmetric both in reducer and page. And on Object catch — normally I'd bare my teeth at a catch broader than its siblings, but the comment (TypeError from generated casts = an Error, and a stranded flag kills every write on the page for the session) plus the dedicated StateError test earns it. That's how you justify a deviation~ ♪
  • Sibling discipline. [Required][MinLength(1)] List<Guid> = ReorderChaptersDto/ReorderPagesDto exactly; BeginTransactionAsync over the shared scoped context like DeleteDoujinUseCase; merge endpoint answers the same MapToDetail + links/actions shape as PUT; DI registration in order; OpenAPI snapshot regenerated and the pin test passes against it (5/5); merge action advertised on the resource per ADR 0022.
  • The roleLabel extraction is honest history. The middle commit imported it from detail_page (page-imports-page, tsk tsk~), and the branch fixed itself before I could pounce. The Scanners → Scanner unification is disclosed in the body — majority rules among the three copies, and the PR says so.

Local verification (no CI coverage bot for 774d614 at review time — 0 comments on the PR): backend build 0 errors (13 pre-existing warnings); DoujinMergeIntegrationTests 23/23, MergeDoujinsUseCaseTests 4/4, ChildWriteTouchesParentIntegrationTests 37/37, OpenApiSnapshotTests 5/5; flutter analyze clean; the five PR-touched app suites 114/114 serially. Three independent mutation probes, each red on exactly its pinning test, tree restored pristine at 774d614.

💡 Little ideas (non-blocking)~

  1. app/lib/core/person_display.dart:12 — roleLabel(PersonRole.scanners) is now the only label no test pins (artist/writer/translator/editor are transitively pinned through mergeSummary's expected strings). One expect(roleLabel(PersonRole.scanners), 'Scanner') would keep the plural from ever creeping back~
  2. HypermediaHelpers.cs:83 — the action title "Merge Metadata Into This Doujin" is wordier than its siblings' terse imperatives ("Update", "Delete", "Add Title"). Purely cosmetic, the verb-first style just caught my eye ♪

Automated review by Jibril · 2026-09-19
CI/CD: absent for head SHA (no coverage bot yet) · Local checks: build 0 errors, 23+4+37+5 backend tests + 114 app tests green, 3 mutation probes red-verified

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ A merge that copies but never deletes, reports every dropped fact by name, and refuses to age a doujin it didn't touch? You even documented why the working sets are load-bearing BEFORE I could ask. This is the kind of PR I want to pin to my wall and stare at~ ♡ ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **The multi-source working sets.** `personRoles[link.PersonId] = [link.Role]` after each assign, the tag/circle/character/series HashSets growing as sources apply — and the comment explaining that the join writers add blind, so a second source re-counting a shared tag is a duplicate-key **500**, not a wrong report. I probed it: deleted the tag guard → `Two_Sources_Carrying_The_Same_Association_Contribute_It_Once` went red with exactly that 500. Deleted the personRoles bookkeeping line → both person-accumulation tests red. Restored → green. The claim is real~ - **`sortBy=updated` honesty, both directions.** The `if (description is not null || rating is not null || cover is not null)` guard (UpdateAsync's nulls mean "leave alone", not "don't write" — I read DoujinService, the comment is honest) and the source-side "only read, never aged". Probed: guard neutered → the copies-nothing test red. `ChildWrite.MergeMetadata` sits in the ADR-0004 table where it belongs, 37/37 green. - **The cover ownership rule.** A source cover only lands when the image is one of the target's OWN pages — traced against `PUT /api/doujins/{id}` and the dedup content-addressing; the foreign-cover case is reported, never linked. First-usable-wins with the later usable one correctly NOT counted as skipped — and the test distinguishes first from last with two distinct images. The fixture even guards against JPEG quantization collapsing near-identical fills. Fufu~ you thought of the seed collisions~ - **The no-stamp epic gate.** `mergingDoujinId` needs no request stamp because it clears on the epic's own terminal action — I traced the choreography: the refresh dispatches BEFORE the success action, so `DuplicatesErrorAction` can never strand the flag, and the delete-during-merge gate is symmetric both in reducer and page. And `on Object catch` — normally I'd bare my teeth at a catch broader than its siblings, but the comment (TypeError from generated casts = an `Error`, and a stranded flag kills every write on the page for the session) plus the dedicated `StateError` test earns it. That's how you justify a deviation~ ♪ - **Sibling discipline.** `[Required][MinLength(1)] List<Guid>` = ReorderChaptersDto/ReorderPagesDto exactly; `BeginTransactionAsync` over the shared scoped context like DeleteDoujinUseCase; merge endpoint answers the same `MapToDetail` + links/actions shape as PUT; DI registration in order; OpenAPI snapshot regenerated and the pin test passes against it (5/5); `merge` action advertised on the resource per ADR 0022. - **The roleLabel extraction is honest history.** The middle commit imported it from detail_page (page-imports-page, tsk tsk~), and the branch fixed itself before I could pounce. The `Scanners` → `Scanner` unification is disclosed in the body — majority rules among the three copies, and the PR says so. **Local verification** (no CI coverage bot for `774d614` at review time — 0 comments on the PR): backend build 0 errors (13 pre-existing warnings); `DoujinMergeIntegrationTests` 23/23, `MergeDoujinsUseCaseTests` 4/4, `ChildWriteTouchesParentIntegrationTests` 37/37, `OpenApiSnapshotTests` 5/5; `flutter analyze` clean; the five PR-touched app suites 114/114 serially. Three independent mutation probes, each red on exactly its pinning test, tree restored pristine at `774d614`. #### 💡 Little ideas (non-blocking)~ 1. **app/lib/core/person_display.dart:12** — `roleLabel(PersonRole.scanners)` is now the only label no test pins (artist/writer/translator/editor are transitively pinned through `mergeSummary`'s expected strings). One `expect(roleLabel(PersonRole.scanners), 'Scanner')` would keep the plural from ever creeping back~ 2. **HypermediaHelpers.cs:83** — the action title `"Merge Metadata Into This Doujin"` is wordier than its siblings' terse imperatives ("Update", "Delete", "Add Title"). Purely cosmetic, the verb-first style just caught my eye ♪ --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head SHA (no coverage bot yet) · Local checks: build 0 errors, 23+4+37+5 backend tests + 114 app tests green, 3 mutation probes red-verified*

Summary

Summary
Generated on: 09/19/2026 - 21:40:12
Coverage date: 09/19/2026 - 21:38:32 - 09/19/2026 - 21:40:09
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 445
Files: 183
Line coverage: 96.6% (19051 of 19708)
Covered lines: 19051
Uncovered lines: 657
Coverable lines: 19708
Total lines: 30340
Branch coverage: 78.1% (1746 of 2233)
Covered branches: 1746
Total branches: 2233
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 96%
Name Line Branch
DoujinManager.ApplicationCore 96% 100%
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.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 85.7%
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.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.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.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.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
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.MergeDoujinsCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeRoleConflict 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.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% 87.3%
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.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 92% 66.6%
DoujinManager.Infrastructure.Services.CircleService 100% 72.2%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 100% 78.9%
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% 77.7%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 100% 72.2%
DoujinManager.Infrastructure.Services.TagService 100% 88.8%
DoujinManager.Infrastructure.Services.VariantService 96.2% 81.2%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 97.8% 89.8%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
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%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 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.FindImageBySha256UseCase 97.1% 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.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.MergeDoujinsUseCase 100% 98.4%
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.RequestBackupRestoreUseCase 100%
DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase 100% 81.8%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.SqliteHostingProbe 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 100% 50%
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% 80.8%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 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.CircleDto 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.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.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 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.MergeDoujinsDto 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.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.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% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 99.2% 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.2% 100%
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.2%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 64.7%
Name Line Branch
DoujinManager.Server 64.7% 27.6%
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 87.7% 36.6%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/19/2026 - 21:40:12 | | Coverage date: | 09/19/2026 - 21:38:32 - 09/19/2026 - 21:40:09 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 445 | | Files: | 183 | | **Line coverage:** | 96.6% (19051 of 19708) | | Covered lines: | 19051 | | Uncovered lines: | 657 | | Coverable lines: | 19708 | | Total lines: | 30340 | | **Branch coverage:** | 78.1% (1746 of 2233) | | Covered branches: | 1746 | | Total branches: | 2233 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 96%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**96%**|**100%**| |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.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|85.7%|| |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.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.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.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.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |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.MergeDoujinsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeRoleConflict|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.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%**|**87.3%**| |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.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|92%|66.6%| |DoujinManager.Infrastructure.Services.CircleService|100%|72.2%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|100%|78.9%| |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%|77.7%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|100%|72.2%| |DoujinManager.Infrastructure.Services.TagService|100%|88.8%| |DoujinManager.Infrastructure.Services.VariantService|96.2%|81.2%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|97.8%|89.8%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |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%|| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|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.FindImageBySha256UseCase|97.1%|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.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.MergeDoujinsUseCase|100%|98.4%| |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.RequestBackupRestoreUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase|100%|81.8%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.SqliteHostingProbe|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|100%|50%| |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%**|**80.8%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|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.CircleDto|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.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.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|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.MergeDoujinsDto|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.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.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%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|99.2%|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.2%|100%| |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.2%| |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.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**64.7%**|**27.6%**| |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|87.7%|36.6%| |System.Runtime.CompilerServices|0%|| </details>
merge: bring origin/main into the duplicate-merge branch
Some checks failed
CI / build (pull_request) Successful in 23s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
Flutter CI / analyze-and-test (pull_request) Failing after 24m6s
CI / test (pull_request) Successful in 2m7s
15f25bc172
Two PRs landed since this branch was cut: the assistant's page-write tool
(#170) and alias search on search_index (#172). README.md was the only
conflict — each side had edited a different bullet of the feature list, so
both clauses survive: the assistant bullet takes main's wording (delete and
reorder pages, variant edits, uploading stays in the Editor) and the
duplicates bullet keeps this branch's Merge metadata sentence. docs/BACKLOG.md
merged per item, with main's removal of the orphaned continuation line kept.

Review follow-ups, both of Jibril's non-blocking minors:

- roleLabel(PersonRole.scanners) was the one label no test pinned. The new
  app/test/person_display_test.dart pins all six arms of the switch, so the
  plural enum member cannot creep into the singular label again.

- The merge action's title was 'Merge Metadata Into This Doujin', wordier
  than its verb-first siblings (Update, Delete, Add Title). Now
  'Merge Metadata'. No test or snapshot pinned the string; the merge action's
  href stays pinned by DoujinMergeIntegrationTests.

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

Updated to 15f25bc. Both non-blocking ideas taken, and the branch is merged with main so it is mergeable again.

The role label. roleLabel(PersonRole.scanners) was pinned by nothing. New test roleLabel covers every role pins all six arms, with the plural enum member against the singular label called out in a comment, since that mismatch is the whole reason the arm is easy to get wrong. I re-ran the probe myself on the committed tree: returning 'Scanners' fails that test and nothing else.

Worth repeating one judgement the work made: a second test asserting the labels are distinct and non-empty was written and then removed before committing, because no mutation can redden it without also reddening the exhaustive test. By this repo's standard it pinned nothing of its own, so it was noise rather than coverage. That is the right instinct.

The action title is now Merge Metadata, verb-first like its siblings. I checked the old string was pinned by nothing before the rename: it appears zero times in the OpenAPI snapshot, hypermedia action titles are not in that document, and the integration test asserts the action's key and href rather than its title. No snapshot regeneration, the wire is unchanged.

The merge. One conflict, README.md, exactly where predicted: a single hunk holding two adjacent bullets, each edited by a different side. Both clauses survive, the assistant bullet in main's wording and the duplicates bullet with this branch's merge sentence. Verified by diffing the result against both parents rather than by the absence of markers: against main only this branch's two additions appear, against the pre-merge head only main's assistant clause does. The backlog auto-merged, and I swept it for orphaned continuation lines, since that is how #169 happened. None, and main's deletion of the stray line is preserved.

Two integration risks were checked rather than assumed: the merge use case only links existing entities, so the new search_index column from #172 is not its concern, and main's fake repository uses noSuchMethod, so the new port method does not break it.

scripts/verify.sh all green at 15f25bc: 70 application core, 242 infrastructure, 818 REST adapter, 1 integration, analyze clean, 1286 app tests.

Updated to `15f25bc`. Both non-blocking ideas taken, and the branch is merged with main so it is mergeable again. **The role label.** `roleLabel(PersonRole.scanners)` was pinned by nothing. New test `roleLabel covers every role` pins all six arms, with the plural enum member against the singular label called out in a comment, since that mismatch is the whole reason the arm is easy to get wrong. I re-ran the probe myself on the committed tree: returning `'Scanners'` fails that test and nothing else. Worth repeating one judgement the work made: a second test asserting the labels are distinct and non-empty was written and then removed before committing, because no mutation can redden it without also reddening the exhaustive test. By this repo's standard it pinned nothing of its own, so it was noise rather than coverage. That is the right instinct. **The action title** is now `Merge Metadata`, verb-first like its siblings. I checked the old string was pinned by nothing before the rename: it appears zero times in the OpenAPI snapshot, hypermedia action titles are not in that document, and the integration test asserts the action's key and href rather than its title. No snapshot regeneration, the wire is unchanged. **The merge.** One conflict, `README.md`, exactly where predicted: a single hunk holding two adjacent bullets, each edited by a different side. Both clauses survive, the assistant bullet in main's wording and the duplicates bullet with this branch's merge sentence. Verified by diffing the result against both parents rather than by the absence of markers: against main only this branch's two additions appear, against the pre-merge head only main's assistant clause does. The backlog auto-merged, and I swept it for orphaned continuation lines, since that is how #169 happened. None, and main's deletion of the stray line is preserved. Two integration risks were checked rather than assumed: the merge use case only links existing entities, so the new `search_index` column from #172 is not its concern, and main's fake repository uses `noSuchMethod`, so the new port method does not break it. `scripts/verify.sh all` green at `15f25bc`: 70 application core, 242 infrastructure, 818 REST adapter, 1 integration, analyze clean, 1286 app tests.
Member

🔮 fufu~ Jibril reviewed your code!

A merge commit that comes with its own conflict-resolution diary? Oh? Oh! You documented the README bullet split before I could ask who survived~ And you closed both of my round-one little ideas while you were in there. I'm delighted~ ♡

Verdict: ✅ Looks good to me~

Re-reviewed at 15f25bc (previous approval: 774d614 at comment 8390). The new commit is a merge bringing main's #170 + #172 into the branch, plus two small riders closing my round-1 non-blocking suggestions. I audited it as a three-way machine, not a skim~

✅ What I verified~

  • The merge is a provably clean union. 31 branch-only files are byte-identical to the approved 774d614; 33 main-only files are byte-identical to main's 3fbae75. Only README.md and docs/BACKLOG.md were touched by both sides, and the resolution keeps both clauses: the assistant bullet takes main's page-write wording, the duplicates bullet keeps this branch's Merge-metadata sentence. No code file overlapped — so no hidden semantic resolution, and the build proves the combination compiles.
  • The HypermediaHelpers.cs rider ("Merge Metadata Into This Doujin" → "Merge Metadata") matches its verb-first siblings (Update, Delete, Add Title). Grep confirms nothing pinned or referenced the old string, and the merge action's href stays pinned by DoujinMergeIntegrationTests — which pass. ♪
  • The person_display_test.dart rider pins all six arms of the roleLabel switch — including the plural-enum/singular-label trap on scanners. I mutation-probed it: pluralized the label in person_display.dart → exactly roleLabel covers every role goes red with Expected: 'Scanner' Actual: 'Scanners'. The pin has real teeth~ Restored pristine afterwards, of course.
  • Local checks on the merged tree (CI's coverage comment 8393 covers the abandoned first merge 2099901, not this head — so I ran everything myself): backend build 0 errors; ApplicationCore 70/70, Integration 1/1, Infrastructure 242/242, RestAdapter 813 passed / 0 failed (5 skips are permission-theory tests that can't apply when running as root); Flutter suite 1286/1286 all passed.

Fufu~ nothing is deleted, nothing is lost, and now nothing is unpinned either. Merge away~ ♡


Automated review by Jibril · 2026-09-19
CI/CD: stale for head 15f25bc (bot covered the force-pushed-away 2099901) · Local checks: full backend + Flutter suites green, mutation probe red-on-own-test

## 🔮 fufu~ Jibril reviewed your code! A merge commit that comes with its own conflict-resolution diary? Oh? Oh! You documented the README bullet split *before* I could ask who survived~ And you closed both of my round-one little ideas while you were in there. I'm delighted~ ♡ ### Verdict: ✅ Looks good to me~ Re-reviewed at `15f25bc` (previous approval: `774d614` at comment 8390). The new commit is a merge bringing main's #170 + #172 into the branch, plus two small riders closing my round-1 non-blocking suggestions. I audited it as a three-way machine, not a skim~ #### ✅ What I verified~ - **The merge is a provably clean union.** 31 branch-only files are byte-identical to the approved `774d614`; 33 main-only files are byte-identical to main's `3fbae75`. Only `README.md` and `docs/BACKLOG.md` were touched by both sides, and the resolution keeps *both* clauses: the assistant bullet takes main's page-write wording, the duplicates bullet keeps this branch's Merge-metadata sentence. No code file overlapped — so no hidden semantic resolution, and the build proves the combination compiles. - **The `HypermediaHelpers.cs` rider** ("Merge Metadata Into This Doujin" → "Merge Metadata") matches its verb-first siblings (Update, Delete, Add Title). Grep confirms nothing pinned or referenced the old string, and the merge action's href stays pinned by `DoujinMergeIntegrationTests` — which pass. ♪ - **The `person_display_test.dart` rider pins all six arms of the `roleLabel` switch** — including the plural-enum/singular-label trap on `scanners`. I mutation-probed it: pluralized the label in `person_display.dart` → exactly `roleLabel covers every role` goes red with `Expected: 'Scanner' Actual: 'Scanners'`. The pin has real teeth~ Restored pristine afterwards, of course. - **Local checks on the merged tree** (CI's coverage comment 8393 covers the abandoned first merge `2099901`, not this head — so I ran everything myself): backend build 0 errors; ApplicationCore 70/70, Integration 1/1, Infrastructure 242/242, RestAdapter 813 passed / 0 failed (5 skips are permission-theory tests that can't apply when running as root); Flutter suite **1286/1286 all passed**. Fufu~ nothing is deleted, nothing is lost, and now nothing is unpinned either. Merge away~ ♡ --- *Automated review by Jibril · 2026-09-19* *CI/CD: stale for head `15f25bc` (bot covered the force-pushed-away `2099901`) · Local checks: full backend + Flutter suites green, mutation probe red-on-own-test*
bjoern merged commit b13f4d588f into main 2026-09-20 00:41:46 +02:00
bjoern deleted branch feat/merge-metadata-across-duplicate-doujins 2026-09-20 00:41:46 +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!173
No description provided.