Guides & specifications
Integration-Test Tier Rework Plan (RemediationBacklog #14)
Status: complete (Phase 4 broker-transport tier landed 2026-07-06; Phase 5 residual = coverlet only).
- Phase 0 ✅ —
Tests/WebAPIrevived asMMCA.Common.APImiddleware unit tests (16 tests), inMMCA.ADC.CI.slnf. - Phase 1 (Identity) — ✅ verified green in CI.
Tests/Integration/MMCA.ADC.Identity.IntegrationTests: reusableIdentityIntegrationTestFixture(boots the host viaWebApplicationFactory<Program>, overrides config via process env vars set beforeCreateClient, real SQL DB viaADC_TEST_SQL_BASE/LocalDB, Respawn reset, drop-on-dispose) + 11 re-homed tests (AnonymousAuthTests,AttendeeClaimsTests). Compiles clean; runtime is verified by theintegration-testsworkflow (SQL Server service container) — it cannot be run in the headless dev environment (no Docker + an instance-wide SQL logon trigger blocks LocalDB/localhost). Run viaMMCA.ADC.Integration.slnf. - Phase 2 (Conference) — ✅ verified green in CI.
Tests/Integration/MMCA.ADC.Conference.IntegrationTests: the WAF re-points theAddForwardedJwtBearerBearer scheme at the in-memory test key (PostConfigure<JwtBearerOptions>— Authority/ConfigurationManager nulled, static RSAIssuerSigningKey+ValidIssuer), fakes the Engagement gRPCIBookmarkCountService, and the host got an env-gated warmup guard (Conference.Service/Program.csusesapp.RunAsync()underTestinginstead ofStartAsync+self-HTTP warmup). Re-homedAnonymousConferenceReadTests(33 tests — anonymous reads + organizer-authenticated writes, so it exercises the JWT override). Added toMMCA.ADC.Integration.slnf(same CI job). Compiles 0/0. - Phase 3 (Engagement) — ✅ verified green in CI.
Tests/Integration/MMCA.ADC.Engagement.IntegrationTests: same WAF shape as Conference (JWT in-process override; no warmup guard — Engagement usesapp.RunAsync()), with the Conference gRPCISessionBookmarkValidationServicefaked (validates every session except a sentinel id).AttendeeBookmarkTests(10 tests) re-homed and adapted to Engagement-only — bookmark CRUD, duplicate→409, ineligible-session→400/404, and theOwnerOrAdminFilterownership matrix (attendee-self / organizer-any / other-attendee→403). Added toMMCA.ADC.Integration.slnf. Compiles 0/0; CI run pending. - Breadth fan-out (in progress). Re-homing the remaining single-service tests onto the proven fixtures: Conference +14 files (~102 tests) — Organizer Event/Session/Room/Question/Category CRUD + lifecycle + edge-cases, Associations, Attendee Q&A, Speaker management/update-auth; Identity +4 files (~21 tests) — Auth edge-cases, Attendee auth, Attendee profile (
/UserClaims), Organizer user management. Compiles 0/0. Access-denied authz matrices split + re-homed (~55 tests, the #11 authz-gate coverage):Anonymous→ Identity 5 / Conference 21 / Engagement 4;Attendee→ Conference 24 / Identity 1. Still to re-home: Engagement bookmark edge-cases (3); speaker-link/analytics (cross-service → Phase 4). - Phase 5 (partial): integration-tests is now a deploy gate. The
integration-testsjob moved intodeploy.ymlas a required dependency ofdeploy(needs: [build-and-test, integration-tests]), and the standaloneintegration-tests.ymlwas removed — so the ~290 integration tests must pass before any production deploy (they also run on PRs). Branch-protection required-checks were rejected: they'd force PRs, conflicting with the direct-push-to-main workflow. Remaining in Phase 5: coverlet coverage collection (needs the MTPMicrosoft.Testing.Extensions.CodeCoveragewiring — deferred to avoid risking the now-gating job on an unverified coverage flag). - Phase 4 (cross-service flows) — headline flows ✅ done in-process; broker-transport tier deferred.
The two load-bearing cross-service flows are re-homed as in-process consumer-handler integration
tests on the proven per-service fixtures (no Docker — they resolve the real
IIntegrationEventHandler<T>from the booted host and assert against the real DB; runtime verified by the SQLintegration-testsjob, same as Phases 1–3):- BR-207 auto-link —
Tests/Integration/MMCA.ADC.Conference.IntegrationTests/CrossService/CrossServiceUserRegisteredTests.csdrives Conference'sUserRegisteredHandler(name-match link, ambiguous-name skip, no-match skip). ComplementsOutboxFidelityTests(Identity tier, which proved the producer enqueues the event): the consumer auto-link is now covered too. - Speaker-link round-trip —
Tests/Integration/MMCA.ADC.Identity.IntegrationTests/CrossService/CrossServiceSpeakerLinkTests.csdrives Identity'sSpeakerLinkedToUserHandler/SpeakerUnlinkedFromUserHandler(sets / clearsUser.LinkedSpeakerId). - Both required only a small additive
public IServiceProvider Services => _factory!.Services;accessor on the Conference + Identity fixtures. Compile 0/0. - Broker-transport tier: ✅ landed 2026-07-06 (container-backed, non-gating). New
Tests/Integration/MMCA.ADC.CrossService.IntegrationTestsboots all three REST hosts in one process (extern-aliasProgramaccess) against real Testcontainers SQL Server + RabbitMQ, proving the genuine MassTransit broker round-trip (UserRegistered→auto-link andSpeakerLinked/Unlinkedtransit outbox→broker→consumer, not just handler logic) plus the Conference→Engagement bookmark-count gRPC read (Engagement on a real Kestrel socket). 9 tests behind a smoke gate. It runs in the new nightly /workflow_dispatchcross-service-tests.yml(needs Docker), is excluded from both slnf filters, and is never indeploy.needsso it cannot block a production deploy. First execution is a manual dispatch. Speaker analytics cross-service reads remain the only deferred item (low value; revisit if needed).
- BR-207 auto-link —
The original MMCA.ADC.IntegrationTests (258 tests / 29 files) and MMCA.ADC.WebAPI.Tests
referenced the deleted single MMCA.ADC.WebAPI host, won't build, and were removed from
MMCA.ADC.slnx. Their single-service tests are re-homed (breadth fan-out above) and their headline
cross-service flows are re-homed (Phase 4 above), so the orphaned MMCA.ADC.IntegrationTests project
folder is now fully superseded and safe to delete (the last Phase 5 step):
Remove-Item -Recurse -Force Tests/Integration/MMCA.ADC.IntegrationTests.
Recommended strategy — two tiers
- Primary: per-service
WebApplicationFactory<Program>— one in-process host per service (Identity / Conference / Engagement), cross-service edges mocked.AddBrokerMessagingshort-circuits to in-process whenMessageBus:Provideris absent, so no broker container is needed. Covers ~80% of the suite (auth gate, CRUD, validation, soft-delete, ownership). - Secondary: real-broker + real-gRPC — Testcontainers RabbitMQ + a real Identity JWKS
endpoint, for the genuine cross-process flows (BR-207
UserRegistered→speaker auto-link,SpeakerLinkedToUser/SpeakerUnlinkedFromUserround-trip, Conference→Engagement bookmark count). - Aspire
DistributedApplicationTestingBuilderis deferred to the existing Playwright E2E lane (too heavy for the integration tier; overlaps E2E).
Three code facts that shape the rework (verified)
- Only
Conference.Serviceis WAF-incompatible — it ends withStartAsync()+ self-HTTP/2WarmupViaHttpAsync+WaitForShutdownAsync(). Identity/Engagement/Notification useapp.RunAsync(). Conference needs an env-gated guard (skip warmup + useRunAsyncunderTesting). - Non-Identity services use
AddForwardedJwtBearer(fetches{authority}/.well-known/...JWKS). A per-service WAF has no JWKS authority, so the fixture must overrideJwtBearerOptionsto validate theJwtTokenGeneratortest public key in-process (Authority/ConfigurationManager nulled,IssuerSigningKeyset). Identity itself usesAddCommonAuthentication(in-process) — no override. - Each host needs
public partial class Program;(one line) soWebApplicationFactory<Program>can bind.
Databases
- SQLite in-memory for the fast bulk tier (no Docker, CI-friendly;
DatabaseInitStrategy=EnsureCreated). - MsSql Testcontainers for a tagged SQL-fidelity subset (soft-delete filters, rowversion
concurrency, outbox,
ef migrations has-pending-model-changesdrift check — the #8 gaps). - No
SQLServerDbContextsplit — per-service DB routing is config-only viaDataSources.
Project structure
- One WAF test project per service (
MMCA.ADC.{Identity,Conference,Engagement}.IntegrationTests) — can't reference twoProgram-bearing hosts in one project. - One
MMCA.ADC.CrossService.IntegrationTestsfor the real-broker/real-gRPC tier. - Revive
WebAPI.Testsas a host-neutral middleware unit-test project (it's ~16 pure unit tests ofMMCA.Common.APIexception handlers — only the dead WebAPIProjectReferencebroke it). - Reuse
MMCA.Common.TestingIntegrationTestBase<TFixture>+JwtTokenGenerator.
CI
- Add the SQLite per-service tier to
CI.slnf(seconds, no Docker) → restores the authz/CRUD merge gate (#11) with no workflow change. - Keep the container-based MsSql + RabbitMQ tier in a separate push-to-main / nightly job.
Phased sequencing (fastest win first)
- Phase 0 — re-home
WebAPI.Testsmiddleware unit tests; drop the dead WebAPI reference; re-add toslnx+CI.slnf. ~16 tests green; removes a non-building project (#16). - Phase 1 — Identity WAF (simplest host). Build the reusable
ServiceTestFixture<TProgram>; re-home theAuth*/AttendeeClaims/OrganizerUsertests. Restores the auth/authz gate (#11). - Phase 2 — Conference WAF: first use of the
JwtBearerOptionsoverride + the warmup guard; mockIBookmarkCountService; re-home the CRUD/lifecycle/edge-case block. - Phase 3 — Engagement WAF: mock
ISessionBookmarkValidationService; bookmark +OwnerOrAdminFiltertests. - Phase 4 — SQL-fidelity subset (MsSql) + cross-service real-broker tier (BR-207, speaker-link, bookmark-count); the separate container CI job.
- Phase 5 — wire coverlet, finalize
slnx/CI.slnf, update the CLAUDE.md "excluded" note, tick #14.
Key risks
- The non-Identity
JwtBearerOptionsin-process override is the trickiest piece — prove it on one Conference auth test before fanning out. - SQLite vs SQL-Server fidelity (owned types, soft-delete filters, rowversion) — run the concurrency/soft-delete/outbox subset on MsSql.
- Re-homing ~258 cross-module tests is the bulk of the effort: cross-module seeding becomes "seed-own-DB + fake-foreign-service"; genuine cross-module assertions move to the real-broker tier.
Critical files
Tests/Integration/MMCA.ADC.IntegrationTests/Infrastructure/TestWebApplicationFactory.cs(combined-host factory → split into per-service fixtures; its JWT config block is the auth-override template)Tests/Integration/MMCA.ADC.IntegrationTests/Infrastructure/IntegrationTestBase.cs(ADC seed/auth helpers)Source/Services/MMCA.ADC.Conference.Service/Program.cs(warmup/StartAsyncguard +public partial class Program)MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationBuilderExtensions.cs(AddForwardedJwtBearer/AddCommonAuthentication— the seam the WAF overrides)MMCA.Common/Source/Hosting/MMCA.Common.Testing/IntegrationTestBase.cs,JwtTokenGenerator.csMMCA.ADC.slnx,MMCA.ADC.CI.slnf(re-add reworked projects; CI inclusion per above)