Skip to content

Phase 5: Dashboard.Api DashboardExtensions test coverage#114

Merged
blehnen merged 50 commits into
masterfrom
phase-5-dashboard-coverage
Apr 13, 2026
Merged

Phase 5: Dashboard.Api DashboardExtensions test coverage#114
blehnen merged 50 commits into
masterfrom
phase-5-dashboard-coverage

Conversation

@blehnen

@blehnen blehnen commented Apr 13, 2026

Copy link
Copy Markdown
Owner

Summary

Final phase of the code-coverage improvement milestone. Adds targeted unit and integration tests for DotNetWorkQueue.Dashboard.Api.DashboardExtensions, improving line coverage from 33.3% to a projected ~70% (well above the 50% success criterion).

  • 4 plans executed: IConfiguration overload + transport switch (PLAN-1.1), EnableSwagger + ApiKey security definition (PLAN-1.2), CORS registration + DashboardAuthorizationConvention (PLAN-1.3), and end-to-end Swagger/CORS/AuthorizationPolicy integration tests (PLAN-2.1)
  • 19 new test methods across 4 new test files (3 unit + 1 integration, the latter split into 3 per-class files per project convention)
  • 1 test helper enhanced: DashboardTestServer.CreateAsync gained a 3-arg overload with optional configureServices and configureApp hooks for auth/authz integration tests
  • Zero production code changes. Zero new dependencies. Zero regressions.

Coverage Impact

File Before After (projected)
DashboardExtensions.cs 33.3% line (61/183) ~70% line

Actual coverage will be measured by Jenkins post-merge; the projection is based on test execution evidence and the balanced-band target in .shipyard/phases/5/RESEARCH.md.

Notable Decision: PLAN-1.3 Pivot

PLAN-1.3 originally targeted unit-testing the AuthorizationPolicy branch guard in DashboardExtensions.cs via IOptions<MvcOptions>.Value.Conventions. After 4 debugging iterations, we discovered that AddControllers(action) in a bare ServiceCollection silently drops user-added MvcOptions.Conventions even though filters propagate correctly — a quirk of ASP.NET Core's internal ConfigureMvcOptions pipeline when no real IHostEnvironment is present.

Pivot: PLAN-1.3 tested DashboardAuthorizationConvention.Apply() directly at the unit level (full coverage on the convention class itself), and PLAN-2.1's scope was extended to add an end-to-end integration test via a real WebApplication pipeline with a minimal auth scheme. The end-to-end test provides a stronger guarantee than the unreliable bare-ServiceCollection path would have.

This finding is captured as a new lesson in CLAUDE.md for future ASP.NET Core test authors.

Gates Run

Gate Result
Reviewers (4× retroactive) PASS — 0 critical, 2 non-blocking minor on PLAN-2.1 (resolved: SwaggerEndpointTests split + pipeline-ordering comment)
Verifier PASS — build 0 errors, 216/216 Dashboard.Api unit tests, 226/226 Dashboard.Api integration tests (Memory
Auditor CLEAN — no secrets, no new deps, no IaC, no OWASP findings
Simplifier MINOR (applied) — split 3-class SwaggerEndpointTests.cs into 3 per-class files matching the one-class-per-file convention used by all 28 existing integration test files
Documenter MINOR (applied) — 1 CLAUDE.md lesson + 1 inline WHY comment on DashboardTestServer.cs explaining configureApp pipeline ordering

Test Plan

  • Full Dashboard.Api unit test suite passes (216/216 on net8.0 and net10.0, ~1s per TFM)
  • Dashboard.Api integration tests pass with Memory|Sqlite|LiteDb filter (226/226 on net8.0 and net10.0, no external services needed)
  • New split files compile and 3/3 new integration tests pass individually on both TFMs
  • dotnet build "Source/DotNetWorkQueue.sln" -c Debug succeeds (0 errors, 1 pre-existing SYSLIB0012 warning unrelated to phase 5)
  • Jenkins full multi-transport integration suite (runs post-merge on main, includes SqlServer/PostgreSQL/Redis real-service stages)
  • Jenkins coverage report confirms projected DashboardExtensions line coverage ≥50% (target: ~70%)

Files Changed

New test files (all LGPL headers, MSTest 3.x, FluentAssertions 6.12.2):

  • Source/DotNetWorkQueue.Dashboard.Api.Tests/Extensions/DashboardExtensionsFromConfigurationTests.cs
  • Source/DotNetWorkQueue.Dashboard.Api.Tests/Extensions/DashboardExtensionsSwaggerTests.cs
  • Source/DotNetWorkQueue.Dashboard.Api.Tests/Extensions/DashboardExtensionsCorsAndAuthTests.cs
  • Source/DotNetWorkQueue.Dashboard.Api.Integration.Tests/Tests/SwaggerEndpointTests.cs
  • Source/DotNetWorkQueue.Dashboard.Api.Integration.Tests/Tests/CorsIntegrationTests.cs
  • Source/DotNetWorkQueue.Dashboard.Api.Integration.Tests/Tests/AuthorizationPolicyIntegrationTests.cs

Modified:

  • Source/DotNetWorkQueue.Dashboard.Api.Integration.Tests/Helpers/DashboardTestServer.cs — added 3-arg CreateAsync overload + pipeline-ordering WHY comment
  • CLAUDE.md — appended MvcOptions.Conventions lesson
  • .shipyard/ROADMAP.md, .shipyard/STATE.json, .shipyard/HISTORY.md, .shipyard/LESSONS.md — milestone bookkeeping
  • .shipyard/phases/5/* — RESEARCH, plans, SUMMARY/REVIEW/AUDIT/SIMPLIFICATION/DOCUMENTATION/VERIFICATION artifacts

Milestone Status

Code-coverage milestone: COMPLETE (phases 1–5 all shipped)

Cumulative across the milestone:

  • ~130 new unit tests
  • 2 production refactors (IDbConnectionFactory injection for SqlServer + PostgreSQL SetJobLastKnownEventCommandHandler)
  • 1 BaseLua seam refactor (virtualized TryExecute for Redis Lua handler unit tests)
  • 3 dead code files removed (ObjectPool family)
  • Trace instrumentation for CI integration tests across all transports
  • Coverage: 88.9% → projected ~90% line; major gaps in Dashboard.Api, LiteDb, Redis Lua handlers, and relational job schedulers closed

🤖 Generated with Claude Code

blehnen and others added 30 commits April 12, 2026 13:44
…admap

Four-phase plan targeting job scheduler handler unit tests, in-memory trace
exporter for CI, ObjectPool dead code investigation, and Dashboard.Api coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 has 2 plans in 1 wave (parallel):
- PLAN-1.1: Delete dead ObjectPool code (3 files, zero references)
- PLAN-1.2: Add in-memory ActivityListener to SharedSetup for CI trace coverage

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Refactor RunWithTraceVerification to delegate to shared helper
- Make activity collection opt-in (listener still always-on)
- Revert SharedSetup visibility to internal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 results: ObjectPool dead code removed, in-memory ActivityListener
added (always-on listener for coverage cascade, opt-in activity collection),
shared trace verification helper, 2 new CLAUDE.md lessons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 plans in 1 wave:
- PLAN-1.1: New CreateJobTablesCommandHandler tests
- PLAN-1.2: New GetJobId + GetJobLastKnownEvent query handler tests
- PLAN-1.3: Expand existing GetDashboardJobs/ErrorRetries tests (sync+async)

Rescoped via research: SetJobLastKnownEvent and SendJobToQueue are
transport-specific only; deferred to Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces TestHelpers/AdoNetMockFixture (sync, interface-based) and AdoNetAsyncMockFixture (async, DbConnection/DbCommand/DbDataReader base classes) to centralize NSubstitute scaffolding previously duplicated across Phase 2 unit tests. Refactors CreateJobTablesCommandHandlerTests, GetJobIdQueryHandlerTests, and GetJobLastKnownEventQueryHandlerTests to use the shared fixture. No behavioral changes; 216 tests still pass.
Dashboard sync tests now use AdoNetMockFixture with SetupReaderRows(); async tests use AdoNetAsyncMockFixture. Eliminates duplicated NSubstitute scaffolding and multi-row reader ladder previously copy-pasted across the four files. All 216 tests still pass.
Phase 2 results: 39 new unit tests for shared job handlers in
Transport.RelationalDatabase. Extracted AdoNetMockFixture +
AdoNetAsyncMockFixture helpers (-81 net lines). Added 3 CLAUDE.md
lessons (sync/async DbConnection mocking, MSTest 3.x ThrowsExactly,
no CancellationToken on dashboard async handlers).

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

10 plans across 2 waves:
- Wave 1: Refactor SqlServer/PostgreSQL SetJobLastKnownEvent to inject
  IDbConnectionFactory (production code change to enable unit testing)
- Wave 2: New test files for SqlServer/PostgreSQL/SQLite JobSchema,
  SendJobToQueue, and the refactored SetJobLastKnownEvent handlers

Rescoped via research: LiteDb and Redis transport-specific handlers
deferred to a future phase due to LiteDb concrete-type and Redis
sealed-multiplexer testability issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ler to inject IDbConnectionFactory

Replaces the hardcoded new SqlConnection(_connectionInformation.ConnectionString)
with _dbConnectionFactory.Create() so the handler can be unit tested without a
live SQL Server. Parameter setup switched to generic IDbDataParameter creation
to match the IDbConnection abstraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p NpgsqlConnection cast, expand tests

The Wave 1 refactor cast IDbConnection to NpgsqlConnection, which made
the handler untestable (NpgsqlConnection is sealed). Re-refactored to
operate on IDbConnection directly using generic DbType (Int64 for time
fields, AnsiString for JobName) and IDbCommand.CreateParameter().
Added 3 missing Handle() lifecycle tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 3 results: 41 new unit tests across 8 new test files for relational
transport job handlers (SqlServer/PostgreSQL/SQLite). Refactored 2 production
handlers (SqlServer + PostgreSQL SetJobLastKnownEvent) to inject
IDbConnectionFactory. Mid-flight re-refactor of PostgreSQL handler to drop
sealed NpgsqlConnection cast.

Added 2 CLAUDE.md lessons:
- Casting IDbConnection to sealed transport types breaks NSubstitute
- IDbConnectionFactory injection is the correct test seam

LiteDb + Redis deferred to a future phase due to concrete LiteDB types
and Redis BaseLua sealed multiplexer issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…LiteDb+Redis

- Mark Phases 1-3 as COMPLETE with accurate scope and outcomes
- Insert NEW Phase 4 for the deferred LiteDb + Redis transport handlers
- Renumber old Phase 4 (Dashboard.Api) to Phase 5
- Add cumulative outcomes section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
blehnen and others added 20 commits April 12, 2026 17:52
10 plans across 3 waves:
- Wave 1: Production seams (BaseLua TryExecute virtualization, RedisJobQueueCreation
  depend on IQueueCreation interface for testability)
- Wave 2: LiteDb tests using real in-memory LiteDB instances (5 plans)
- Wave 3: Redis tests using the Wave 1 seams (3 plans)

Key planning decisions:
- LiteDb: real in-memory LiteDB (no production refactor needed)
- Redis: minimal additive refactors (BaseLua virtual + IQueueCreation seam)
- Per-handler plans (max 1 task each) to mitigate builder lockup risk
- LiteDb InternalsVisibleTo confirmed already in place
- RedisJobQueueCreation refactor uses existing IQueueCreation interface
  (no new types added, no removal of sealed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test seam for Lua handler subclasses to override the Redis interaction
without mocking sealed StackExchange.Redis types.

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

Changes constructor to take IQueueCreation (already implemented by
RedisQueueCreation) instead of concrete sealed RedisQueueCreation.
Adds Guard.NotNull. Enables wrapper unit tests via NSubstitute.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dler test coverage

Added 6 Handle() branch tests (message not found, empty queue, happy path,
multi-row isolation, null payloads, format exception). Lifts coverage from
40.9% baseline to ~100% of executable Handle() lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 4 (LiteDb + Redis transport job handler tests) -- post-build pipeline:
- Verification: PASS (full-solution Debug build clean; LiteDb.Tests 166/166;
  Redis.Tests 190/190; deferred scopes in 2.2/2.4 validated against existing
  integration test coverage)
- Security audit: CLEAN (no secrets, no new deps, seam refactors benign)
- Simplification: one minor finding on DashboardUpdateMessageBodyLuaTests
  disposed-connection test -- resolved via documentary comment acknowledging
  the test is a named-for-intent duplicate of the null-return path
- Documentation: three Lessons Learned added to CLAUDE.md (in-memory LiteDB
  pattern, BaseLua virtual TryExecute seam, LiteDbConnectionManager no-seam
  limitation)

State: phase 4 complete. Phase 5 (Dashboard.Api DashboardExtensions) is the
only remaining phase in the code-coverage milestone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 5 (Dashboard.Api DashboardExtensions coverage) -- planning:
- CONTEXT-5.md captured (balanced 60-70% target, researcher-decides test
  layer, feature branch workflow for PR merge)
- RESEARCH.md authored directly by orchestrator after the dispatched
  researcher agent stalled mid-work (documented subagent lockup pattern)
- Key finding: Decision 4 (delete dead overloads) is void -- all 3 public
  methods on DashboardExtensions have real callers. The IConfiguration
  overload has 0% coverage despite being the production Dashboard.Ui
  entrypoint; this is the highest-ROI test target
- Four plans produced across two waves:
  - Wave 1 (parallel unit tests): PLAN-1.1 IConfiguration overload + 5
    transport switch arms (~50 lines), PLAN-1.2 Swagger + ApiKey security
    (~38 lines), PLAN-1.3 CORS + AuthorizationPolicy (~17 lines)
  - Wave 2 (integration): PLAN-2.1 Swagger + CORS integration tests via
    DashboardTestServer (~12 lines)
- Self-critique fixed a wave-1 file conflict (1.2 and 1.3 both appended to
  same test file) and a wrong DashboardOptions property name in 1.1
- Stale Schyntax cleanup PLAN-1.1.md archived under
  plans/archived-schyntax-cleanup/ (git mv, history preserved)
- Projected coverage: 33.3% -> ~64% (within balanced band)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tch tests

Adds DashboardExtensionsFromConfigurationTests.cs with 5 new test methods:
- Happy-path SQLite :memory: registration via IConfiguration overload
- Parameterized test over all 5 transport switch arms (SqlServer, PostgreSql,
  SQLite, LiteDb, Redis) using throwaway connection strings
- Unknown-transport negative test (ArgumentException)
- Missing-Transport and missing-ConnectionString defensive-guard tests

Uses Assert.ThrowsExactly<T> per MSTest 3.x convention. Asserts against
DashboardOptions.ConnectionRegistrations (internal, InternalsVisibleTo already
grants access from the test project).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ionConvention tests

Adds DashboardExtensionsCorsAndAuthTests.cs with 4 test methods:
- CORS policy registers when EnableCors=true with non-empty origins
- CORS policy does NOT register when CorsOrigins is empty (branch guard)
- DashboardAuthorizationConvention.Apply adds AuthorizeFilter to a dashboard
  controller (positive branch)
- DashboardAuthorizationConvention.Apply ignores controllers from other
  assemblies (negative branch)

Note: the DashboardExtensions-level branch "AuthorizationPolicy set -> convention
added" is deliberately not asserted at unit-test scope. Debugging showed the MVC
options pipeline in a bare ServiceCollection doesn't reliably surface the
Dashboard's conventions via IOptions<MvcOptions> — the filters are added but
the conventions are dropped somewhere in the pipeline. The convention class
itself is exercised directly via Apply() instead; end-to-end coverage of the
DashboardExtensions branch belongs in the Dashboard.Api.Integration.Tests
project (PLAN-2.1 or a follow-up).

PLAN-1.2 note: the PLAN-1.2 builder wrote DashboardExtensionsSwaggerTests.cs
before hitting a sandbox block on git commit, and the file was carried along
in the preceding PLAN-1.1 commit due to pre-staging. Both files are Wave 1
and both pass tests; per-plan atomicity is not strict here.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…integration tests

Adds SwaggerEndpointTests.cs with 3 test classes / 3 test methods exercising
the UseDotNetWorkQueueDashboard pipeline branches that unit tests cannot cover:

- SwaggerEndpointTests.SwaggerJson_ReturnsOk_WithValidOpenApiShape — spins up
  a dashboard with EnableSwagger=true and verifies /swagger/v1/swagger.json
  returns 200 with valid OpenAPI content, exercising the UseSwagger() branch
  on line 204 of DashboardExtensions.cs.

- CorsIntegrationTests.CorsRequest_ReturnsAllowOriginHeader_WhenOriginMatches
  — hits /api/v1/dashboard/health with an Origin header and asserts the
  Access-Control-Allow-Origin response header, exercising the UseCors branch
  on line 199.

- AuthorizationPolicyIntegrationTests.DashboardController_Unauthenticated_
  Returns_Unauthorized_When_AuthorizationPolicy_Set — registers a minimal
  Test auth scheme and a DashboardAdmin policy requiring an authenticated
  user, sets options.AuthorizationPolicy = "DashboardAdmin", hits
  /api/v1/dashboard/connections (an MVC controller covered by the Dashboard
  authorization convention), and asserts 401. This recovers the end-to-end
  coverage gap left by PLAN-1.3's scope pivot — it proves the branch guard
  in DashboardExtensions.cs:101-105 DOES add the convention and that the
  convention DOES apply AuthorizeFilter to dashboard controllers in a real
  ASP.NET Core pipeline.

Extends DashboardTestServer with an optional configureServices and
configureApp hook to allow authentication/authorization registration without
touching existing call sites (the 1-arg overload remains the default path
for the other 40+ integration tests).

Verification: 226/226 Memory/Sqlite/LiteDb/Swagger/Cors/Authorization/Health
tests pass on both net8.0 and net10.0. Zero regressions from the
DashboardTestServer change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…CLAUDE.md lesson

Post-review cleanups from the phase 5 simplification + documentation gates:

- Split SwaggerEndpointTests.cs into three per-class files
  (SwaggerEndpointTests, CorsIntegrationTests, AuthorizationPolicyIntegrationTests)
  to match the one-class-per-file convention used across all 28 existing
  integration test files. NoAuthHandler moved into AuthorizationPolicyIntegrationTests.cs.
  Mechanical split, zero logic change; 3/3 tests pass on both net8.0 and net10.0.

- Add WHY comment to DashboardTestServer.CreateAsync documenting that
  configureApp runs after Dashboard middleware and before MapControllers
  (noted as a structural fragility in REVIEW-2.1).

- Append CLAUDE.md lesson: AddControllers(action) in a bare ServiceCollection
  silently drops MvcOptions.Conventions while filters propagate. Cost 4
  debugging iterations in PLAN-1.3; use WebApplication integration tests
  instead of bare ServiceCollection for any test that must assert an
  IControllerModelConvention was registered.
Retroactive post-build pipeline after /shipyard:resume. All 4 plans
(PLAN-1.1, 1.2, 1.3, 2.1) had been committed in earlier sessions but
the reviewer gate and later gates were missed when the session was
interrupted.

Gates run in this session:
- Reviewers (4/4 PASS, 0 critical): REVIEW-{1.1,1.2,1.3,2.1}.md
- Verifier (PASS): VERIFICATION.md — build 0 errors, unit tests
  216/216, integration tests 226/226 on Memory|Sqlite|LiteDb filter,
  no regressions, projected ~70% DashboardExtensions line coverage
- Auditor (CLEAN): AUDIT-5.md — no secrets, no new deps, no IaC, no
  OWASP findings
- Simplifier (MINOR): SIMPLIFICATION-5.md — 1 medium applied
  (split SwaggerEndpointTests), 2 low pre-existing deferred
- Documenter (MINOR): DOCUMENTATION-5.md — 1 CLAUDE.md lesson added
  (MvcOptions.Conventions) + 1 inline comment on DashboardTestServer

Phase 5 COMPLETE. All 5 phases of the code-coverage milestone
complete. Ready for /shipyard:ship.
…tone

Appends a dated section to .shipyard/LESSONS.md covering:
- What went well: balanced-budget plan shape, 4/4 PASS on first reviewer
  pass, Wave-2 scope extension absorbing PLAN-1.3's gap
- Surprises: MvcOptions.Conventions propagation quirk in bare
  ServiceCollection, clean resume after 9 session interruptions,
  retroactive reviewer gate viability
- Pitfalls: stop-and-pivot rule for 4th-iteration DI unit tests,
  one-class-per-file convention, STATE.json staleness after interruptions
- Process: researcher should declare wave-1-to-wave-2 scope contracts,
  artifacts-over-STATE.json reconciliation on resume, retroactive
  reviewer pattern as supported resume path
@blehnen blehnen self-assigned this Apr 13, 2026
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.55%. Comparing base (027c2cb) to head (a1cea76).
⚠️ Report is 51 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #114      +/-   ##
==========================================
+ Coverage   86.41%   88.55%   +2.13%     
==========================================
  Files         995      994       -1     
  Lines       29313    29298      -15     
  Branches     2388     2380       -8     
==========================================
+ Hits        25331    25944     +613     
+ Misses       2991     2475     -516     
+ Partials      991      879     -112     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@blehnen blehnen merged commit 8aeccf5 into master Apr 13, 2026
4 checks passed
@blehnen blehnen deleted the phase-5-dashboard-coverage branch April 13, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant