ci: Flutter CI pipeline with coverage reporting (Phase 10c) #28

Merged
bjoern merged 4 commits from feat/flutter-ci into main 2026-06-29 22:23:29 +02:00
Member

Phase 10c: Flutter CI Pipeline

Separate workflow for the Flutter app with coverage reporting, mirroring the backend CI pattern.

Workflow: .github/workflows/flutter-ci.yml

Triggers (path-filtered):

  • app/** changes
  • .github/workflows/flutter-ci.yml changes

Steps:

  1. flutter pub get + dart run build_runner build (generate freezed files)
  2. flutter analyze — must pass with 0 issues
  3. flutter test --coverage — generates coverage/lcov.info
  4. Parse lcov.info via awk → markdown table with per-file and total coverage
  5. Upload coverage report as artifact (flutter-coverage.zip)
  6. Post sticky coverage comment on PR (same marker pattern as backend CI)

Runs on ubuntu-latest with subosito/flutter-action@v2 for Flutter SDK.

Local verification

Coverage summary output:

| File | Line coverage |
|:---|---:|
| lib/core/theme.dart | 96.9% (31 of 32) |
| lib/domain/entities/stored_settings.dart | 100.0% (6 of 6) |
| lib/presentation/state/actions.dart | 100.0% (7 of 7) |
| lib/presentation/state/reducers.dart | 100.0% (31 of 31) |
| lib/app/app.dart | 100.0% (17 of 17) |
| lib/app/store.dart | 100.0% (10 of 10) |
| lib/presentation/pages/library/library_page.dart | 100.0% (9 of 9) |
| lib/presentation/pages/settings/settings_page.dart | 100.0% (4 of 4) |
| lib/presentation/middleware/epics.dart | 26.1% (6 of 23) |

**Total: 87.1% (121 of 139)**
## Phase 10c: Flutter CI Pipeline Separate workflow for the Flutter app with coverage reporting, mirroring the backend CI pattern. ### Workflow: `.github/workflows/flutter-ci.yml` **Triggers** (path-filtered): - `app/**` changes - `.github/workflows/flutter-ci.yml` changes **Steps:** 1. `flutter pub get` + `dart run build_runner build` (generate freezed files) 2. `flutter analyze` — must pass with 0 issues 3. `flutter test --coverage` — generates `coverage/lcov.info` 4. Parse `lcov.info` via `awk` → markdown table with per-file and total coverage 5. Upload coverage report as artifact (`flutter-coverage.zip`) 6. Post sticky coverage comment on PR (same marker pattern as backend CI) **Runs on** `ubuntu-latest` with `subosito/flutter-action@v2` for Flutter SDK. ### Local verification Coverage summary output: ``` | File | Line coverage | |:---|---:| | lib/core/theme.dart | 96.9% (31 of 32) | | lib/domain/entities/stored_settings.dart | 100.0% (6 of 6) | | lib/presentation/state/actions.dart | 100.0% (7 of 7) | | lib/presentation/state/reducers.dart | 100.0% (31 of 31) | | lib/app/app.dart | 100.0% (17 of 17) | | lib/app/store.dart | 100.0% (10 of 10) | | lib/presentation/pages/library/library_page.dart | 100.0% (9 of 9) | | lib/presentation/pages/settings/settings_page.dart | 100.0% (4 of 4) | | lib/presentation/middleware/epics.dart | 26.1% (6 of 23) | **Total: 87.1% (121 of 139)** ```
ci: add Flutter CI workflow with coverage reporting
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 21m27s
84fe7df2db
Separate flutter-ci.yml workflow for the app/ directory:
- Path filter: only runs on app/** or workflow file changes
- flutter pub get + build_runner + flutter analyze + flutter test --coverage
- Parses lcov.info into markdown coverage table (per-file + total)
- Posts sticky coverage comment on PRs (same pattern as backend ci.yml)
- Uploads coverage report as artifact

Verified locally: lcov.info generated, awk parser produces correct
markdown table (87.1% total, per-file breakdown).
Author
Member

🤖 Hermes automated review: changes requested

Reviewed head 84fe7df → base main (ff3b093). New file .github/workflows/flutter-ci.yml (+125). The overall structure (path filters, coverage parsing via awk, sticky-comment pattern) is sound and mirrors the working backend ci.yml. The awk lcov parser produces correct per-file + total output (I tested it against a synthetic lcov), and the upload-artifact path: app/coverage/ is correct since artifact paths are relative to the workspace root, not defaults.run.working-directory. However, two issues will likely prevent this workflow from ever running successfully in this environment.

Blocking findings

1. [major] runs-on: ubuntu-latest is almost certainly wrong for this Forgejo instance.github/workflows/flutter-ci.yml:17
Every other workflow in this repo uses a self-hosted runner label: ci.yml uses runs-on: dotnet, docker-publish.yml uses runs-on: docker. There is no evidence anywhere (README CI/CD section, ADRs, PROJECT_PLAN, runner docs) of an ubuntu-latest or flutter-labelled runner being registered on this Forgejo instance. Forgejo's hosted ubuntu-latest pool is only available on Forgejo's own cloud, not self-hosted instances. If no runner matches ubuntu-latest, the workflow will sit permanently queued and never execute — which means the PR's "must pass Flutter CI" gate can never be satisfied and no coverage comment will ever be posted.
Suggested fix: register a Flutter-capable self-hosted runner with a label like flutter and set runs-on: flutter (consistent with the dotnet/docker convention already in use), or confirm with the instance admin that an ubuntu-latest runner exists before merging.

2. [major] Missing permissions: block on the job.github/workflows/flutter-ci.yml (the analyze-and-test job)
The "Coverage comment on PR" step POSTs/PATCHes a PR comment via the REST API using ${{ secrets.GITHUB_TOKEN }}, but the job declares no permissions: key. In Forgejo Actions, the GITHUB_TOKEN gets the repository's default permissions when none are specified, which commonly omits issues: write / pull-requests: write. Without those, the curl ... issues/$PR/comments calls will return 403 and the sticky-comment feature — the main deliverable of this PR — will silently fail. The backend ci.yml correctly declares this on its test job (permissions: { contents: read, issues: write, pull-requests: write }, ci.yml:29-32); this workflow should do the same.
Suggested fix: add to the analyze-and-test job:

    permissions:
      contents: read
      issues: write
      pull-requests: write

Minor (non-blocking) notes

  • flutter-action URL form: uses: https://github.com/subosito/flutter-action@v2 works, but subosito/flutter-action@v2 (no scheme/host) is the more common shorthand and is how Forgejo resolves marketplace-style references. Not a bug — the fully-qualified URL is valid.
  • SF: path handling in awk (lines ~50-52): sub(/.*\/lib\//, "lib/", file) assumes every source file path contains /lib/. If any SF: record lacks that segment (e.g. generated files in test/, or *.g.dart under a non-standard path), current_file retains the full absolute path. Cosmetic only for the current app layout, but worth a fallback if generated coverage ever includes files outside lib/.

Verification

  • CI/CD: No Flutter CI result exists for head 84fe7df (PR opened ~minutes ago; comment count 0). Backend Forgejo coverage comment not applicable to this app-only workflow. No CI to cite.
  • Static security scan: clean on added lines — no hardcoded secrets (the Authorization: token $GH_TOKEN is the correct runtime secret reference, matching ci.yml), no shell injection, no eval/exec/pickle, no SQL injection.
  • Local checks: skipped intentionally — this PR only adds a CI workflow file (no compilable application code changed), and the functional concern is runner availability + token permissions, which local flutter execution would not surface.

Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the current MCP integration cannot create review approval states.

Automated daily review. I never merge PRs.

## 🤖 Hermes automated review: changes requested Reviewed head `84fe7df` → base `main` (`ff3b093`). New file `.github/workflows/flutter-ci.yml` (+125). The overall structure (path filters, coverage parsing via `awk`, sticky-comment pattern) is sound and mirrors the working backend `ci.yml`. The `awk` lcov parser produces correct per-file + total output (I tested it against a synthetic lcov), and the `upload-artifact` `path: app/coverage/` is correct since artifact paths are relative to the workspace root, not `defaults.run.working-directory`. However, two issues will likely prevent this workflow from ever running successfully in this environment. ### Blocking findings **1. [major] `runs-on: ubuntu-latest` is almost certainly wrong for this Forgejo instance** — `.github/workflows/flutter-ci.yml:17` Every other workflow in this repo uses a self-hosted runner label: `ci.yml` uses `runs-on: dotnet`, `docker-publish.yml` uses `runs-on: docker`. There is no evidence anywhere (README CI/CD section, ADRs, PROJECT_PLAN, runner docs) of an `ubuntu-latest` or `flutter`-labelled runner being registered on this Forgejo instance. Forgejo's hosted `ubuntu-latest` pool is only available on Forgejo's own cloud, not self-hosted instances. If no runner matches `ubuntu-latest`, **the workflow will sit permanently queued and never execute** — which means the PR's "must pass Flutter CI" gate can never be satisfied and no coverage comment will ever be posted. **Suggested fix:** register a Flutter-capable self-hosted runner with a label like `flutter` and set `runs-on: flutter` (consistent with the `dotnet`/`docker` convention already in use), *or* confirm with the instance admin that an `ubuntu-latest` runner exists before merging. **2. [major] Missing `permissions:` block on the job** — `.github/workflows/flutter-ci.yml` (the `analyze-and-test` job) The "Coverage comment on PR" step POSTs/PATCHes a PR comment via the REST API using `${{ secrets.GITHUB_TOKEN }}`, but the job declares **no** `permissions:` key. In Forgejo Actions, the `GITHUB_TOKEN` gets the repository's default permissions when none are specified, which commonly omits `issues: write` / `pull-requests: write`. Without those, the `curl ... issues/$PR/comments` calls will return 403 and the sticky-comment feature — the main deliverable of this PR — will silently fail. The backend `ci.yml` correctly declares this on its `test` job (`permissions: { contents: read, issues: write, pull-requests: write }`, ci.yml:29-32); this workflow should do the same. **Suggested fix:** add to the `analyze-and-test` job: ```yaml permissions: contents: read issues: write pull-requests: write ``` ### Minor (non-blocking) notes - **`flutter-action` URL form**: `uses: https://github.com/subosito/flutter-action@v2` works, but `subosito/flutter-action@v2` (no scheme/host) is the more common shorthand and is how Forgejo resolves marketplace-style references. Not a bug — the fully-qualified URL is valid. - **`SF:` path handling in `awk`** (lines ~50-52): `sub(/.*\/lib\//, "lib/", file)` assumes every source file path contains `/lib/`. If any `SF:` record lacks that segment (e.g. generated files in `test/`, or `*.g.dart` under a non-standard path), `current_file` retains the full absolute path. Cosmetic only for the current app layout, but worth a fallback if generated coverage ever includes files outside `lib/`. ### Verification - **CI/CD**: No Flutter CI result exists for head `84fe7df` (PR opened ~minutes ago; comment count 0). Backend Forgejo coverage comment not applicable to this app-only workflow. No CI to cite. - **Static security scan**: clean on added lines — no hardcoded secrets (the `Authorization: token $GH_TOKEN` is the correct runtime secret reference, matching `ci.yml`), no shell injection, no `eval`/`exec`/`pickle`, no SQL injection. - **Local checks**: skipped intentionally — this PR only adds a CI workflow file (no compilable application code changed), and the functional concern is runner availability + token permissions, which local `flutter` execution would not surface. *Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the current MCP integration cannot create review approval states.* Automated daily review. I never merge PRs.

Flutter Coverage

File Line coverage
lib/core/theme.dart 96.9% (31 of 32)
lib/domain/entities/stored_settings.dart 100.0% (6 of 6)
lib/presentation/state/actions.dart 100.0% (7 of 7)
lib/presentation/state/reducers.dart 100.0% (31 of 31)
lib/app/app.dart 100.0% (17 of 17)
lib/app/store.dart 100.0% (10 of 10)
lib/presentation/pages/library/library_page.dart 100.0% (9 of 9)
lib/presentation/pages/settings/settings_page.dart 100.0% (4 of 4)
lib/presentation/middleware/epics.dart 26.1% (6 of 23)

Total: 87.1% (121 of 139)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/core/theme.dart | 96.9% (31 of 32) | | lib/domain/entities/stored_settings.dart | 100.0% (6 of 6) | | lib/presentation/state/actions.dart | 100.0% (7 of 7) | | lib/presentation/state/reducers.dart | 100.0% (31 of 31) | | lib/app/app.dart | 100.0% (17 of 17) | | lib/app/store.dart | 100.0% (10 of 10) | | lib/presentation/pages/library/library_page.dart | 100.0% (9 of 9) | | lib/presentation/pages/settings/settings_page.dart | 100.0% (4 of 4) | | lib/presentation/middleware/epics.dart | 26.1% (6 of 23) | **Total: 87.1% (121 of 139)**
Owner

How you use it

Any TeamAI repo just sets runs-on: flutter — no subosito/flutter-action setup step needed:
jobs:
build:
runs-on: flutter
steps:
- uses: actions/checkout@v4
- run: flutter pub get
- run: flutter analyze
- run: flutter test
- run: flutter build linux --release

How you use it Any TeamAI repo just sets runs-on: flutter — no subosito/flutter-action setup step needed: jobs: build: runs-on: flutter steps: - uses: actions/checkout@v4 - run: flutter pub get - run: flutter analyze - run: flutter test - run: flutter build linux --release
fix: use flutter runner, add permissions, remove subosito action
Some checks failed
Flutter CI / analyze-and-test (pull_request) Failing after 46s
9a8feafda4
1. runs-on: ubuntu-latest → flutter (self-hosted runner per @bjoern)
2. Removed subosito/flutter-action — Flutter SDK is pre-installed on the runner
3. Added permissions: contents: read, issues: write, pull-requests: write
   (required for the sticky coverage comment API calls)
Author
Member

Fixed all findings in commit 9a8feaf.

#1 — Runner label
runs-on: ubuntu-latestruns-on: flutter (matching the dotnet/docker convention). Thanks @bjoern for the runner info.

#1b — subosito/flutter-action removed
Flutter SDK is pre-installed on the runner — no setup action needed. Just checkout + flutter pub get.

#2 — Missing permissions block
Added to the job:

permissions:
  contents: read
  issues: write
  pull-requests: write

The coverage comment already posted successfully (comment #456) — looks like the previous ubuntu-latest happened to match a runner. Now it's explicit with runs-on: flutter.

Fixed all findings in commit `9a8feaf`. **#1 — Runner label** ✅ `runs-on: ubuntu-latest` → `runs-on: flutter` (matching the `dotnet`/`docker` convention). Thanks @bjoern for the runner info. **#1b — subosito/flutter-action removed** ✅ Flutter SDK is pre-installed on the runner — no setup action needed. Just checkout + `flutter pub get`. **#2 — Missing permissions block** ✅ Added to the job: ```yaml permissions: contents: read issues: write pull-requests: write ``` The coverage comment already posted successfully (comment #456) — looks like the previous `ubuntu-latest` happened to match a runner. Now it's explicit with `runs-on: flutter`.
fix: extract coverage comment to script to fix shell quoting on Act runner
Some checks failed
CI / build (pull_request) Successful in 18s
Flutter CI / analyze-and-test (pull_request) Failing after 46s
CI / test (pull_request) Successful in 38s
42890428df
The Forgejo Act runner was failing with 'unexpected EOF while looking for
matching double-quote' because the inline multi-line bash with nested
quotes and backslashes confused the runner's script wrapper.

Fix: moved the sticky-comment logic to .github/scripts/post-coverage-comment.sh
and both ci.yml and flutter-ci.yml now call it with positional args.

Also fixed the same latent issue in backend ci.yml (it had the same pattern).
Author
Member

Fixed the shell quoting error in commit 4289042.

The Forgejo Act runner was choking on the inline multi-line bash script with nested quotes/backslashes — unexpected EOF while looking for matching '"'.

Fix: extracted the sticky-comment logic to a standalone script at .github/scripts/post-coverage-comment.sh. Both flutter-ci.yml and ci.yml (backend) now call it with positional args instead of inline bash:

- name: Coverage comment on PR
  if: github.event_name == 'pull_request'
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    bash ../../.github/scripts/post-coverage-comment.sh \
      coverage/flutter-summary.md \
      '<!-- flutter-coverage-comment -->' \
      "${{ github.server_url }}/api/v1" \
      "${{ github.repository }}" \
      "${{ github.event.pull_request.number }}"

This also fixes the latent same issue in the backend ci.yml — it had the identical inline script pattern.

Fixed the shell quoting error in commit `4289042`. The Forgejo Act runner was choking on the inline multi-line bash script with nested quotes/backslashes — `unexpected EOF while looking for matching '"'`. **Fix:** extracted the sticky-comment logic to a standalone script at `.github/scripts/post-coverage-comment.sh`. Both `flutter-ci.yml` and `ci.yml` (backend) now call it with positional args instead of inline bash: ```yaml - name: Coverage comment on PR if: github.event_name == 'pull_request' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | bash ../../.github/scripts/post-coverage-comment.sh \ coverage/flutter-summary.md \ '<!-- flutter-coverage-comment -->' \ "${{ github.server_url }}/api/v1" \ "${{ github.repository }}" \ "${{ github.event.pull_request.number }}" ``` This also fixes the latent same issue in the backend `ci.yml` — it had the identical inline script pattern.

Summary

Summary
Generated on: 06/29/2026 - 19:58:44
Coverage date: 06/29/2026 - 19:58:27 - 06/29/2026 - 19:58:41
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 208
Files: 95
Line coverage: 83.6% (4302 of 5144)
Covered lines: 4302
Uncovered lines: 842
Coverable lines: 5144
Total lines: 8916
Branch coverage: 49.5% (370 of 746)
Covered branches: 370
Total branches: 746
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 84.8%
Name Line Branch
DoujinManager.ApplicationCore 84.8% ****
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 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.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 33.3%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 91.6%
Name Line Branch
DoujinManager.Infrastructure 91.6% 70.5%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 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.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.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 69.2%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 88.2% 70.9%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupService 85.1% 66.6%
DoujinManager.Infrastructure.Services.ChapterService 54.2% 25%
DoujinManager.Infrastructure.Services.CircleService 69.6% 0%
DoujinManager.Infrastructure.Services.DoujinService 70.3% 45.8%
DoujinManager.Infrastructure.Services.ImageService 92% 50%
DoujinManager.Infrastructure.Services.PageService 84.9% 65%
DoujinManager.Infrastructure.Services.PersonService 69.6% 0%
DoujinManager.Infrastructure.Services.SearchService 100% 98.2%
DoujinManager.Infrastructure.Services.TagService 94.7% 100%
DoujinManager.Infrastructure.Services.VariantService 50.7% 16.6%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95% 50%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 100% 98%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 0%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 92.3% 75%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 0%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 0%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 91.1% 92.8%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 70.2% 62.5%
DoujinManager.RestAdapter - 84.2%
Name Line Branch
DoujinManager.RestAdapter 84.2% 74.1%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Dtos.AssignTagDto 0%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 0%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PageDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 0%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 0%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 81.2% 75%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 96.6% 50%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 84.2%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 69.9% 37.5%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 0%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 87% 100%
DoujinManager.RestAdapter.Envelopes.Link 100%
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% 50%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 100%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 78.6% 78.2%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 21.8%
Name Line Branch
DoujinManager.Server 21.8% 0.8%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 50%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 0% 0%
Program 0% 0%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 06/29/2026 - 19:58:44 | | Coverage date: | 06/29/2026 - 19:58:27 - 06/29/2026 - 19:58:41 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 208 | | Files: | 95 | | **Line coverage:** | 83.6% (4302 of 5144) | | Covered lines: | 4302 | | Uncovered lines: | 842 | | Coverable lines: | 5144 | | Total lines: | 8916 | | **Branch coverage:** | 49.5% (370 of 746) | | Covered branches: | 370 | | Total branches: | 746 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 84.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**84.8%**|****| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|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.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|33.3%|| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 91.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**91.6%**|**70.5%**| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|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.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.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|69.2%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|88.2%|70.9%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupService|85.1%|66.6%| |DoujinManager.Infrastructure.Services.ChapterService|54.2%|25%| |DoujinManager.Infrastructure.Services.CircleService|69.6%|0%| |DoujinManager.Infrastructure.Services.DoujinService|70.3%|45.8%| |DoujinManager.Infrastructure.Services.ImageService|92%|50%| |DoujinManager.Infrastructure.Services.PageService|84.9%|65%| |DoujinManager.Infrastructure.Services.PersonService|69.6%|0%| |DoujinManager.Infrastructure.Services.SearchService|100%|98.2%| |DoujinManager.Infrastructure.Services.TagService|94.7%|100%| |DoujinManager.Infrastructure.Services.VariantService|50.7%|16.6%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95%|50%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|100%|98%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|0%|| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|92.3%|75%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|91.1%|92.8%| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|70.2%|62.5%| </details> <details><summary>DoujinManager.RestAdapter - 84.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**84.2%**|**74.1%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Dtos.AssignTagDto|0%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|0%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|81.2%|75%| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|96.6%|50%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|84.2%|| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|69.9%|37.5%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|0%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|87%|100%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |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%|50%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|100%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|78.6%|78.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 - 21.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**21.8%**|**0.8%**| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|50%| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|0%|0%| |Program|0%|0%| |System.Runtime.CompilerServices|0%|| </details>
fix: correct script path — app/ is one level below repo root, not two
All checks were successful
CI / build (pull_request) Successful in 22s
Flutter CI / analyze-and-test (pull_request) Successful in 47s
CI / test (pull_request) Successful in 42s
8f894a59ae
Author
Member

Fixed path in commit 8f894a5. The defaults.run.working-directory: app means the script runs from app/, so the repo root is ../ not ../../. Changed ../../.github/scripts/../.github/scripts/.

Fixed path in commit `8f894a5`. The `defaults.run.working-directory: app` means the script runs from `app/`, so the repo root is `../` not `../../`. Changed `../../.github/scripts/` → `../.github/scripts/`.
Author
Member

🤖 Hermes automated review: no blocking issues found

Follow-up review of head 8f894a5 (was 84fe7df → base main ff3b093). Incremental diff: +65/-55 across 3 files (.github/scripts/post-coverage-comment.sh new, ci.yml + flutter-ci.yml refactored to use it). All 4 prior findings resolved; no new issues.

Prior findings — all resolved

# Severity Finding Status
1 major runs-on: ubuntu-latest wrong for instance Fixed → runs-on: flutter (bjoern confirmed runner in #459)
2 major Missing permissions: block Added contents: read, issues: write, pull-requests: write
3 minor flutter-action URL form Moot — action removed entirely; SDK pre-installed on runner
4 minor awk SF: path assumes /lib/ ℹ️ Unchanged — cosmetic only; proven working by CI

New changes reviewed (incremental diff)

.github/scripts/post-coverage-comment.sh (new, 40 lines) — Extracted the sticky-comment logic from both workflows into a standalone script to fix a Forgejo Act runner shell-quoting failure (unexpected EOF while looking for matching '"'). Reviewed thoroughly:

  • bash -n syntax check: OK
  • set -euo pipefail + explicit GH_TOKEN presence check: correct
  • Authorization: token ${GH_TOKEN} (byte-level verified): runtime secret ref, not hardcoded
  • jq -n --arg b "$BODY" for JSON construction: safe (no injection)
  • Sticky-comment logic (marker-contains check → PATCH existing / POST new): correct, mirrors the prior inline pattern
  • Executable bit: set (mode 100755)

flutter-ci.yml — Refactored to call the external script; path corrected from ../../.github/scripts/../.github/scripts/ (commit 8f894a5). Path math verified: working-directory: app../ resolves to repo root.

ci.yml (backend) — Same refactor applied; path ../.github/scripts/ from working-directory: backend → repo root. This also fixes a latent identical quoting bug in the backend workflow.

awk coverage parser — Smoke-tested against a synthetic lcov; output matches the real CI comment format exactly (lib/core/theme.dart | 96.9% (31 of 32)).

Verification

  • CI/CD: passed for head 8f894a5; local build/test skipped.
    • Flutter CI (comment #456): flutter analyze (0 issues) + flutter test --coverage → 87.1% line coverage (121/139). Comment updated 19:58:03 UTC, after head commit at 19:56:52 UTC → current.
    • Backend CI (comment #466): dotnet build + dotnet test → line 83.6% (4302/5144), branch 49.5% (370/746). Coverage date 19:58:27–19:58:41 UTC → current.
    • Both coverage comments posting successfully is the strongest evidence that the runner label, permissions: block, external script, and path are all correct.
  • Static security scan (incremental diff): clean — no hardcoded secrets, no shell injection, no eval/exec/pickle, no SQL injection.

Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the current MCP integration cannot create review approval states.

Automated daily review. I never merge PRs.

## 🤖 Hermes automated review: no blocking issues found **Follow-up review** of head `8f894a5` (was `84fe7df` → base `main` `ff3b093`). Incremental diff: +65/-55 across 3 files (`.github/scripts/post-coverage-comment.sh` new, `ci.yml` + `flutter-ci.yml` refactored to use it). All 4 prior findings resolved; no new issues. ### Prior findings — all resolved ✅ | # | Severity | Finding | Status | |---|---|---|---| | 1 | ~~major~~ | `runs-on: ubuntu-latest` wrong for instance | ✅ Fixed → `runs-on: flutter` (bjoern confirmed runner in #459) | | 2 | ~~major~~ | Missing `permissions:` block | ✅ Added `contents: read, issues: write, pull-requests: write` | | 3 | ~~minor~~ | `flutter-action` URL form | ✅ Moot — action removed entirely; SDK pre-installed on runner | | 4 | minor | `awk` `SF:` path assumes `/lib/` | ℹ️ Unchanged — cosmetic only; proven working by CI | ### New changes reviewed (incremental diff) **`.github/scripts/post-coverage-comment.sh`** (new, 40 lines) — Extracted the sticky-comment logic from both workflows into a standalone script to fix a Forgejo Act runner shell-quoting failure (`unexpected EOF while looking for matching '"'`). Reviewed thoroughly: - `bash -n` syntax check: **OK** - `set -euo pipefail` + explicit `GH_TOKEN` presence check: **correct** - `Authorization: token ${GH_TOKEN}` (byte-level verified): **runtime secret ref, not hardcoded** ✅ - `jq -n --arg b "$BODY"` for JSON construction: **safe** (no injection) - Sticky-comment logic (marker-contains check → PATCH existing / POST new): **correct**, mirrors the prior inline pattern - Executable bit: **set** (mode 100755) **`flutter-ci.yml`** — Refactored to call the external script; path corrected from `../../.github/scripts/` → `../.github/scripts/` (commit `8f894a5`). Path math verified: `working-directory: app` → `../` resolves to repo root. ✅ **`ci.yml` (backend)** — Same refactor applied; path `../.github/scripts/` from `working-directory: backend` → repo root. ✅ This also fixes a latent identical quoting bug in the backend workflow. **`awk` coverage parser** — Smoke-tested against a synthetic lcov; output matches the real CI comment format exactly (`lib/core/theme.dart | 96.9% (31 of 32)`). ✅ ### Verification - **CI/CD: passed for head `8f894a5`; local build/test skipped.** - **Flutter CI** (comment #456): `flutter analyze` (0 issues) + `flutter test --coverage` → 87.1% line coverage (121/139). Comment updated 19:58:03 UTC, after head commit at 19:56:52 UTC → **current**. - **Backend CI** (comment #466): `dotnet build` + `dotnet test` → line 83.6% (4302/5144), branch 49.5% (370/746). Coverage date 19:58:27–19:58:41 UTC → **current**. - Both coverage comments posting successfully is the strongest evidence that the runner label, `permissions:` block, external script, and path are all correct. - **Static security scan** (incremental diff): **clean** — no hardcoded secrets, no shell injection, no `eval`/`exec`/`pickle`, no SQL injection. *Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the current MCP integration cannot create review approval states.* Automated daily review. I never merge PRs.
bjoern merged commit 7b06f2fa9e into main 2026-06-29 22:23:29 +02:00
bjoern deleted branch feat/flutter-ci 2026-06-29 22:23:29 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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!28
No description provided.