Onboarding guide
27. Testing & Quality Infrastructure
What this group covers. Everything the codebase uses to prove itself: the four reusable
test-support packages that ship out of MMCA.Common/Source/Hosting (MMCA.Common.Testing,
MMCA.Common.Testing.E2E, MMCA.Common.Testing.UI, MMCA.Common.Testing.Architecture), the
architecture-fitness rule library that gates the build, the backend-less component Gallery harness, and
the many per-repo test projects that consume all of it. The distinction to hold onto while reading:
most of the types in this group are reusable bases, fixtures, harnesses, and helpers compiled
into and shipped by MMCA.Common, while the concrete [Fact]-bearing test classes that subclass them
live in each consumer repo (MMCA.Common.*.Tests, MMCA.ADC.*.Tests, MMCA.Store.*.Tests). Those
individual test classes are cataloged by project in the companion rollup section; this chapter teaches
the machinery they stand on.
There are five moving parts, and they map onto the test pyramid plus one governance layer:
- Integration-test scaffolding (
IIntegrationTestFixture,IntegrationTestBase<TFixture>,SqlServerIntegrationTestFixtureBase<TEntryPoint>,JwtTokenGenerator,FeatureManagementTestExtensions,EntityBuilderBase<TBuilder, TEntity>) boots a real service host in-process against a throwaway SQL Server database and drives it over HTTP. - Architecture fitness functions (
IArchitectureMap,Layer,LayerRef,ArchitectureAssert,RuleHelpers,CrossEntityNavigationFinder, the eighteenArchitectureRules.*partial files, and the thirty abstract*TestsBaseclasses includingRouteAuthorizationTestsBaseandBrandColorTokenTestsBase) turn architectural rules into build-gating assertions that run identically across every repo. - Component (bUnit) testing (
BunitComponentTestBase,TestPrincipal,CapturingHttpMessageHandler,UiHttpServiceHarness,MarkupSnapshot) render Blazor components in isolation with real MudBlazor services and faked HTTP/auth edges. - End-to-end (Playwright) testing (
PlaywrightFixture,E2ETestConfiguration,PageExtensions,AxeOptions,AccessibilityViolationException,WebVitalsCollector, and the reusable page objectsLoginPage/RegisterPage/ProfilePage) drive a real browser against a running app, asserting accessibility and performance alongside behavior. - Contract and pipeline bases (
SecurityHeadersTestsBase,OpenApiContractTestsBase<TFixture>,ProblemDetailsContractTestsBase<TFixture>,ServiceInfoVersioningContractTestsBase<TFixture>,DecoratorPipelineOrderTestsBase<TCommand, TCommandResult, TQuery, TQueryResult>,HandlerTestBase<THandler>) pin cross-cutting HTTP and pipeline guarantees so a refactor cannot silently drop them.
This whole group is the [Rubric §14, Testability] story made concrete: the framework does not merely permit testing, it ships the reusable substrate so every consumer tests the same way. The front-end slices additionally carry [Rubric §21, Accessibility], [Rubric §22, Responsive/Cross-Browser], [Rubric §23, Front-End Performance], and [Rubric §28, Front-End Testing]; the fitness library carries [Rubric §34, Architecture Governance & Documentation].
Integration tests: a real host, a throwaway database, a per-test reset
The integration tier boots the actual application, not a mock of it. The abstraction at its center is
IIntegrationTestFixture
(MMCA.Common.Testing/IIntegrationTestFixture.cs:8): a two-method contract, CreateClient()
(IIntegrationTestFixture.cs:11) and ResetDatabaseAsync() (IIntegrationTestFixture.cs:19), that
hides how the host and its database are provisioned. Its remarks are load-bearing: a host running
multiple physical data sources (database per service, see
primer and ADR-006) must reset every
relational source, and a fixture can resolve IEntityDataSourceRegistry / IDataSourceResolver from
the booted host to enumerate them (IIntegrationTestFixture.cs:13-18).
IntegrationTestBase<TFixture>
(MMCA.Common.Testing/IntegrationTestBase.cs:13) is the per-test base every integration test class
inherits. It implements xUnit's IAsyncLifetime, so InitializeAsync resets the database before each
test (IntegrationTestBase.cs:31) and DisposeAsync disposes the HTTP client after
(IntegrationTestBase.cs:34). It exposes typed HTTP helpers (GetAsync<T>, PostAsync<T>,
PutAsync<T>, PutAsync, DeleteAsync, IntegrationTestBase.cs:51-72), bearer-token management
(SetBearerToken / ClearAuthentication, IntegrationTestBase.cs:42-48), and a thread-safe
NextId() counter seeded at 1000 (IntegrationTestBase.cs:16,75) so parallel tests never collide on
generated identifiers. Downstream projects subclass it to add domain-specific auth and entity helpers.
SqlServerIntegrationTestFixtureBase<TEntryPoint>
(MMCA.Common.Testing/SqlServerIntegrationTestFixtureBase.cs:27) is the concrete fixture scaffolding.
InitializeAsync (SqlServerIntegrationTestFixtureBase.cs:67) mints a GUID-suffixed database name,
sets ASPNETCORE_ENVIRONMENT=Testing and the top-level connection string as process environment
variables (so the host reads them at configure-time, :74-77), builds the subclass-supplied
WebApplicationFactory (:79), and forces database creation by requesting the first client, which runs
the host's Migrate init strategy (:81-84). It then builds a Respawn checkpoint that ignores
__EFMigrationsHistory (:90-94); ResetDatabaseAsync (:99) replays that checkpoint between tests,
and DisposeAsync (:115) drops the throwaway database and restores every pushed environment variable
(:125-130). The Testing environment is chosen deliberately so appsettings.Development.json (which
points a module's DataSources entry at localhost) does not load, leaving the resolver to collapse
onto the overridden top-level connection string, a single-database monolith shape (:16-24). Server
selection defaults to LocalDB but is overridable through SqlBaseEnvironmentVariable (:58, read at
:69-70) so CI can target a SQL service container. Because these fixtures need a reachable SQL Server,
the per-module *.Integration.slnf suites build in a headless sandbox but only run in CI.
Three helpers round out the tier. JwtTokenGenerator
(MMCA.Common.Testing/JwtTokenGenerator.cs:29) issues RS256-signed tokens (GenerateToken,
JwtTokenGenerator.cs:111) using an embedded dev RSA-2048 keypair (JwtTokenGenerator.cs:48-95) under a
fixed kid of mmca-test-key (:40), so integration tests exercise the exact JWKS/RS256 validation code
path production runs (ADR-004); the class remarks flag, correctly, that the committed keypair is insecure
by design and must never be used in a real deployment (:21-27). FeatureManagementTestExtensions
(MMCA.Common.Testing/FeatureManagementTestExtensions.cs:10) adds a ConfigureTestFeatureFlags
extension member (:21) that builds an in-memory FeatureManagement:* configuration so a test
WebApplicationFactory can flip a gate without touching appsettings.json.
EntityBuilderBase<TBuilder, TEntity>
(MMCA.Common.Testing/Builders/EntityBuilderBase.cs:9) is a minimal fluent-builder base whose single
abstract Build() (:17) returns the entity through its domain factory, so test setup specifies only
what a test cares about. Together these embody [Rubric §11, Security] (real token validation rather than
bypassed auth middleware) and [Rubric §14, Testability].
Architecture fitness functions: rules that gate the build
The layering and DDD conventions this codebase commits to are not left to code review, they are executed
as tests. The reusable rule library lives in MMCA.Common.Testing.Architecture and is the subject of
ADR-015. Its keystone is IArchitectureMap
(MMCA.Common.Testing.Architecture/IArchitectureMap.cs:39): the single per-repo boundary every fitness
function keys off. Each repo supplies one implementation (for example StoreArchitectureMap) declaring
its layer and module assemblies as LayerRef records (IArchitectureMap.cs:31) tagged by the
Layer enum (IArchitectureMap.cs:9), and exposes them through query members such as
OfLayer, ModuleDomain, ModuleApplication, For, ModuleOf, and OtherModuleNamespaces
(IArchitectureMap.cs:51-81). The shared rules consume only the interface, which is why one rule body
runs identically across MMCA.Common, MMCA.Store, MMCA.ADC, and Helpdesk: the map is the only thing that
varies. Layer deliberately includes optional layers (Ui, Grpc, Contracts, ServiceHost,
IArchitectureMap.cs:16-19) that a repo simply omits, so a rule iterating them is vacuously satisfied
with no compile dependency on an absent assembly (IArchitectureMap.cs:3-8).
The rule bodies are split across sixteen ArchitectureRules.* partial files (layers, purity, handlers,
handler results, entities, events, modules, slices, naming, transport, controllers,
immutability, governance, localization, localized text, and specifications; aggregate-convention
rules live inside ArchitectureRules.Entities.cs and are exercised through
Bases/AggregateConventionTestsBase.cs, not a dedicated partial), and the thirty abstract
*TestsBase classes under Bases/ (LayerDependencyTestsBase, DomainPurityTestsBase,
MicroserviceExtractionTestsBase, ModuleIsolationTestsBase, PiiConventionTestsBase,
DependencyVersionTestsBase, IntegrationEventContractTestsBase, DataResidencyTestsBase, and more)
each expose a rule as one [Fact] that a sealed per-repo subclass activates by supplying its map.
Failures report through ArchitectureAssert
(MMCA.Common.Testing.Architecture/ArchitectureAssert.cs:8), which has two overloads: one lists the
failing types from a NetArchTest TestResult (ArchitectureAssert.cs:11-23), the other lists a
reflection-derived violation set (ArchitectureAssert.cs:26-32). Rules NetArchTest cannot express
(method return types, generic constraints, property accessors, attribute usage) reflect over loaded types
via the internal RuleHelpers (MMCA.Common.Testing.Architecture/RuleHelpers.cs:14),
whose LoadableTypes extension property tolerates a partially resolvable assembly by falling back to the
ReflectionTypeLoadException's resolved types (RuleHelpers.cs:19-33). One such walk,
CrossEntityNavigationFinder
(MMCA.Common.Testing.Architecture/ArchitectureRules.Specifications.cs:97), is an ExpressionVisitor
that collects the entity types a specification's criteria navigates to beyond its own
(ArchitectureRules.Specifications.cs:99-105). These runtime rules are the second of two enforcement
layers, the first being the compile-time MSBuild layer guard
(MMCA.Common/Source/Build/MMCA.Common.LayerEnforcement.targets, see
group 14); ADR-015 describes both, and this is the clearest
[Rubric §34, Architecture Governance] expression in the codebase.
The fitness library reaches beyond pure layering into cross-cutting product guarantees.
RouteAuthorizationTestsBase
(MMCA.Common.Testing.Architecture/Bases/RouteAuthorizationTestsBase.cs:22, tagged rubric §25 in its own
remarks, [Rubric §25, Navigation & IA]) reflects over routable Blazor pages and asserts every governed
page keeps its [Authorize(Roles = "...")] gate, so an admin route cannot regress to a bare
[Authorize]. It detects RouteAttribute and AuthorizeAttribute by full-name reflection
(RouteAuthorizationTestsBase.cs:24-25) so the package stays free of ASP.NET references, and a
MinimumGovernedPages floor (:47) guards against a moved namespace silently emptying the scan.
BrandColorTokenTestsBase
(MMCA.Common.Testing.Architecture/Bases/BrandColorTokenTestsBase.cs:13, [Rubric §20, Design System &
Theming]) reads landing-page stylesheets embedded as manifest resources and fails the build if a host
re-hardcodes the brand hex #1565C0 instead of sourcing var(--mmca-primary) from the shared token
(BrandColorTokenTestsBase.cs:15-16,24). Sibling bases pin integration-event contracts (ADR-010), the
MassTransit-v8 major-version policy (DependencyVersionTestsBase), data residency, forms conventions,
localization resources, concurrency, and constructor dependency counts, so the governance-as-tests
pattern spans much of the 34-category rubric.
Component tests: real MudBlazor, faked edges
The bUnit tier renders a single Blazor component in-process with its real dependencies but stubbed
network and auth. BunitComponentTestBase
(MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:33) registers MudBlazor services and
puts JSInterop in loose mode so MudBlazor's JS probes do not throw during render
(BunitComponentTestBase.cs:42-43), then wires a mutable AuthenticationStateProvider
(BunitComponentTestBase.cs:97) plus an IsAuthenticatedAuthorizationService
(BunitComponentTestBase.cs:111) so both <AuthorizeView> cascades and pages that inject the provider
directly behave. Tests render anonymously by default via RenderUnderTest<TComponent>
(BunitComponentTestBase.cs:59) or as a supplied ClaimsPrincipal via RenderAs<TComponent>
(BunitComponentTestBase.cs:65), with TestPrincipal
(MMCA.Common.Testing.UI/Infrastructure/TestPrincipal.cs:6) minting the authenticated principal
(authentication type, user_id claim, name, roles, TestPrincipal.cs:13-20). RenderMudProviders
(BunitComponentTestBase.cs:83) mounts the popover, dialog, and snackbar providers and returns them as a
MudProviderHandles record (BunitComponentTestBase.cs:92) so components that open a dialog or raise a
toast have somewhere to render. The class is pinned to bUnit v2 (the line compatible with xUnit v3 and
Microsoft Testing Platform) and isolates every version-specific symbol here so a bUnit change touches
only this file (BunitComponentTestBase.cs:24-31). Localization is pre-registered (AddLocalization,
BunitComponentTestBase.cs:48-52) so components injecting IStringLocalizer<T> (ADR-027) render without
per-test setup, an [Rubric §27, i18n] touch.
HTTP-backed UI services are exercised without a server through CapturingHttpMessageHandler
(MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:18), a canned-response,
request-recording HttpMessageHandler supporting both a responder delegate
(CapturingHttpMessageHandler.cs:38) and route registration, with unmatched requests returning 404 to
mirror the WebAPI's not-found behavior (CapturingHttpMessageHandler.cs:7-17); it rebuilds each response
fresh so a Polly retry never reuses a consumed HttpContent, and records every request as a
CapturedRequest (CapturingHttpMessageHandler.cs:129) against a registered Route
(CapturingHttpMessageHandler.cs:110). UiHttpServiceHarness
(MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:12) wraps that handler with a
FreshApiClientFactory (UiHttpServiceHarness.cs:73) returning a fresh "APIClient" per call (the
services dispose each client, so the same instance must never come back twice) plus a fixed-token storage
stub, on a https://gateway.test/ base address (UiHttpServiceHarness.cs:15).
MarkupSnapshot (MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs:21) adds
dependency-free golden-markup regression testing: it normalizes the per-render GUIDs MudBlazor injects,
compares against a committed baseline under Snapshots/ next to the calling test, and returns a
MarkupSnapshotResult (MarkupSnapshot.cs:104) for the caller to assert on; UPDATE_SNAPSHOTS=1
rewrites baselines and a missing baseline is written but reported as a non-match so a regression cannot
slip through (MarkupSnapshot.cs:6-20). This tier is [Rubric §28, Front-End Testing] and [Rubric §18, UI
Architecture].
End-to-end tests: a real browser, accessibility and performance as gates
The E2E tier drives a real browser through Playwright. PlaywrightFixture
(MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:6) is an xUnit collection fixture that
launches the engine selected from configuration, chromium, firefox, or webkit, with unknown values
falling back to Chromium (PlaywrightFixture.cs:16-22). That environment-selected engine is what lets CI
run the same suite as a cross-browser matrix, [Rubric §22, Responsive/Cross-Browser]; in MMCA.Common's
ui-e2e job all three engines are required merge gates. Headless mode, slow motion, base URL, timeouts,
trace capture, and the seeded admin/user credentials all come from
E2ETestConfiguration (MMCA.Common.Testing.E2E/Infrastructure/E2ETestConfiguration.cs:8),
whose nested AdminCredentials (E2ETestConfiguration.cs:66) and UserCredentials
(E2ETestConfiguration.cs:78) let a downstream project set app-specific defaults through a
[ModuleInitializer] while environment variables always win.
The hard part of Blazor E2E is timing, and PageExtensions
(MMCA.Common.Testing.E2E/Infrastructure/PageExtensions.cs:19) is where that knowledge is centralized, as
C# extension(IPage) and extension(ILocator) blocks (PageExtensions.cs:21,185, see
primer). The app uses InteractiveAuto with
prerendering, so a page appears as static HTML before the runtime wires its event handlers.
WaitForBlazorAsync (PageExtensions.cs:27) waits for window.Blazor._internal then two animation
frames plus a 500 ms settle before any interaction (PageExtensions.cs:32-40);
GotoAndWaitForBlazorAsync (:47) pairs navigation with it and deliberately waits on Load rather than
NetworkIdle, because the persistent SignalR WebSocket means network idle never arrives (:50-52);
BlazorNavigateAsync (:62) routes client-side so a protected page is not re-prerendered without its
token. FillAndVerifyAsync (:197) fills a field then auto-waits until the value sticks, retyping
character by character if hydration wiped it (:212-214), and ClickAndVerifyAsync (:230) and
ClickAndWaitForUrlAsync (:273) retry a click until its visible effect appears so a click that beats
hydration is not silently swallowed. These helpers encode hard-won lessons about the prerender and
hydration race and are shared by every page object.
Accessibility and performance are asserted here, not deferred to a separate audit.
AssertNoAccessibilityViolationsAsync (PageExtensions.cs:157) runs an axe-core scan and throws
AccessibilityViolationException
(MMCA.Common.Testing.E2E/Infrastructure/AccessibilityViolationException.cs:7) with a compact per-node
summary of every violation (PageExtensions.cs:170-181), so an inaccessible page fails the build,
[Rubric §21, Accessibility]. The scan scope itself is shipped as AxeOptions
(MMCA.Common.Testing.E2E/Infrastructure/AxeOptions.cs:9): Wcag21Aa (AxeOptions.cs:17) pins the
documented target of WCAG 2.1 AA tags and deliberately excludes axe's advisory best-practice rules, and
Wcag21AaExceptMudPagerCombobox (AxeOptions.cs:35) is the one documented carve-out for MudBlazor
9.6.0's unlabeled pager select (AxeOptions.cs:26-33). WebVitalsCollector
(MMCA.Common.Testing.E2E/Infrastructure/WebVitalsCollector.cs:17) installs PerformanceObserver-based
Core Web Vitals capture (LCP, CLS, FCP, TTFB, INP) as an init script before first paint (InstallAsync,
WebVitalsCollector.cs:37), reads the accumulated values back as a WebVitalsSample (CollectAsync,
WebVitalsCollector.cs:44,73), and writes a citable JSON artifact under WEB_VITALS_OUTPUT_DIR
(WriteArtifactAsync, WebVitalsCollector.cs:60) for CI, [Rubric §23, Front-End Performance] (the source
tags it rubric §12). LCP and CLS are Chromium-only, so on Firefox and WebKit those fields stay 0 and the
observers fail silently rather than throwing (WebVitalsCollector.cs:11-14,19-21). The reusable identity
page objects LoginPage (MMCA.Common.Testing.E2E/PageObjects/LoginPage.cs:6),
RegisterPage (MMCA.Common.Testing.E2E/PageObjects/RegisterPage.cs:6), and
ProfilePage (MMCA.Common.Testing.E2E/PageObjects/ProfilePage.cs:6) wrap the framework's
real auth surfaces with role- and label-based locators (LoginPage.cs:12-18) and route their own fills
through the anti-race helper (LoginPage.cs:31-32, invoked at :25-26); downstream apps add their own family, for example the
MMCA.ADC.E2E.Tests page objects for events, sessions, speakers, rooms, questions, and feedback.
The Gallery harness
Component and E2E coverage of MMCA.Common's own UI needs a page to render, but the framework is not a
runnable app. MMCA.Common.UI.Gallery is a deliberately backend-less Blazor host that renders the real
MMCA.Common.UI auth pages (/login, /register), the shared notification pages, and a primitives
showcase (/components), so a real-browser axe scan can run inside MMCA.Common's own CI
(MMCA.Common.UI.Gallery/GalleryHost.cs:15-20). It is kept outside MMCA.Common.slnx (together with
MMCA.Common.UI.E2E.Tests) so the unit-test run stays fast; the CI ui-e2e job builds both by csproj
path and scans the gallery. The host runs without a backend by registering stubs before AddUIShared so
its TryAdd* registrations defer to them (GalleryHost.cs:55-63): NoOpAuthUIService,
NullTokenStorageService
(MMCA.Common.UI.Gallery/Stubs/NullTokenStorageService.cs:10), NullTokenRefresher
(MMCA.Common.UI.Gallery/Stubs/NullTokenRefresher.cs:9), and
GalleryAuthenticationStateProvider
(MMCA.Common.UI.Gallery/Stubs/GalleryAuthenticationStateProvider.cs:16), which mirrors the request's
authentication in both render phases. Because the notification pages carry a real [Authorize] that
MapRazorComponents surfaces as endpoint metadata, the gallery also needs a genuine authentication
scheme: GalleryFakeAuthenticationHandler
(MMCA.Common.UI.Gallery/Stubs/GalleryFakeAuthenticationHandler.cs:19) authenticates only requests
carrying the gallery_auth=1 cookie (GalleryFakeAuthenticationHandler.cs:26,30), so the guarded pages
are scanned signed in while /login, /register, and /components are scanned in their deliberate
anonymous state (GalleryFakeAuthenticationHandler.cs:8-17).
Contract and pipeline bases
The last family pins guarantees that live in the composition of the stack rather than in any one type.
SecurityHeadersTestsBase (MMCA.Common.Testing/SecurityHeadersTestsBase.cs:16,
[Rubric §11, Security] and [Rubric §26, Front-End Security]) probes an always-responding endpoint
(ProbePath, default /alive, SecurityHeadersTestsBase.cs:19) and asserts the hardened header set:
X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy containing geolocation=(), a
Content-Security-Policy containing frame-ancestors 'none', and, in the Production environment, HSTS
(SecurityHeadersTestsBase.cs:30-36). Its siblings
OpenApiContractTestsBase<TFixture>
(MMCA.Common.Testing/OpenApiContractTestsBase.cs:21),
ProblemDetailsContractTestsBase<TFixture>
(MMCA.Common.Testing/ProblemDetailsContractTestsBase.cs:21), and
ServiceInfoVersioningContractTestsBase<TFixture>
(MMCA.Common.Testing/ServiceInfoVersioningContractTestsBase.cs:19) all subclass
IntegrationTestBase<TFixture> and pin the corresponding API
contracts, [Rubric §9, API & Contract Design].
Two bases guard the CQRS pipeline itself.
DecoratorPipelineOrderTestsBase<TCommand, TCommandResult, TQuery, TQueryResult>
(MMCA.Common.Testing/DecoratorPipelineOrderTestsBase.cs:36) is the opt-in fitness function for ADR-014:
it builds a real ServiceCollection through the repo's own registration sequence, resolves the decorated
handlers, unwraps each decorator's private inner-handler field by reflection, and asserts the runtime
nesting is exactly FeatureGate, Logging, Caching, Validating, Transactional, handler for commands and
FeatureGate, Logging, Caching, handler for queries (DecoratorPipelineOrderTestsBase.cs:9-30). Because
Scrutor's TryDecorate applies decorators in reverse registration order, an innocent-looking reorder of
the AddApplicationDecorators() lines silently changes runtime behavior, and this base turns that into a
test failure (see group 5). HandlerTestBase<THandler>
(MMCA.Common.Testing/HandlerTestBase.cs:38) is the fast unit-tier counterpart for exercising a single
handler without a host.
The takeaway for a new engineer: pick the tier that matches what you are proving (a fast unit test for
domain logic, bUnit for a component, an integration fixture for a full request path, an E2E page object
for a browser flow, a *TestsBase subclass for an architectural invariant), and the reusable base you
need is already in one of the four MMCA.Common.Testing.* packages. Every remaining concrete test class
is cataloged by project in the companion per-project test rollup for this chapter.
ObservabilityConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/ObservabilityConventionTests.cs:17· Level 0 · class (sealed partial)
- What it is: an SLO alert-to-runbook pairing gate. It reads the Azure alerts that
infra/main.bicepprovisions and asserts that every one keeps a matching, severity-correct triage section ininfra/OPERATIONS.md, so an alert cannot be added, renamed, or re-tiered without its runbook moving in the same change. - Depends on: BCL only (
System.Globalization,System.Text.RegularExpressions,System.Reflectionfor the executing assembly). Bothinfra/main.bicepandinfra/OPERATIONS.mdare embedded as manifest resources (ObservabilityConventionTests.cs:20-21, wired in the csproj), so the gate reads them without touching the filesystem. - Concept introduced, the architecture fitness function. A fitness function is a plain unit test whose
subject is not application logic but a rule the architecture must keep obeying, so a governance decision
becomes an executable, CI-enforced guard instead of a review-time convention.
[Rubric §13, Observability & Operability]assesses whether the running system can be operated: alerts that page a human are worthless without a runbook, and this test pins the alert-runbook pairing at build time.[Rubric §34, Architecture Governance & Documentation]applies too, this is documentation kept honest by code rather than by discipline. - Walkthrough
- Constants (
ObservabilityConventionTests.cs:19-22):MinimumAlertSpecs = 3(the non-vacuous floor), the two embedded-resource logical names, and the-alert-infix that ties a bicep key to a runbook heading. SloAlertSpecs_AreDiscovered_GateIsNotVacuous(:25-32): asserts the parser finds at least three specs, so a drifted parse anchor fails loudly rather than passing an empty gate.EveryProvisionedSloAlert_HasASeverityCorrectRunbookSection(:35-60): for each discovered(key, severity)it finds the### ...-alert-{key}heading and checks the heading carries the matching(sev N)tag (:51-55).EveryRunbookAlertSection_MapsToAProvisionedAlert(:63-74): the reverse direction, an orphan runbook section whose alert no longer exists fails the build.DiscoverAlertSpecs(:76-97): slices the bicep betweenvar sloAlertSpecsandresource sloAlerts(:80-85), then runs the source-generatedAlertKeyRegex/AlertSeverityRegexover that block and asserts the key and severity counts match (:86-88).- Source-generated regexes (
:110-117):[GeneratedRegex]partial properties with a 2 second match timeout, the compile-time-safe way to parse the two infra files.
- Constants (
- Why it's built this way: reading the real embedded infra files (not a hand-maintained list) means the gate tracks whatever is actually deployed, and the minimum-spec floor is what keeps a parser regression from silently disabling the whole guard.
- Where it's used: run as part of the ADC architecture-test tier in
CI.slnf; no database needed. - Caveats / not-in-source: the test asserts the pairing between the bicep spec and the runbook markdown; whether Azure actually raised an alert at runtime is outside its scope.
BrandColorTokenTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/BrandColorTokenTests.cs:12· Level 1 · class (sealed)
- What it is: a brand-token drift guard. It asserts the conference landing page's shared scoped
stylesheet sources the primary brand color from the
var(--mmca-primary)CSS custom property rather than re-hardcoding the hex value. - Depends on: the shared
BrandColorTokenTestsBase(extended,BrandColorTokenTests.cs:12). The base owns the scan and assertion; this subclass only names the embedded stylesheet. - Concept: a design-system fitness test.
[Rubric §20, Design System & Theming]assesses whether the UI derives from tokens rather than scattered literals; the base guards the C#-to-CSS token definition in MMCA.Common, and this subclass guards the ADC consumer of that token (BrandColorTokenTests.cs:3-10). - Walkthrough
EmbeddedCssLogicalNames(:14-17): the single override, listingADCHome.Shared.razor.css, the shared scoped stylesheet rendered by both UI hosts, embedded into this assembly so a re-introduced literal fails the build.
- Why it's built this way: a subclass supplies only the file names because the drift rule itself lives once in the base and is reused across every repo that consumes the brand token.
- Where it's used: ADC architecture-test tier; no database needed.
TranslationCompletenessTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/TranslationCompletenessTests.cs:12· Level 5 · class (sealed)
- What it is: an internationalization completeness gate. It asserts every base
*.resxunderSource/has a complete, non-empty Spanish.es.resxsibling, so adding an English key without its Spanish counterpart fails CI instead of silently shipping a half-translated UI. - Depends on: the shared
LocalizationResourceTestsBase(extended,TranslationCompletenessTests.cs:12). - Concept: an i18n fitness test.
[Rubric §27, i18n]assesses whether localization is complete and enforced rather than aspirational; this gate makes translation parity a build-time invariant, backed by ADR-027 (which supersedes the single-locale ADR-011,TranslationCompletenessTests.cs:4). - Walkthrough
RequiredCultures(:14):["es"], the one culture that must exist for every base resource set.MinimumBaseResources(:16):40, the non-vacuous floor, ADC has 40-plus localized resource sets across the three module UIs, the UI hosts' landing page, nav-item descriptors, and API error-resource sets, so a near-zero discovery count means the scan path drifted.
- Why it's built this way: pinning both the required culture and the discovery floor turns "we support Spanish" into an executable, non-vacuous guarantee.
- Where it's used: ADC architecture-test tier; no database needed.
DecoratorPipelineOrderTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/DecoratorPipelineOrderTests.cs:25· Level 8 · class (sealed)
- What it is: a fitness function for the CQRS decorator pipeline. It builds the Identity module's real registration sequence and asserts the resolved runtime nesting is exactly the expected order for both a command and a query, so a re-ordered or dropped decorator is caught at build time rather than surfacing as a subtle runtime bug.
- Depends on: the shared
DecoratorPipelineOrderTestsBase<TCommand, TCommandResult, TQuery, TQueryResult>(extended,DecoratorPipelineOrderTests.cs:25-26), instantiated over the real Identity pairChangePreferencesCommand/GetUserPreferencesQueryreturningResult. It usesMoqfor the decorator constructor dependencies and the framework DI extensionsAddApplication/ScanModuleApplicationServices/AddApplicationDecorators(DecoratorPipelineOrderTests.cs:1-12). - Concept: the decorator pipeline is introduced in
primer §2; this test pins its ordering.
[Rubric §6, CQRS & Event-Driven]assesses whether the command/query pipeline is coherent and composable; the ordering is load-bearing because Scrutor'sTryDecoratecan only wrap handlers already registered, so decorators must be added last (DecoratorPipelineOrderTests.cs:37-41). - Walkthrough
ConfigureServices(:28-42): registers test doubles for each decorator constructor dependency (IFeatureManager,ICorrelationContext,ICacheService,IUnitOfWork,ILogger<>viaNullLogger<>,:31-35), then reproduces the genuine registration sequence: handler scan first viaScanModuleApplicationServices<...ClassReference>, decorators last viaAddApplicationDecorators(:39-41).- The asserted nesting (base-supplied, documented at
:22-23): FeatureGate -> Logging -> Caching -> Validating -> Transactional -> Handler for commands, and FeatureGate -> Logging -> Caching -> Handler for queries (queries skip validation and the transaction).
- Why it's built this way: exercising the module's real registration order against real command/query
types (rather than asserting a static list) proves the pipeline the production host actually resolves,
making the ADR-014 ordering executable (
DecoratorPipelineOrderTests.cs:17). - Where it's used: ADC architecture-test tier; no database needed.
- Caveats / not-in-source: this is described as an opt-in v1.120.0 fitness function
(
DecoratorPipelineOrderTests.cs:17), so it exercises one representative pair, not every handler.
AdcArchitectureMap
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/AdcArchitectureMap.cs:8· Level 9 · class (sealed, internal)
- What it is: the single source of truth that tells every convention test which assembly is which layer
of which module. It maps the MMCA.Common framework layers plus the Identity, Conference, and Engagement
modules, pinning each
(module, layer)to one anchor type so the fitness functions can reason about dependencies structurally. - Depends on: the shared
ArchitectureMapBase(extended,AdcArchitectureMap.cs:8), whoseFramework(...)/Module(...)helpers andLayerRef/Layertypes it composes, andSystem.Reflection'sAssembly.Load. Every anchor type it references (for exampleResult,BaseEntity<TIdentifierType>,ApiControllerBase,User,Event,UserSessionBookmark) is a real type from the layer it pins. - Concept introduced, the architecture map as the linchpin of every convention test. Rather than each
test re-discovering assemblies, one map declares the topology and every
*ConventionTestsBaseconsumes it through theIArchitectureMapabstraction.[Rubric §3, Clean Architecture]assesses whether layer boundaries are explicit and enforced; this map is the machine-readable statement of those boundaries.[Rubric §7, Microservices Readiness]applies because the map enumerates each module's six layers (Domain, Application, Infrastructure, Shared, Api, Ui), the same boundaries the modules extract along. - Walkthrough
RepoToken(AdcArchitectureMap.cs:10):"MMCA.ADC", used by the base to locate the repo root ({RepoToken}.slnx).DefineLayers(:12-44): a collection expression ofLayerRefs. The five framework layers are pinned by a concrete MMCA.Common type's assembly (:15-19); each module then pins its Domain / Shared / Api / Ui by an anchor type and its Application / Infrastructure byAssembly.Loadby name, because those assemblies lack a convenient public anchor (:22-43).
- Why it's built this way: pinning by a real type means the map cannot reference an assembly that does
not exist, and centralizing the topology in one class is what lets ~25 convention tests each be a
two-line subclass that only supplies
Map. - Where it's used: instantiated as the
Mapproperty of every convention-test subclass in this unit (for exampleLayerDependencyTests,ModuleIsolationTests,MicroserviceExtractionTests).
ConcurrencyConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/ConcurrencyConventionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
ConcurrencyConventionTestsBase, which enforces the framework's optimistic-concurrency conventions across the mapped assemblies. - Depends on: the base (extended,
ConcurrencyConventionTests.cs:3) andAdcArchitectureMap. - Concept: reuses the concurrency convention taught by the base.
[Rubric §8, Data Architecture]is the relevant lens, concurrency handling is a data-integrity concern the base pins. - Walkthrough
Map(ConcurrencyConventionTests.cs:5): the only override,new AdcArchitectureMap().
- Where it's used: ADC architecture-test tier; no database needed.
ConstructorDependencyCountTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/ConstructorDependencyCountTests.cs:11· Level 10 · class (sealed)
- What it is: a single-responsibility ceiling guard. It caps how many constructor dependencies an Application-layer service may take, so a class silently accreting collaborators fails CI.
- Depends on: the shared
ConstructorDependencyCountTestsBase(extended,ConstructorDependencyCountTests.cs:11) andAdcArchitectureMap. - Concept: a SOLID fitness test.
[Rubric §1, SOLID]assesses cohesion and single responsibility; a high constructor-dependency count is the classic smell this guard makes visible. - Walkthrough
Map(:13) andMaxConstructorDependencies(:15): the ceiling is8. The XML doc records that the high-water mark isAuthenticationServiceat 8, a cohesive auth facade whose 8th dependency (IExternalLoginEmailVerifier) was a conscious raise for the OAuth verified-email gate, not an artificial bundle (ConstructorDependencyCountTests.cs:5-9).
- Why it's built this way: the ceiling is meant to be raised consciously (with the rationale recorded) rather than dodged by hiding dependencies behind a bundle.
- Where it's used: ADC architecture-test tier; no database needed.
ControllerConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/ControllerConventionTests.cs:3· Level 10 · class (sealed)
- What it is: an API-consistency guard. It asserts every controller routes through
ApiControllerBase(and itsResult-to-HTTP mapping), with a small, justified exemption list. - Depends on: the shared
ControllerConventionTestsBase(extended,ControllerConventionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §9, API & Contract Design], uniform error shaping is a contract-design property, and this guard keeps every domain controller on the shared mapping. - Walkthrough
ControllersExemptFromApiControllerBase(:11-15): two deliberate exemptions,OAuthController(which drives the OAuth2 redirect/challenge/cookie flow) andServiceInfoController(an anonymous version-discovery diagnostic), neither of which returns domainResults, so both extendControllerBasedirectly (ControllerConventionTests.cs:7-10).
- Where it's used: ADC architecture-test tier; no database needed.
DataResidencyTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/DataResidencyTests.cs:12· Level 10 · class (sealed)
- What it is: a compliance-drift guard. It asserts the data-residency statement published in
PRIVACY.mdmatches the Azure region where personal data is actually provisioned, so a stale privacy claim cannot outlive a region change. - Depends on: the shared
DataResidencyTestsBase(extended,DataResidencyTests.cs:12) andAdcArchitectureMap, plusSystem.IOto read the deploy workflow. - Concept:
[Rubric §30, Compliance/Privacy/Data Governance]assesses whether privacy statements track reality; account data and session bookmarks live in Azure SQL, whose region is the source of truth here (DataResidencyTests.cs:6-10). - Walkthrough
ForbiddenResidencyClaims(:16): blocks the stale"central United States"statement that once contradicted the deployed region.ExtractDeployedRegion(:20-31): the app-specific hook, reads.github/workflows/deploy.ymland parses theSQL_LOCATION_OVERRIDE:-<region>default (:24-30), asserting the marker exists so the parse cannot silently pass.
- Why it's built this way: deriving the deployed region from the actual deploy workflow (not a hand-kept
constant) means the privacy claim is checked against the region CI would really provision, which for ADC
differs from the Container Apps region because the QiMata sponsorship forces SQL into another region
(
DataResidencyTests.cs:6-10). - Where it's used: ADC architecture-test tier; no database needed.
DomainPurityTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/DomainPurityTests.cs:3· Level 10 · class (sealed)
- What it is: a Clean Architecture guard that keeps infrastructure concerns out of the Domain and Shared layers, extended here to also forbid the broker client.
- Depends on: the shared
DomainPurityTestsBase(extended,DomainPurityTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §3, Clean Architecture]and[Rubric §4, DDD], a pure domain has no outward dependencies, so the guard keeps the dependency arrows pointing inward. - Walkthrough
Map(:5) andExtraForbiddenDomainDependencies(:9): adds"RabbitMQ"to the framework-default forbidden list, because ADC runs on a broker (RabbitMQ locally, Azure Service Bus in production) and its client must never leak into Domain or Shared (DomainPurityTests.cs:7-8).
- Where it's used: ADC architecture-test tier; no database needed.
EntityConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/EntityConventionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
EntityConventionTestsBase, which enforces the framework's entity conventions (factory methods, encapsulated state, audit/soft-delete contracts) across the mapped Domain assemblies. - Depends on: the base (extended,
EntityConventionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §4, DDD], the entity conventions are the DDD building-block rules taught in group-02; the base pins them and this subclass applies them to ADC. - Walkthrough
Map(EntityConventionTests.cs:5): the only override,new AdcArchitectureMap().
- Where it's used: ADC architecture-test tier; no database needed.
EventConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/EventConventionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
EventConventionTestsBase, which enforces the framework's domain-event conventions (naming, immutability, past-tense records) across the mapped assemblies. - Depends on: the base (extended,
EventConventionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §6, CQRS & Event-Driven], domain events are the event half of the pattern; the base pins their shape. - Walkthrough
Map(EventConventionTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
FormsConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/FormsConventionTests.cs:14· Level 10 · class (sealed)
- What it is: a UX-safety convention guard. It asserts the six Conference create forms each keep their unsaved-changes guard, dirty tracking, a validated form with required-field errors, and a per-form error summary, and it adds a bespoke check for the Identity Profile form.
- Depends on: the shared
FormsConventionTestsBase(extended,FormsConventionTests.cs:14),AdcArchitectureMap,AwesomeAssertions, andSystem.IO. - Concept:
[Rubric §24, Forms/Validation/UX Safety]assesses whether forms protect the user against lost work and unclear validation; this guard pins those markers so a form cannot silently drop them (FormsConventionTests.cs:3-13). - Walkthrough
MinimumCreateForms(:18):6, the Event/Session/Room/Question/Speaker/ConferenceCategory create forms.RequiredMarkers(:24-29): extends the base set with the per-formMudAlerterror summary and the localizedValidation.CorrectFollowingheading.ProfileForm_KeepsErrorSummaryAndPasswordValidation(:31-62): a bespoke[Fact]for the Identity Profile form, which is a single-section password/delete form with no navigate-away step and so does not match the base's*Create.razorglob (:3-13). It locatesProfile.razorvia the map's repo root (:34-38), asserts the error-summary and password-validation markers are present (:43-56), and counts at least threeRequired="true"and threeRequiredErrormarkers for the current/new/confirm password fields (:58-61).
- Why it's built this way: the shared markers cover the standard create forms, and the extra
[Fact]covers the one form whose shape differs, so no UX-safety guard is left un-verified. - Where it's used: ADC architecture-test tier; no database needed.
FrameworkVersionConsistencyTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/FrameworkVersionConsistencyTests.cs:9· Level 10 · class (sealed)
- What it is: an evolvability/drift gate. It asserts every
MMCA.Common.*package pinned inDirectory.Packages.propsshares one version, so a partial framework sweep is caught at CI time instead of producing a subtly mismatched surface at runtime. - Depends on: the shared
FrameworkVersionConsistencyTestsBase(extended,FrameworkVersionConsistencyTests.cs:9) andAdcArchitectureMap. - Concept:
[Rubric §16, Maintainability]and[Rubric §32, Dependency & Supply-Chain], the lockstep-versioning rule (ADR-016) is made executable here, matching the no-phased-rollout policy (FrameworkVersionConsistencyTests.cs:3-7). - Walkthrough
Map(:11): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
HandlerConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/HandlerConventionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
HandlerConventionTestsBase, which enforces the framework's CQRS handler conventions (naming, one-handler-per-request, layer placement). - Depends on: the base (extended,
HandlerConventionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §6, CQRS & Event-Driven], handler shape is pinned once in the base. - Walkthrough
Map(HandlerConventionTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
HandlerResultConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/HandlerResultConventionTests.cs:8· Level 10 · class (sealed)
- What it is: a fitness function asserting every ADC command/query handler returns
ResultorResult<T>, turning the decorator pipeline's runtimeResultFailureFactoryconstraint into a build-time gate. - Depends on: the shared
HandlerResultConventionTestsBase(extended,HandlerResultConventionTests.cs:8) andAdcArchitectureMap. - Concept:
[Rubric §6, CQRS & Event-Driven], the Result pattern (taught in group-01) is the universal handler return; this gate proves the pipeline's failure-mapping decorator can wrap every handler (HandlerResultConventionTests.cs:3-7). - Walkthrough
Map(:10): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
- Caveats / not-in-source: labelled an opt-in v1.120.0 fitness function
(
HandlerResultConventionTests.cs:3-6).
ImmutabilityTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/ImmutabilityTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
ImmutabilityTestsBase, which enforces the framework's immutability conventions (required/initmembers, no public setters on value objects and messages). - Depends on: the base (extended,
ImmutabilityTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §4, DDD]and[Rubric §15, Best Practices & Code Quality], immutability of value objects and DTOs is pinned by the base. - Walkthrough
Map(ImmutabilityTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
IntegrationEventContractTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/IntegrationEventContractTests.cs:3· Level 10 · class (sealed)
- What it is: a frozen-wire-contract guard for the cross-service async API. It pins the exact shape of every integration event that crosses the outbox-to-broker boundary, so evolving one is a deliberate, reviewed change rather than an accidental break of a downstream consumer.
- Depends on: the shared
IntegrationEventContractTestsBase(extended,IntegrationEventContractTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §7, Microservices Readiness]and[Rubric §9, API & Contract Design], the integration event is the wire contract between services, so a snapshot test protects consumers exactly as the outbox pattern (ADR-003) protects delivery. - Walkthrough
ExpectedContract(:9-14): the pinned list,SpeakerLinkedToUserandSpeakerUnlinkedFromUser({ SpeakerId:Guid, UserId:Int32 }) andUserRegistered({ Email, FirstName, LastName, Role:String, UserId:Int32 }), each with its full property signature. The comment records the intent: update deliberately, versioning or coordinating the consumer rollout in the same commit (IntegrationEventContractTests.cs:7-8).
- Why it's built this way: freezing the property signatures (not just the type names) means a renamed or retyped field breaks CI, forcing the wire evolution to be conscious.
- Where it's used: ADC architecture-test tier; no database needed.
LayerDependencyTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/LayerDependencyTests.cs:3· Level 10 · class (sealed)
- What it is: the core Clean Architecture guard. It asserts each layer only depends on the layers it is
allowed to, using the
AdcArchitectureMaptopology. - Depends on: the shared
LayerDependencyTestsBase(extended,LayerDependencyTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §3, Clean Architecture], this is the canonical dependency-rule fitness function (Domain depends on nothing outward, Infrastructure and Api depend inward), enforced from the map. - Walkthrough
Map(LayerDependencyTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
LocalizedTextConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/LocalizedTextConventionTests.cs:14· Level 10 · class (sealed)
- What it is: a localized-text convention gate. It asserts user-visible literals are not hard-coded in
.razor/.razor.csunderSource/, so snackbar messages, page titles, breadcrumb labels, and nav-item titles resolve throughIStringLocalizerresources instead. - Depends on: the shared
LocalizedTextConventionTestsBase(extended,LocalizedTextConventionTests.cs:14) andAdcArchitectureMap. - Concept:
[Rubric §27, i18n], this is the source-code companion toTranslationCompletenessTests: that one checks resx parity, this one checks that visible strings actually route through resources (ADR-027,LocalizedTextConventionTests.cs:3-13). Deliberate literals (the conference brand name, content data) are exempted per line with ani18n: allowmarker. - Walkthrough
MinimumScannedFiles(:18):60, the non-vacuous floor, ADC has about 77 razor files, so a near-zero discovery count means the scan path drifted.
- Where it's used: ADC architecture-test tier; no database needed.
MicroserviceExtractionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/MicroserviceExtractionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
MicroserviceExtractionTestsBase, which asserts each module stays independently extractable (no cross-module domain references, communication only through interfaces). - Depends on: the base (extended,
MicroserviceExtractionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §7, Microservices Readiness], this guard keeps the modular-monolith-to-services path (ADRs 007/008) real, which matters especially for ADC because its modules are already extracted into separate service hosts. - Walkthrough
Map(MicroserviceExtractionTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
ModuleIsolationTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/ModuleIsolationTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
ModuleIsolationTestsBase, which asserts one module's internals are not referenced by another module directly. - Depends on: the base (extended,
ModuleIsolationTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §7, Microservices Readiness], module isolation is the companion rule to extraction, taught alongside the module system in group-14. - Walkthrough
Map(ModuleIsolationTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
NamingConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/NamingConventionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
NamingConventionTestsBase, which enforces the framework's type-naming conventions (commands, queries, handlers, DTOs, specifications). - Depends on: the base (extended,
NamingConventionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §15, Best Practices & Code Quality], consistent naming is what lets the other convention tests target types by suffix. - Walkthrough
Map(NamingConventionTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
PiiConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/PiiConventionTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
PiiConventionTestsBase, which asserts personal-data fields carry the framework'sPiiAttributeso erasure and export can find them. - Depends on: the base (extended,
PiiConventionTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §30, Compliance/Privacy/Data Governance], thePiiAttribute/ soft-delete-vs-erasure model (ADR-005) is pinned by the base so no PII field goes unmarked. - Walkthrough
Map(PiiConventionTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
RawQueryableConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/RawQueryableConventionTests.cs:11· Level 10 · class (sealed)
- What it is: a fitness function that forbids Application-layer code from using the repository's raw
IQueryablesurfaces (Table/TableNoTracking*), because raw-queryable handlers are EF-coupled and cannot move behind a gRPC boundary. An allowlist pins ADC's existing deliberate uses so only new code is blocked. - Depends on: the shared
RawQueryableConventionTestsBase(extended,RawQueryableConventionTests.cs:11),AdcArchitectureMap, andSystem.IO. - Concept:
[Rubric §7, Microservices Readiness], keeping handlers off raw EF surfaces is what lets a module later be served over gRPC; the allowlist is an adoption ratchet, meant to shrink over time and never grow without scrutiny (RawQueryableConventionTests.cs:3-9). - Walkthrough
ApplicationSourceDirectories(:21-30): overrides the base enumeration to also include the thin Notification module's Application project (which the map does not declare), so it falls under the same rule (:15-20).AllowedFiles(:33-52): the pinned exemptions, Engagement live-layer hot-path aggregations, bookmark count/projection queries, the Identity user-list paging projection, and the Notification GDPR export join, each annotated with why it is intra-module and safe.
- Why it's built this way: an allowlist rather than a blanket ban lets the rule land against an existing codebase while still blocking new raw-queryable handlers.
- Where it's used: ADC architecture-test tier; no database needed.
- Caveats / not-in-source: labelled an opt-in v1.120.0 fitness function
(
RawQueryableConventionTests.cs:3-6).
SharedLayerTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/SharedLayerTests.cs:3· Level 10 · class (sealed)
- What it is: a thin ADC binding of the shared
SharedLayerTestsBase, which asserts the Shared layer holds only contracts (DTOs, integration events, interfaces) and no implementation or infrastructure. - Depends on: the base (extended,
SharedLayerTests.cs:3) andAdcArchitectureMap. - Concept:
[Rubric §3, Clean Architecture], the Shared layer is the module's public contract surface; the base keeps it thin so cross-module references stay contract-only. - Walkthrough
Map(SharedLayerTests.cs:5): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
SliceCohesionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/SliceCohesionTests.cs:8· Level 10 · class (sealed)
- What it is: a vertical-slice cohesion guard. It asserts every module's
Application/{Aggregate}/UseCases/{Operation}/slice keeps its command or query, its handler, and its validator in one namespace, so a handler cannot be stranded from its contract. - Depends on: the shared
SliceCohesionTestsBase(extended,SliceCohesionTests.cs:8) andAdcArchitectureMap. - Concept:
[Rubric §5, Vertical Slice], slice cohesion is the physical expression of the organize-by-feature principle; the base pins the folder-to-namespace shape (SliceCohesionTests.cs:3-7). - Walkthrough
Map(:10): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
SpecificationConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/SpecificationConventionTests.cs:8· Level 10 · class (sealed)
- What it is: a cross-source specification guard. It asserts no specification filters by navigating to
another entity (which would not translate if that entity later moved to a different data source),
steering such filters onto
CrossSourceSpecificationinstead. - Depends on: the shared
SpecificationConventionTestsBase(extended,SpecificationConventionTests.cs:8) andAdcArchitectureMap. - Concept:
[Rubric §8, Data Architecture], this keeps the polyglot-persistence extension point (ADR-018) usable. The comment records that ADC's Session-to-Cosmos / Room-to-SQLite trial was reverted but the framework extension points were kept, so this guard stays on as a forward safeguard even though every entity currently routes to SQL Server (SpecificationConventionTests.cs:3-7). - Walkthrough
Map(:10): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
StateManagementConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/StateManagementConventionTests.cs:9· Level 10 · class (sealed)
- What it is: a state-management convention guard. It asserts the module UI assemblies carry no mutable static state (a static member is shared across every Blazor Server circuit) and that stateful UI services stay scoped, so the per-circuit state model is CI-enforced.
- Depends on: the shared
StateManagementConventionTestsBase(extended,StateManagementConventionTests.cs:9) andAdcArchitectureMap. - Concept:
[Rubric §19, State Management], in Blazor Server a static field leaks state across users, so the base pins the scoped-per-circuit model (StateManagementConventionTests.cs:3-8). - Walkthrough
Map(:11): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
UIArchitectureConventionTests
MMCA.ADC.Architecture.Tests ·
MMCA.ADC.Architecture.Tests·MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/UIArchitectureConventionTests.cs:10· Level 10 · class (sealed)
- What it is: a UI-architecture convention guard. It asserts every code-behind under
Source/stays within the 400-line convention cap and inline@codeblocks stay small, so the container/presentational split is enforced by CI rather than by review. - Depends on: the shared
UIArchitectureConventionTestsBase(extended,UIArchitectureConventionTests.cs:10) andAdcArchitectureMap. - Concept:
[Rubric §18, UI Architecture], the size cap is a proxy for keeping presentation thin and logic in services; the comment notes this gate subsumed TD-13 (the oversized Conference dashboards were split to conform when it landed,UIArchitectureConventionTests.cs:3-9). - Walkthrough
Map(:12): the only override.
- Where it's used: ADC architecture-test tier; no database needed.
EntityBuilderBase<TBuilder, TEntity>
MMCA.Common.Testing ·
MMCA.Common.Testing.Builders·MMCA.Common/Source/Hosting/MMCA.Common.Testing/Builders/EntityBuilderBase.cs:9· Level 0 · class (abstract)
- What it is: the tiny root of the framework's fluent test-data builders. A subclass fixes sensible
defaults for one entity type so a test only has to state the properties it actually cares about, then
calls
Build()to materialize the entity through its real domain factory. - Depends on: nothing first-party. Two type parameters and one abstract method, no BCL surface
beyond
object. - Concept introduced, the Test Data Builder + self-referencing generic (CRTP).
[Rubric §14, Testability]assesses how easily the code can be exercised in isolation; a builder base is a textbook §14 affordance, it removes the copy-pasted setup that otherwise bloats every arrange step. The signatureEntityBuilderBase<TBuilder, TEntity> where TBuilder : EntityBuilderBase<TBuilder, TEntity>(MMCA.Common/Source/Hosting/MMCA.Common.Testing/Builders/EntityBuilderBase.cs:9-10) is the curiously-recurring template pattern: a concrete builder passes itself asTBuilder, so theWithX(...)methods a subclass adds can return the concrete builder type and keep a fluent chain strongly typed without a cast. - Walkthrough
Build()(MMCA.Common/Source/Hosting/MMCA.Common.Testing/Builders/EntityBuilderBase.cs:17): the single abstract member. The XML doc (MMCA.Common/Source/Hosting/MMCA.Common.Testing/Builders/EntityBuilderBase.cs:12-15) records the contract, the subclass calls the entity'sResult-returning factory and throws if it failed, so a builder never yields a domain object that violated its invariants. The base deliberately owns no state and no defaultWithXhelpers, those live on each concrete builder because defaults are per-entity.
- Why it's built this way: keeping the base to one abstract method means it adds zero coupling and
zero opinions beyond "a builder produces a
TEntity". The CRTP is the only structural rule it enforces, and it exists purely so fluent chaining stays type-safe down in the subclasses. - Where it's used: subclassed in each downstream test project's builders (per module), consumed by the integration and domain tests that need pre-populated aggregates.
- Caveats / not-in-source: the "throws on failure" and "sensible defaults" behavior is documented on the base but implemented only in subclasses, which live outside this unit.
FeatureManagementTestExtensions
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/FeatureManagementTestExtensions.cs:10· Level 0 · class (static)
- What it is: a one-method helper that lets an integration-test host force feature-flag values,
overriding whatever
appsettings.jsonwould otherwise resolve, so a test can pin a flag on or off and assert both branches of a feature-gated command or query. - Depends on: BCL / NuGet only,
IServiceCollectionandIConfiguration(Microsoft.Extensions.*) plusAddFeatureManagementfromMicrosoft.FeatureManagement(MMCA.Common/Source/Hosting/MMCA.Common.Testing/FeatureManagementTestExtensions.cs:1-3). No first-party dependency. - Concept: this is the test-side counterpart to the framework's
FeatureGateCommandDecorator<TCommand, TResult>, the outermost link in the CQRS pipeline (taught in primer §2).[Rubric §14, Testability]again: a gated handler is only meaningfully testable if a test can flip its flag deterministically;[Rubric §10, Cross-Cutting]applies too, feature management is a cross-cutting concern, and this helper keeps its test-time configuration in one reusable place. - Walkthrough
- The whole class body is a single C# preview
extension(IServiceCollection services)block (MMCA.Common/Source/Hosting/MMCA.Common.Testing/FeatureManagementTestExtensions.cs:12), the same extension-member style the framework uses for DI registration, not a classicthis-parameter extension method. ConfigureTestFeatureFlags(Dictionary<string, bool> features)(MMCA.Common/Source/Hosting/MMCA.Common.Testing/FeatureManagementTestExtensions.cs:21-35): projects eachname -> boolpair into an in-memory configuration key under theFeatureManagement:section (MMCA.Common/Source/Hosting/MMCA.Common.Testing/FeatureManagementTestExtensions.cs:24-29), registers thatIConfigurationas a singleton, callsAddFeatureManagementagainst the section (MMCA.Common/Source/Hosting/MMCA.Common.Testing/FeatureManagementTestExtensions.cs:31-32), and returns the collection for chaining.
- The whole class body is a single C# preview
- Why it's built this way: pushing overrides through the real
IConfiguration+AddFeatureManagementpath (rather than mocking anIFeatureManager) means the test exercises the same feature-evaluation code the production host runs, only the source of the flag value changes. - Where it's used: called from a test
WebApplicationFactory'sConfigureServicesin downstream integration-test fixtures that need to toggle a gated slice.
IIntegrationTestFixture
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/IIntegrationTestFixture.cs:8· Level 0 · interface
- What it is: the contract every integration-test fixture implements, the two capabilities a test
base needs from a booted host: hand me an
HttpClient, and reset the database to clean between tests. - Depends on: BCL only (
HttpClient,Task). No first-party dependency, which is what lets it sit at Level 0 and be referenced by the test base above it. - Concept introduced, the test fixture as an abstraction boundary.
[Rubric §14, Testability]: by depending on this interface rather than a concreteWebApplicationFactory, the reusableIntegrationTestBase<TFixture>stays host-agnostic, each downstream app supplies its own concrete fixture with its ownProgram, JWT keys, and data sources. This is the boundary that keeps the shared test scaffolding inMMCA.Common.Testingand the app-specific wiring in each repo. - Walkthrough
CreateClient()(MMCA.Common/Source/Hosting/MMCA.Common.Testing/IIntegrationTestFixture.cs:11): returns anHttpClientconfigured for the in-process test server.ResetDatabaseAsync()(MMCA.Common/Source/Hosting/MMCA.Common.Testing/IIntegrationTestFixture.cs:19): resets the database between tests (the doc names Respawn as the typical mechanism). The doc comment (MMCA.Common/Source/Hosting/MMCA.Common.Testing/IIntegrationTestFixture.cs:13-18) records a load-bearing rule for the database-per-service topology (ADR-006): a host with multiple physical data sources must reset every relational source, and can enumerate them by resolvingIEntityDataSourceRegistry/IDataSourceResolverfrom the host's services.
- Why it's built this way: two members, no state, no host coupling. The interface is deliberately minimal so that the reset strategy (single database vs. multi-source) is the fixture's problem, not the base's.
- Where it's used: implemented by
SqlServerIntegrationTestFixtureBase<TEntryPoint>and by each app's concrete fixtures; consumed as theTFixtureconstraint onIntegrationTestBase<TFixture>and every contract test base in this group.
JwtTokenGenerator
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:29· Level 0 · class (static)
- What it is: a static factory that mints signed JWT bearer tokens for integration tests, so a test can call an authorized endpoint as any role/user without standing up the real login flow. Each downstream project wraps it with role-specific convenience methods (AdminToken, OrganizerToken, ...).
- Depends on: BCL / NuGet only,
System.IdentityModel.Tokens.Jwt,System.Security.Claims,System.Security.Cryptography(RSA), andMicrosoft.IdentityModel.Tokens(MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:1-5). The generated claim layout mirrors the framework'sITokenServiceso downstream auth middleware cannot tell a test token from a real one (MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:97-102). TheuserIdparameter is typedUserIdentifierType(MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:113), the solution-wide identifier alias. - Concept introduced, exercising the real RS256/JWKS path in tests.
[Rubric §11, Security]assesses how authentication and key handling are done; the deliberate choice here is that tests sign with RS256 (SecurityAlgorithms.RsaSha256,MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:130) using an embedded RSA-2048 dev keypair, the same asymmetric algorithm production uses, so integration tests run the identical JWKS/RS256 validation code path (ADR-004 authentication dual-fetch, taught in primer §2) rather than a weaker HMAC shortcut.[Rubric §14, Testability]covers the ergonomics: deterministic tokens with no per-run key generation. - Walkthrough
- Public constants (
MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:32-95):DefaultIssuer(https://localhost:6001, line 32),DefaultKeyId(mmca-test-key, line 40, thekidthe host advertises on its JWKS document), and the pairedDefaultPublicKeyPem(line 48) /DefaultPrivateKeyPem(line 67). The class doc records the wiring contract: test host appsettings setJwt:SigningAlgorithm=RS256,Jwt:RsaPublicKeyPem, andJwks:KeyId(lines 17-19) soRsaJwksProviderpublishes a JWKS entry with the matchingkid. GenerateToken(...)(MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:111-152): imports the PEM private key intoRSAParametersinside ausingso theRSAinstance can be disposed without invalidating the key held bySigningCredentials(lines 120-130), assembles the standard claim set (ClaimTypes.NameIdentifier,user_id,ClaimTypes.Role) plus any caller-supplied extras (lines 132-142), and writes a one-hour token (lines 144-151). Defaulted parameters mean a caller normally passes only audience, user id, and role (lines 112-118).
- Public constants (
- Why it's built this way: the whole point is fidelity, tokens are indistinguishable in shape and signing algorithm from production, so auth middleware, JWKS discovery, and role checks are all under test, not stubbed.
- Where it's used: called by
IntegrationTestBase<TFixture>subclasses viaSetBearerToken(...), wrapped by each app's role-specific token helpers. - Caveats / not-in-source: the class doc
(
MMCA.Common/Source/Hosting/MMCA.Common.Testing/JwtTokenGenerator.cs:21-27) carries an explicit security warning, the embedded keypair is committed to the public git repo and is insecure by design, it exists only to make integration tests deterministic. Production keys are provisioned via user-secrets / Azure Key Vault perJwtSettings.RsaPrivateKeyPemand must never be this keypair.
SecurityHeadersTestsBase
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/SecurityHeadersTestsBase.cs:16· Level 0 · class (abstract)
- What it is: a one-test fitness base that asserts a booted host emits the hardened set of security response headers on every response, so a later pipeline refactor cannot silently drop them. Authored once, re-run as a thin subclass per host under test.
- Depends on:
AwesomeAssertionsandXunit(MMCA.Common/Source/Hosting/MMCA.Common.Testing/SecurityHeadersTestsBase.cs:1-2). It does not extend the integration base, it only needs anHttpClient, so it takes one via an abstract factory rather than inheriting the SQL fixture machinery. - Concept: a security fitness test.
[Rubric §11, Security]assesses defense-in-depth at the HTTP edge; this test pins the exact header values the sharedAddCommonSecurityHeaders/UseCommonSecurityHeadersmiddleware (seeSecurityHeadersMiddleware) is expected to emit.[Rubric §14, Testability]covers the reusable-base shape. - Walkthrough
ProbePath(MMCA.Common/Source/Hosting/MMCA.Common.Testing/SecurityHeadersTestsBase.cs:19): overridable, defaults to/alivebecause the liveness endpoint always answers independent of any backend being reachable, so the header check is never flaky for the wrong reason (rationale in the class doc, lines 12-14).AliveResponse_CarriesHardenedSecurityHeaders(MMCA.Common/Source/Hosting/MMCA.Common.Testing/SecurityHeadersTestsBase.cs:21-36): the single[Fact]. It GETsProbePath(lines 26-27, threadingTestContext.Current.CancellationToken) and assertsX-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: strict-origin-when-cross-origin, aPermissions-Policycontaininggeolocation=(), aContent-Security-Policycontainingframe-ancestors 'none', and (because the host under test boots in the Production environment) an HSTSStrict-Transport-Securityheader with amax-age=(lines 29-35).CreateClient()(MMCA.Common/Source/Hosting/MMCA.Common.Testing/SecurityHeadersTestsBase.cs:42): abstract, the subclass supplies it from itsWebApplicationFactoryclass fixture.Header(...)(MMCA.Common/Source/Hosting/MMCA.Common.Testing/SecurityHeadersTestsBase.cs:44-45) is the private helper that joins a header's values or returns null when the header is absent.
- Why it's built this way: pinning literal header values (not just presence) turns "we harden
responses" into an executable, per-host guarantee, and probing
/alivekeeps the test independent of application state. Booting the subclass fixture in Production is what makes the HSTS assertion valid. - Where it's used: subclassed by each host's security-header fitness test (typically the gateway).
DecoratorPipelineOrderTestsBase<TCommand, TCommandResult, TQuery, TQueryResult>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/DecoratorPipelineOrderTestsBase.cs:36· Level 1 · class (abstract)
- What it is: an opt-in fitness base that builds a real
ServiceCollectionthrough a repo's own registration sequence, resolves the decorated command and query handlers out of the built provider, and asserts the runtime object graph nests the decorators in exactly the ADR-014 order. - Depends on:
ICommandHandler<in TCommand, TResult>andIQueryHandler<in TQuery, TResult>fromMMCA.Common.Application.UseCases(MMCA.Common/Source/Hosting/MMCA.Common.Testing/DecoratorPipelineOrderTestsBase.cs:4), plusSystem.Reflection,Microsoft.Extensions.DependencyInjection,AwesomeAssertions, andXunit(lines 1-5). - Concept introduced, verifying a decorator chain by unwrapping the constructed graph. The
decorator pipeline itself is taught in
group-05; what is new here is
how you prove it. Scrutor's
TryDecorateapplies decorators in reverse registration order, so the outermost decorator is the last one registered, and an innocent-looking reorder of theAddApplicationDecorators()lines (or a module scan that runs after it) silently changes runtime behavior with no compile error (class doc, lines 15-18). Rather than inspecting the registration list, this base resolves the service and walks the real chain by reflection (lines 27-30).[Rubric §6, CQRS & Event-Driven]assesses whether the command/query pipeline is coherent and intentional;[Rubric §2, Design Patterns]assesses correct application of the decorator pattern;[Rubric §14, Testability]covers turning an ordering convention into an executable check; and[Rubric §34, Architecture Governance]covers the fact that a decision record (ADR-014) is enforced here rather than merely written down. - Walkthrough
- Four type parameters (lines 32-35): a representative command with its
TResultand a representative query with itsTResult, each of which must have a concrete registered handler. ConfigureServices(IServiceCollection services)(MMCA.Common/Source/Hosting/MMCA.Common.Testing/DecoratorPipelineOrderTestsBase.cs:44): the one abstract member. The subclass registers test doubles for the decorator dependencies (IFeatureManager,ICorrelationContext,ICacheService,IUnitOfWork,ILogger<>) and then runs the repo's real registration sequence, module scans first andAddApplicationDecorators()last (doc lines 19-26).ExpectedCommandDecorators(lines 47-54) pins, outermost first,FeatureGateCommandDecorator,LoggingCommandDecorator,CachingCommandDecorator,ValidatingCommandDecorator,TransactionalCommandDecorator.ExpectedQueryDecorators(lines 57-62) pinsFeatureGateQueryDecorator,LoggingQueryDecorator,CachingQueryDecorator, the query pipeline having neither validation nor a transaction. Both arevirtual, so a host with a deliberately different chain can narrow them.- The two
[Fact]s,CommandPipeline_NestsDecorators_InAdr014Order(lines 64-66) andQueryPipeline_NestsDecorators_InAdr014Order(lines 68-70), each hand the closed handler interface and the expected list toAssertPipeline. AssertPipeline(lines 72-91): builds the collection, builds a provider, opens a scope (handlers are scoped), resolves the outermost handler and asserts it is non-null with a message that tells the subclass author what is missing (lines 80-82). It then unwraps the chain, maps each link to a simple type name, and asserts every element except the last equals the expected decorator list in order (lines 84-87), finally asserting the innermost element does not end inDecorator, that is, it is the concrete handler (lines 89-90).UnwrapChain(lines 98-118): walks outermost to innermost by reflecting over each object's instance fields (public and non-public) and picking the first value that implements the same closed handler interface and is not the object itself (lines 105-108), which is how it finds the compiler-generated backing field holding the inner handler.SimpleTypeName(lines 120-125) strips the generic-arity backtick suffix so a two-arityLoggingCommandDecoratorcompares as the plain name.
- Four type parameters (lines 32-35): a representative command with its
- Why it's built this way: asserting the constructed object graph is strictly stronger than
asserting the registration list, it catches a decorator that was registered but never applied (for
example because a module scan re-registered the handler afterwards). Comparing simple type names
keeps the base free of a compile-time reference to the decorator classes, which live in
MMCA.Common.Application. - Where it's used: opt-in, a repo subclasses it once with one representative command/query pair and its own registration sequence.
- Caveats / not-in-source: the base ships in the package, but which repos have subclassed it is not determinable from this file.
IntegrationTestBase<TFixture>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/IntegrationTestBase.cs:13· Level 1 · class (abstract)
- What it is: the workhorse base class every integration test inherits. It owns the per-test HTTP client and lifecycle, typed request helpers, bearer-token management, and a thread-safe id counter, so a concrete test class is left with just its arrange/act/assert.
- Depends on:
IIntegrationTestFixture(theTFixtureconstraint,MMCA.Common/Source/Hosting/MMCA.Common.Testing/IntegrationTestBase.cs:14), plusXunit'sIAsyncLifetimeandSystem.Net.Http.Json(MMCA.Common/Source/Hosting/MMCA.Common.Testing/IntegrationTestBase.cs:1-3). - Concept introduced, the xUnit async test lifecycle + per-test isolation.
[Rubric §14, Testability]: the base implementsIAsyncLifetimesoInitializeAsyncruns before each test andDisposeAsyncafter, and it hangs the database reset off that hook so every test starts from a clean database, the single most important property for reliable integration tests. - Walkthrough
- Fields / properties: a
static int _nextId = 1000seed (MMCA.Common/Source/Hosting/MMCA.Common.Testing/IntegrationTestBase.cs:16), and theFixture/Clientprotected properties (lines 19-22). - Constructor (
MMCA.Common/Source/Hosting/MMCA.Common.Testing/IntegrationTestBase.cs:24-28): stores the injected fixture and eagerly creates theHttpClientfrom it. InitializeAsync(line 31): aValueTaskthat awaitsFixture.ResetDatabaseAsync()before each test.DisposeAsync(lines 34-39): suppresses finalization and disposes the client.- Auth helpers:
SetBearerToken(string)/ClearAuthentication()(lines 42-48) set or clear theAuthorizationheader, the hook through which aJwtTokenGeneratortoken is applied. - Typed HTTP helpers:
GetAsync<T>(lines 51-56, which callsEnsureSuccessStatusCodethen deserializes), andPostAsync<T>/PutAsync<T>/PutAsync/DeleteAsync(lines 59-72) returning the rawHttpResponseMessageso a test can assert status codes. NextId()(line 75):Interlocked.Incrementover the shared seed, so parallel tests never collide on generated ids.
- Fields / properties: a
- Why it's built this way: per-test database reset plus a per-test client is the isolation contract;
centralizing the typed helpers keeps individual tests short and consistent. The static
Interlockedcounter is the cheapest safe way to hand out unique ids under xUnit's parallelism. - Where it's used: the direct base of all three contract test bases below, and of every concrete integration test in the downstream apps.
SqlServerIntegrationTestFixtureBase<TEntryPoint>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/SqlServerIntegrationTestFixtureBase.cs:27· Level 1 · class (abstract)
- What it is: the reusable fixture that boots a real service host in-process against a throwaway
SQL Server database, applies the module's migrations on first start, resets data between tests with
Respawn, and drops the database on disposal. It is the concrete engine behind
IIntegrationTestFixturefor SQL Server hosts. - Depends on:
IIntegrationTestFixture(implemented, line 27), plusMicrosoft.AspNetCore.Mvc.Testing(WebApplicationFactory),Microsoft.Data.SqlClient,Respawn, andXunit'sIAsyncLifetime(MMCA.Common/Source/Hosting/MMCA.Common.Testing/SqlServerIntegrationTestFixtureBase.cs:1-4). - Concept introduced, the disposable-database integration fixture + environment-variable overrides.
[Rubric §14, Testability]and[Rubric §8, Data Architecture]: real integration coverage needs a real relational database, and this fixture makes that cheap and hermetic, a fresh GUID-named database per fixture, migrated from scratch, Respawned between tests, dropped at the end. The database-per-service routing (ADR-006) is why the class doc stresses theDataSourcescollapse onto a single overridden connection string (lines 16-24). - Walkthrough
- State (
MMCA.Common/Source/Hosting/MMCA.Common.Testing/SqlServerIntegrationTestFixtureBase.cs:30-45): the recorded original-environment map, the server-base / database-name strings, theWebApplicationFactory, theRespawner, a_databaseCreatedflag, and the publicClient/ConnectionString.ConnectionString(line 45) is exposed so SQL-fidelity tests can read raw tables (for example to assert an integration event landed in the outbox). Services(line 52): the booted host's root service provider, exposed so cross-service tests can resolve a consumer-sideIIntegrationEventHandler<T>or a repository and drive the integration-event flow directly against the real database.- Abstract knobs:
SqlBaseEnvironmentVariable(line 58, names the env var holding the CI SQL base connection string),DatabaseNamePrefix(line 61), andCreateFactory()(line 134, where the subclass builds the host).CreateClient()(line 64) satisfies the interface by delegating to the factory. InitializeAsync(lines 67-96): resolves the server base fromSqlBaseEnvironmentVariableor falls back to LocalDB (lines 69-70), composes a GUID-suffixed database name and connection string (lines 71-72), forcesASPNETCORE_ENVIRONMENT=Testingand pushes the top-level SQL connection string as environment variables (lines 75-76), lets the subclass push its own viaConfigureTestEnvironment(line 77), and builds the factory (line 79); creating the client is what triggers the host'sMigrateinit to create the database and apply the module's migrations (lines 81-84). It then builds theRespawner, ignoring__EFMigrationsHistory(lines 86-95).ResetDatabaseAsync(lines 99-112): returns immediately when no respawner exists, otherwise opens a connection and callsRespawner.ResetAsync.DisposeAsync(lines 115-131): disposes client and factory, drops the database when one was created, and restores the environment.ConfigureTestEnvironment(lines 142-144) is an emptyvirtualhook receiving the setter delegate.SetEnvironmentVariable(lines 146-155) records only the first original value per key so re-pushing a key cannot clobber the restore point;RestoreEnvironment(lines 157-165) puts them all back and clears the map.DropDatabaseAsync(lines 167-188): clears pooled connections so the database is free to drop (line 170), connects tomaster, and runs a guardedSET SINGLE_USER WITH ROLLBACK IMMEDIATE+DROP DATABASE(lines 180-183), with a scopedCA2100suppression justified because the database name is a server-generated GUID, never user input (line 179).
- State (
- Why it's built this way: overrides go through process environment variables because the host reads
its connection string at configure-time; forcing the
Testingenvironment skipsappsettings.Development.json(which would pointDataSourcesatlocalhost) so the resolver collapses onto the single overridden top-level connection string, making the fixture behave like a clean single-database monolith. LocalDB-by-default keeps local runs zero-config while CI can point at a SQL service container. - Where it's used: the base of each app's per-service integration fixture (Identity / Conference /
Engagement and equivalents), which is then the
TFixturefor that service's integration and contract tests. - Caveats / not-in-source: the fixture needs a reachable SQL Server, so it builds but does not run without one; these suites run in CI's SQL-service job.
OpenApiContractTestsBase<TFixture>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/OpenApiContractTestsBase.cs:21· Level 2 · class (abstract)
- What it is: a contract fitness base that boots a host and asserts its
/openapi/v1.jsondocument is served, is a well-formed OpenAPI 3.x document, and still describes the core public resources, so an accidental controller/route removal fails CI instead of silently changing the published contract. - Depends on:
IntegrationTestBase<TFixture>(inherited,MMCA.Common/Source/Hosting/MMCA.Common.Testing/OpenApiContractTestsBase.cs:21),System.Net,System.Text.Json,AwesomeAssertions,Xunit(lines 1-4). - Concept introduced, the contract guard on the live document.
[Rubric §9, API & Contract Design]assesses whether the API surface is described and kept stable; the pattern across all three Level 2 bases is a live-document guard with no committed snapshot (MMCA.Common/Source/Hosting/MMCA.Common.Testing/OpenApiContractTestsBase.cs:14-16), the assertions run against the document the host actually serves, so new controllers can never leave a stale snapshot behind and a removed one is caught immediately. - Walkthrough
- Overridable / abstract knobs:
OpenApiDocumentPath(line 30, defaults to/openapi/v1.json),MinimumPathCount(line 37, a coarse floor under the route surface),MinimumPathCountBecause(line 44, the failure-message reason), andCorePublicResources(line 50, the resource paths that must keep being described). OpenApiDocument_IsServed_AsWellFormedOpenApiDescribingTheApiSurface(lines 52-65): parses the JSON and assertsopenapistarts with3.,info.titleis non-empty, apathsobject exists, and it holds at leastMinimumPathCountentries.OpenApiDocument_DescribesEveryCorePublicResource(lines 67-85): first guards against a vacuous pass (the subclass must pin at least one resource, lines 70-71), then checks everyCorePublicResourcesentry is present, matching on name case-insensitively (lines 77-80) so presence, not exact casing, is the contract.GetOpenApiJsonAsync(lines 91-100): clears auth (the document is anonymous outside Production), fetches the path, asserts 200, and returns the raw JSON.
- Overridable / abstract knobs:
- Why it's built this way: asserting the live document (rather than diffing a checked-in snapshot) keeps the guard maintenance-free while still catching the two failures that matter, the document disappearing and a public resource vanishing.
- Where it's used: subclassed once per service host (
OpenApiContractTestsin each app), supplying the fixture, the path floor, and the pinned resource list.
ProblemDetailsContractTestsBase<TFixture>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/ProblemDetailsContractTestsBase.cs:21· Level 2 · class (abstract)
- What it is: a contract fitness base that asserts a host's error responses are RFC 9457 Problem
Details documents, machine-readable bodies carrying
status,title, and a diagnostic extension, across both error-shaping paths the framework uses. - Depends on:
IntegrationTestBase<TFixture>(inherited,MMCA.Common/Source/Hosting/MMCA.Common.Testing/ProblemDetailsContractTestsBase.cs:21),System.Net,System.Net.Http.Json,System.Text.Json,AwesomeAssertions,Xunit(lines 1-5). Same live-guard shape as the OpenAPI base above. - Concept: still
[Rubric §9, API & Contract Design], here the pinned contract is the error shape. The class covers the two distinct paths that produce errors (class doc, lines 10-18): ASP.NET Core model validation (a 400application/problem+jsonbody) and the framework'sHandleFailureResult-error mapping (seeApiControllerBase), which turns aResultfailure such as anErrornot-found into a 404 problem. - Walkthrough
Validation_400_HasProblemDetailsShape(lines 29-39): sends the subclass's validation probe, asserts the shared shape at 400, then checks theproblem+jsoncontent type and the model-validation-only extensionstype,traceId, anderrors.NotFound_404_HasProblemDetailsShape(lines 41-47): sends the 404 probe and asserts the shared shape.- Abstract probes:
SendValidationErrorProbeAsync(line 54) andSendNotFoundProbeAsync(line 60), the only app-specific pieces, authenticating first when the endpoint requires it. AssertProblemDetailsShapeAsync(lines 67-83): the sharedprotected staticassertion, JSON content type, echoedstatus, non-emptytitle, and at least one diagnostic extension (errors,traceId, orrequestId), returning the parsed body so a subclass can follow up (for example a host with a reachable 409-conflict path adds its own conflict test reusing this helper, doc lines 16-18).
- Why it's built this way: pinning both the validation path and the
HandleFailurepath in one base means a regression in either error channel breaks CI, and factoring the shape assertion into a shared static keeps every host's error contract identical. - Where it's used: subclassed as
ProblemDetailsContractTestsper host, plus per-host conflict tests layered onAssertProblemDetailsShapeAsync.
ServiceInfoVersioningContractTestsBase<TFixture>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/ServiceInfoVersioningContractTestsBase.cs:19· Level 2 · class (abstract)
- What it is: a contract fitness base that proves the API-versioning machinery actually works across
more than one version, that
/ServiceInfois served by both v1.0 (deprecated) and v2.0, selected by theapi-versionheader, and that the host reports supported/deprecated versions in response headers. - Depends on:
IntegrationTestBase<TFixture>(inherited,MMCA.Common/Source/Hosting/MMCA.Common.Testing/ServiceInfoVersioningContractTestsBase.cs:19),System.Net,System.Text.Json,AwesomeAssertions,Xunit(lines 1-4). - Concept:
[Rubric §9, API & Contract Design]again, the versioning axis. The class doc (MMCA.Common/Source/Hosting/MMCA.Common.Testing/ServiceInfoVersioningContractTestsBase.cs:8-17) makes the point that without a second working version the whole versioning story would be untestable, so this base keeps the machinery exercised rather than merely asserted. Because theServiceInfocontroller ships inMMCA.Common.API(ServiceInfoControllerBase), the entire test body is identical across repos; a subclass supplies only its fixture. - Walkthrough
ServiceInfo_V1_ReturnsMinimalShape_AndIsReportedDeprecated(lines 27-41): requests v1.0, asserts 200, checksapiVersion == "1.0"and that the evolvedsupportedVersionslist is absent in the v1 shape (lines 35-36), then asserts anapi-deprecated-versionsresponse header contains1.0(lines 38-40).ServiceInfo_V2_ReturnsEvolvedShape_AndIsReportedSupported(lines 43-57): requests v2.0, asserts 200, checksapiVersion == "2.0"and thatsupportedVersionscontains2.0(lines 50-52), then asserts anapi-supported-versionsheader advertises2.0(lines 54-56).GetServiceInfoAsync(string apiVersion)(lines 59-65): clears auth and sends the GET with theapi-versionheader set to the requested version.
- Why it's built this way: keeping a real deprecated v1 and a real v2 side by side, and asserting both
the payload shapes and the
ReportApiVersionsheaders, is what proves version negotiation is wired end to end rather than configured and forgotten. - Where it's used: subclassed per host as the service-info versioning fitness test, fixture only.
HandlerTestBase<THandler>
MMCA.Common.Testing ·
MMCA.Common.Testing·MMCA.Common/Source/Hosting/MMCA.Common.Testing/HandlerTestBase.cs:38· Level 9 · class (abstract)
- What it is: the reusable Moq scaffold for command/query handler unit tests. It hands a derived
test class a pre-configured
Mock<IUnitOfWork>, a no-op logger typed to the handler under test, and two one-line helpers that register a repository mock into that unit of work. - Depends on:
IUnitOfWork,IRepository<TEntity, TIdentifierType>,IReadRepository<TEntity, TIdentifierType>,AuditableAggregateRootEntity<TIdentifierType>andAuditableBaseEntity<TIdentifierType>(the two generic constraints), plusMoq,Microsoft.Extensions.Logging, andNullLogger<T>(MMCA.Common/Source/Hosting/MMCA.Common.Testing/HandlerTestBase.cs:1-5). - Concept: the arrange-phase base class. Where
IntegrationTestBase<TFixture>gives an end-to-end test a booted host, this gives an isolated unit test a mocked persistence boundary: no database, no host, no HTTP. The class doc (lines 10-12) frames it as the shared replacement for the per-test copy-paste ofMock<IUnitOfWork>+GetRepositorywiring +SaveChangesAsyncsetup.[Rubric §14, Testability]assesses whether the design permits fast isolated tests; the fact that handlers depend onIUnitOfWork(an Application-layer abstraction) rather than aDbContextis what makes this scaffold possible at all, which is[Rubric §3, Clean Architecture]paying off in the test tier.[Rubric §16, Maintainability]covers the deduplication itself. - Walkthrough
- Constructor (
MMCA.Common/Source/Hosting/MMCA.Common.Testing/HandlerTestBase.cs:41-42): a single expression-bodied statement that pre-configuresUnitOfWork.SaveChangesAsync(...)to return1, the success path, so a happy-path test writes no persistence setup at all. Failure-path tests override it with their ownSetup(doc lines 32-35). UnitOfWork(line 45): theMock<IUnitOfWork>every registered repository is wired into, created by a property initializer so the constructor can configure it.Logger(line 48):NullLogger<THandler>.Instance, typed by the handler type parameter so it binds directly to the handler'sILogger<THandler>constructor parameter.RegisterRepository<TEntity, TIdentifierType>()(lines 56-64): creates aMock<IRepository<TEntity, TIdentifierType>>, wires that same object into bothGetRepository<...>()andGetReadRepository<...>()(lines 61-62), and returns the mock for furtherSetup/Verify. Constrained toAuditableAggregateRootEntity<TIdentifierType>(line 57), that is, to aggregate roots.RegisterReadRepository<TEntity, TIdentifierType>()(lines 72-79): the read-only counterpart for non-aggregate child entities that expose no read-write repository, constrained to the looserAuditableBaseEntity<TIdentifierType>(line 73) and wiring onlyGetReadRepository<...>().
- Constructor (
- Why it's built this way: wiring one repository mock into both accessors matters because a handler
may read through
GetReadRepositoryand write throughGetRepositoryon the same aggregate; a test forced to register two mocks would have to keep their state in sync. Pre-succeedingSaveChangesAsyncencodes the common case so only the interesting deviation appears in a test. - Where it's used: the base of handler unit-test classes across the framework and the downstream
application modules; the class doc carries a worked
CreateEventHandlerTestsexample (lines 19-31).
ArchitectureAssert
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureAssert.cs:8· Level 0 · static class
- What it is: the shared failure-reporting helper for every architecture fitness function, a static class with two
NoViolationsoverloads that turn a rule breach into a readable, offender-listing assertion failure. - Depends on:
NetArchTest.Rules.TestResult(NuGet) and AwesomeAssertions'Should()fluent API. No first-party dependencies: this is the bottom of the fitness-function stack. - Concept introduced, architecture fitness functions. A fitness function is an automated test that asserts a structural property of the codebase (a layer never references another, a controller is sealed) rather than a behavioral one. This package makes those rules first-class, shared code.
[Rubric §14, Testability]assesses how well invariants are guarded by executable checks;ArchitectureAssertis the reporting primitive that makes a failing invariant name its offenders instead of just going red.[Rubric §34, Architecture Governance]assesses whether architectural decisions are enforced rather than merely documented; every rule in this package funnels its verdict through here. - Walkthrough
NoViolations(NetArchTest.Rules.TestResult result, string reason)(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureAssert.cs:11) returns early whenresult.IsSuccessful(line 13), otherwise joins theFailingTypesfull names into a bullet list (lines 18-19) and assertsIsSuccessful.Should().BeTrue(...)with the reason plus the violation list as thebecauseargument (lines 21-22).NoViolations(IEnumerable<string> violations, string reason)(line 26) materializes the sequence and assertslist.Should().BeEmpty(...)(line 30), for the reflection-derived and file-scanning rules that produce a plain string list rather than a NetArchTest result.
- Why it's built this way: the XML doc (lines 3-7) names it the un-drifted successor to the three per-repo
ArchitectureTestHelper.AssertNoViolationscopies: the reporting logic was duplicated in MMCA.Common, MMCA.Store, and MMCA.ADC, and centralizing it here removes the drift. - Where it's used: every rule in ArchitectureRules and every reflection-based test base calls one of these two overloads as its final step.
BrandColorTokenTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/BrandColorTokenTestsBase.cs:13· Level 0 · abstract class
- What it is: an abstract xUnit test base that fails the build when a landing-page stylesheet re-hardcodes the brand hex instead of sourcing it from the shared
var(--mmca-primary)CSS custom property. - Depends on:
[Fact](xUnit), AwesomeAssertions, andAssembly.GetManifestResourceStream(BCL) to read embedded CSS. No first-party type dependency: it operates on the strings the subclass embeds. - Concept introduced, the drift fitness function. Unlike a layer rule that reflects over assemblies, a drift function reads committed text (CSS here) and asserts a single source of truth is used.
[Rubric §20, Design System & Theming]assesses whether visual tokens have one authoritative definition; this base guards that consumers of the framework palette cannot silently fork the primary color. - Walkthrough
- Two private constants pin the forbidden literal
#1565C0and the required tokenvar(--mmca-primary)(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/BrandColorTokenTestsBase.cs:15-16). - The subclass supplies
EmbeddedCssLogicalNames(line 22), the manifest-resource names of its landing-page stylesheets. - The single
[Fact]LandingPageCss_SourcesBrandColorFromToken_NotHardcodedHex(line 25) first asserts the list is non-empty (a non-vacuity guard, lines 27-28), then for each stylesheet reads it viaReadEmbeddedCss(line 56, which throws when the resource is missing, lines 58-60) and records a violation when the file is blank (line 37), when the token is absent (line 43), or when the raw hex is present (line 48).
- Two private constants pin the forbidden literal
- Why it's built this way: the doc (lines 4-11) explains the split. MMCA.Common's own
BrandColorTokenTestsguards the C#-to-CSS token definition (fromBrandColors.Primary), while this base guards every downstream consumer of it, embedding the stylesheets as manifest resources so the package needs no file-system access into the consumer repo. - Where it's used: subclassed once per repo that ships a branded landing page (Store, ADC).
CrossEntityNavigationFinder
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Specifications.cs:97· Level 0 · private sealed class
- What it is: a private
ExpressionVisitornested in ArchitectureRules that walks a specification'sCriterialambda and collects the names of other entity types it navigates into. - Depends on:
System.Linq.Expressions.ExpressionVisitor,MemberExpression,PropertyInfo(BCL) and the RuleHelpers extension memberInheritsAuditableEntity. - Concept introduced, expression-tree inspection as a fitness check. NetArchTest reasons about assembly-level references only; to catch a rule expressed inside a lambda body (
s => s.Event.IsPublished), the code instantiates the specification, reads itsCriteriaexpression tree, and visits it. This backs the polyglot / database-per-service invariant (ADR-006): aCriteriathat navigates to an entity in another physical data source produces an untranslatable join at runtime.[Rubric §8, Data Architecture]assesses cross-source data access discipline; this visitor is how that discipline is machine-checked. - Walkthrough
- The primary constructor captures
ownEntityType(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Specifications.cs:97), and_navigatedis the accumulating set (line 99). Find(Expression body)visits the body and returns the set (lines 101-105).VisitMember(line 107) resolves the accessed property's type throughEntityTypeOf(line 121) and, when the result is an auditable entity other than the specification's own type, adds its name (lines 111-115).EntityTypeOf(line 121) treats a direct entity property as a navigation (lines 123-126) and unwraps generic collection navigations such asICollection<TChild>to their element type (lines 129-136).
- The primary constructor captures
- Why it's built this way: filtering by a foreign-key column is engine-portable; navigating is not (notably on Cosmos, where the cross-source relationship is degraded out of the model). The finder is the enforcement half of
ArchitectureRules.SpecificationsDoNotNavigateToOtherEntities(line 24), whose failure message points authors at CrossSourceSpecification instead (lines 14-15). Only parameterless specifications can be instantiated and inspected; ones with constructor dependencies are skipped (lines 37-41). - Where it's used: only inside that rule (line 66), which is surfaced through SpecificationConventionTestsBase.
Layer
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/IArchitectureMap.cs:9· Level 0 · enum
- What it is: the closed vocabulary of architectural layers a fitness function can reason about:
Shared,Domain,Application,Infrastructure,Api,Ui,Grpc,Contracts,ServiceHost(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/IArchitectureMap.cs:11-19). - Depends on: nothing; a plain enum.
- Concept introduced: the Clean Architecture layer taxonomy made into a type. The layer flow itself is taught in primer §1; here it becomes an enum the rule library keys off, so a rule that iterates layers is written once against the enum rather than hard-coded per repo.
[Rubric §3, Clean Architecture]assesses whether the layering is explicit and enforced; this enum is the shared alphabet. - Walkthrough: the doc (lines 3-8) notes that
Ui,Grpc,Contracts, andServiceHostare optional: a repo simply omits them from its map when absent, so a rule iterating them is vacuously satisfied with no compile dependency on the missing assembly. ArchitectureMapBase.Segmenttranslates each member to its namespace segment (for exampleApito"API"andServiceHostto"Service",MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureMapBase.cs:105-117). - Where it's used: carried by LayerRef, projected by IArchitectureMap
.OfLayer, and threaded through nearly every method in ArchitectureRules.
RouteAuthorizationTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/RouteAuthorizationTestsBase.cs:22· Level 0 · abstract class
- What it is: an abstract test base that reflects over a UI assembly's routable Blazor pages and fails the build if a page the subclass marks as governed has lost its
[Authorize(Roles = "...")]role gate. - Depends on:
[Fact](xUnit), AwesomeAssertions, RuleHelpers.LoadableTypes, and pure reflection over attribute instances by full name (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/RouteAuthorizationTestsBase.cs:24-25). - Concept introduced, the security-regression fitness function.
[Rubric §11, Security]and[Rubric §25, Navigation & IA]assess whether protected routes stay protected; this base turns "the admin page must require the Organizer role" from a review checklist into a compiled assertion, so a page cannot silently regress from[Authorize(Roles=...)]to a bare[Authorize]reachable by any authenticated user. - Walkthrough
- The subclass supplies
TargetAssembly(line 28), the exactRequiredRole(line 31), anIsGovernedPagestrategy (line 40), and aMinimumGovernedPagesnon-vacuity floor (line 47, default 1). GovernedPages_RequireDeclaredRole(line 50) collects pages that are routable, governed, and do not require the role, then asserts the offender set is empty, naming each offender's route templates (lines 52-60).GovernedPageSet_IsNotEmpty(line 64) guards the guard: if a refactor moved namespaces soIsGovernedPagematched nothing, the first test would pass vacuously, so this one asserts the discovered count meets the floor (lines 68-73).- Detection is all reflection by attribute full name:
IsRoutablePage(line 77),RequiresRole(line 83, which reads theRolesproperty off the attribute instance and requires an exact ordinal match, so a bare[Authorize]or a different role fails), the subclass helperHasAuthorizeAttribute(line 94),Routes(line 97), and the base-type walkIsOrDerivesFrom(line 105).
- The subclass supplies
- Why it's built this way: matching attributes by full name keeps the shared package free of an ASP.NET Core reference while still inspecting ASP.NET attributes (lines 16-20); a reflection scan also covers future pages matching the strategy without hand-enumeration. Deliberately anonymous public pages and bare-
[Authorize]self-service pages simply must not matchIsGovernedPage(lines 12-15). - Where it's used: subclassed per module UI assembly in Store and ADC.
RuleHelpers
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/RuleHelpers.cs:14· Level 0 · internal static class
- What it is: the internal reflection toolbox the reflection-based fitness functions share: extension members for enumerating loadable types, matching suffix conventions on generic types, detecting base types and interfaces by open generic or name prefix, and classifying property setters as mutable or
init-only. - Depends on:
System.Reflection(Assembly,Type,PropertyInfo,ReflectionTypeLoadException,BindingFlags) only. - Concept introduced: the doc (lines 5-9) states the premise: NetArchTest cannot inspect method return types, generic-argument constraints, property accessors, or attribute usage, so those rules reflect over loaded types directly through these helpers.
[Rubric §14, Testability]and[Rubric §15, Best Practices]apply: the reflection subtleties (partial assembly loads,init-only detection) are solved once here rather than re-derived per rule. - Walkthrough: the class body is three C# preview
extension(T)blocks, so every helper is an extension property or method, not a classicthis-parameter extension method.extension(Assembly assembly)(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/RuleHelpers.cs:16):LoadableTypes(line 19) tolerates a partially-resolvable assembly by catchingReflectionTypeLoadExceptionand returning the types that did load throughOfType<Type>(), which both filters nulls and narrows the element type (lines 27-31).ConcreteClasses(line 36) narrows that to non-abstract classes.extension(Type type)(line 40):SimpleName(line 47) strips the generic-arity backtick so suffix conventions match generic types too.InheritsGeneric(line 58) walks the base chain andImplementsGeneric(line 72) scans the interface set for an open generic.HasBaseTypeStartingWith(line 80) detects a framework base by full-name prefix without a compile dependency (for example FluentValidation'sAbstractValidator).DeclaredPublicProperties(line 94) narrows to declared-only public instance properties.InheritsAggregateRoot(line 101) andInheritsAuditableEntity(line 108) hard-code the MMCA entity base full names (AuditableAggregateRootEntity<TIdentifierType> plus theAuditableBaseEntityandBaseEntityancestors) so the entity rules can classify types cross-repo.extension(PropertyInfo property)(line 114):HasPublicMutableSetter(line 121) is the immutability primitive. It reportsfalsewhen there is no public setter, andfalseforinit-only setters by looking for theSystem.Runtime.CompilerServices.IsExternalInitrequired custom modifier on the setter's return parameter (lines 131-135).
- Why it's built this way: every helper avoids a compile-time reference to the type it detects (base types matched by string prefix), which is what lets one rule body run identically across three repos that do not reference each other. The class carries a file-level
[SuppressMessage]for CA1708 (lines 10-13): with multipleextension(T)blocks in one static class the analyzer flags the compiler-generated grouping members as case-colliding, a documented false positive. - Where it's used: throughout the ArchitectureRules partials, inside CrossEntityNavigationFinder, and directly by RouteAuthorizationTestsBase.
- Caveats / not-in-source: the type is
internal, so consumer repos cannot call these helpers directly; they reach the same behavior only through the public rules and bases.
LayerRef
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/IArchitectureMap.cs:31· Level 1 · sealed record
- What it is: an immutable record describing one assembly in a repo's architecture: its owning
Module, its Layer, the compiledAssembly, and itsRootNamespace(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/IArchitectureMap.cs:31). - Depends on: Layer and
System.Reflection.Assembly. - Concept introduced: the atomic unit of an architecture map.
Moduleis the empty string for framework (MMCA.Common) layers that belong to no business module (lines 22-30), which is how the same record models both a module assembly (("Catalog", Application, ...)) and a shared framework assembly (("", Shared, ...)). Every projection and every isolation rule keys off that one convention. - Walkthrough: a four-parameter positional
sealed record(line 31), so it gets structural equality and immutability for free; its members are set once at construction by the map'sDefineLayers. - Where it's used: ArchitectureMapBase stores a lazy
IReadOnlyList<LayerRef>and derives every projection from it; itsFrameworkandModulefactory helpers are what build these.
IArchitectureMap
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/IArchitectureMap.cs:39· Level 2 · interface
- What it is: the single per-repo abstraction every architecture fitness function keys off. Each repo supplies one implementation declaring its layer and module assemblies; the shared rule library and abstract test bases consume only this interface, so a rule is written once and runs identically across MMCA.Common, MMCA.Store, and MMCA.ADC (
MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/IArchitectureMap.cs:33-38). - Depends on: LayerRef, Layer,
System.Reflection.Assembly. - Concept introduced, the architecture map as the fitness-function extension point. This is a classic Dependency Inversion: the rules depend on an abstraction (the map), and each repo provides the concrete inventory of its assemblies.
[Rubric §1, SOLID](DIP) and[Rubric §7, Microservices Readiness]apply: the map also models the per-module layers a would-be extracted service owns, so the isolation rules can check module boundaries the same way in any repo. - Walkthrough: the interface exposes identity (
RepoTokenline 42,ModuleNamesline 45), the rawLayersinventory (line 48), and the projections the rules lean on:OfLayer(all assemblies of a kind, line 51), the per-moduleModuleDomain/ModuleApplication/ModuleShared(lines 54-60),Infrastructure()/Api()across framework plus modules (lines 63-66), the lookupsFor(module, layer)(line 69) andModuleOf(assembly)(line 72), namespace derivationRootNamespace(module, layer)(line 75), andOtherModuleNamespaces(line 81), which returns the same-layer namespaces of every other module (the forbidden targets for a module-isolation rule, empty for framework layers and single-module repos). - Why it's built this way: funneling every rule through one interface is what removed three drifting copies of the architecture-test suite; add a repo and you write one map, not a new rule set.
- Where it's used: held as the
protected abstract IArchitectureMap Mapon nearly every*TestsBasein this group and passed to nearly every method of ArchitectureRules. ArchitectureMapBase is the reusable partial implementation, and CommonArchitectureMap / AdcArchitectureMap are two concrete maps.
ArchitectureMapBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureMapBase.cs:11· Level 3 · abstract class
- What it is: the reusable base implementation of IArchitectureMap: a repo supplies only
RepoTokenand aDefineLayers()declaration, and every projection, namespace derivation, and module-isolation target computation is derived here (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureMapBase.cs:3-10). - Depends on: IArchitectureMap, LayerRef, Layer,
System.Lazy, andSystem.IO(forFindRepoRoot). - Concept introduced, the template-method shape for a repo map: the base fixes the algorithm and the subclass fills two holes. It also centralizes every namespace and assembly string in one file, which the doc (lines 8-9) notes fixes Ubuntu CI case-sensitivity in one place.
- Walkthrough
- The constructor wraps
DefineLayers()in aLazy<IReadOnlyList<LayerRef>>(lines 15-16) so the assembly list materializes once, andLayersreads that value (line 25). ModuleNames(line 28) filters out framework refs, then distinct-orders the module names ordinally.OfLayer(line 35),ModuleDomain/ModuleApplication/ModuleShared(lines 39-45, via the privateModuleLayer, line 101),Infrastructure(line 48), andApi(line 51) are one-line LINQ projections overLayers.For(line 54) andModuleOf(line 59) are the lookups, both ordinal-comparison based.RootNamespace(line 63) branches on module: framework layers becomeMMCA.Common.{Segment}, module layers{RepoToken}.{module}.{Segment}.OtherModuleNamespaces(line 69) maps every other module through it.- The static
FindRepoRoot(solutionFileName)(line 79) walks up fromAppContext.BaseDirectoryto the directory containing the named.slnx, so doc and config consistency tests can read committed files regardless of the runner's working directory, throwing a clearInvalidOperationExceptionwhen not found (lines 89-90). - The
protected static Framework(...)(line 94) andprotected Module(...)(line 98) factory helpers build LayerRefs with the right namespace, and the internalSegment(line 105) maps each Layer to its namespace token.
- The constructor wraps
- Why it's built this way: a per-repo map stays a flat declaration of assemblies (the two abstract members), and everything derivable is derived, so the maps cannot drift in how they compute namespaces.
- Where it's used: each repo's concrete map subclasses this (CommonArchitectureMap, AdcArchitectureMap, and Store's equivalent).
FindRepoRootis called directly by every file-reading base: DataResidencyTestsBase, FormsConventionTestsBase, FrameworkVersionConsistencyTestsBase, LocalizedTextConventionTestsBase, RawQueryableConventionTestsBase, StateManagementConventionTestsBase, and UIArchitectureConventionTestsBase.
ArchitectureRules
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Controllers.cs:3· Level 3 · static partial class
- What it is: the reusable rule library: one large
static partial classsplit across sixteenArchitectureRules.*.csfiles, whose methods each assert one architectural invariant across every applicable assembly a map declares. A repo's test classes reduce to a sealed subclass of the matching*TestsBasesupplying its own map. - Depends on: IArchitectureMap, Layer, ArchitectureAssert, RuleHelpers, NetArchTest (
Types.InAssembly(...)), and, for the specification rule,System.Linq.Expressionsplus CrossEntityNavigationFinder. - Concept introduced, the rule as a parameterized function. Each method takes an
IArchitectureMapand does its own loop, so the*TestsBaseclasses are thin[Fact]shells that delegate. The partial is organized by concern across the filesArchitectureRules.{Controllers, Entities, Events, Governance, HandlerResults, Handlers, Immutability, Layers, Localization, LocalizedText, Modules, Naming, Purity, Slices, Specifications, Transport}.cs.[Rubric §3, Clean Architecture],[Rubric §4, DDD],[Rubric §7, Microservices Readiness], and[Rubric §34, Governance]all apply: this is where the codebase's structural decisions become executable assertions. - Walkthrough: three representative shapes.
- NetArchTest shape,
ControllersDoNotDependOnInfrastructure(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Controllers.cs:6): loops the map's per-module API layer refs, computes the forbidden Infrastructure namespace viamap.RootNamespace(...), runsTypes.InAssembly(...).That().HaveNameEndingWith("Controller").ShouldNot().HaveDependencyOnAny(forbidden), and reports throughArchitectureAssert.NoViolations(result, ...)(lines 8-18). - Layer-flow shape,
ArchitectureRules.Layers.cs: one public method per forbidden edge,DomainDoesNotDependOnApplication(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Layers.cs:12) throughUiDoesNotDependOnInfrastructure(line 60), all delegating to the privateLayerNotDependOnLayer(line 101), which loops every assembly of thefromlayer and asserts no dependency on thetolayer's namespace. Two non-vacuity rules sit alongside them:LayerMapDeclaresLayers(line 72) andModulesDeclareLayers(line 89). - Reflection shape,
ControllersAreSealed(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Controllers.cs:37): enumeratesmap.Api().ConcreteClasses(the RuleHelpers extension property), filters non-sealed controllers via the privateIsController(line 70, which matches on theControllersuffix or an MVC base type), and asserts the string offender list is empty.ControllersInheritApiControllerBase(line 54) is the same shape with a caller-supplied exempt set, accepting eitherApiControllerBaseorEntityControllerBaseas the base (lines 62-63).
- NetArchTest shape,
- Why it's built this way: the MMCA.Common CLAUDE.md records the intent: the rule bodies live once here, and each repo's architecture test project is a set of sealed subclasses supplying its map, so all three repos enforce identical rules. The compile-time
Source/Build/MMCA.Common.LayerEnforcement.targetsguards the same layer flow at build time as a second gate. - Where it's used: every
*TestsBasein this group calls into it; those[Fact]methods are its public surface. - Caveats / not-in-source: the full method roster spans sixteen partials; only the entry file and representative methods are cited here. The authoritative fitness-method count lives in
MMCA.Common/FACTS.md, which is generated and CI-gated.
ConstructorDependencyCountTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ConstructorDependencyCountTestsBase.cs:14· Level 3 · abstract class
- What it is: a single-responsibility-ceiling fitness function: it fails the build if any Application-layer
*Serviceclass has a constructor with more than the repo's accepted dependency count. - Depends on: IArchitectureMap (via
Map.ModuleApplication()),[Fact], AwesomeAssertions, and reflection over constructors. - Concept introduced, quantifying the SRP smell.
[Rubric §1, SOLID]assesses single-responsibility discipline; a ballooning constructor-dependency list is the canonical smell, and this base turns a previously implicit judgement call into an enforced ceiling so the next service cannot silently grow past it (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ConstructorDependencyCountTestsBase.cs:3-13). - Walkthrough: the subclass supplies
Map(line 16) and theMaxConstructorDependencieshigh-water mark (line 22; it is abstract, so every repo states its own number).ApplicationServices_DoNotExceedConstructorDependencyCeiling(line 25) scansMap.ModuleApplication()for concrete*Serviceclasses (lines 27-31), asserts at least one was found (non-vacuity, lines 33-34), computes each service's maximum constructor parameter count (lines 36-44), and asserts none exceed the ceiling, naming offenders with their counts (lines 45-53). - Why it's built this way: the ceiling is raised only with a conscious decision; repos without business modules (MMCA.Common itself) have nothing to scan and do not subclass this (lines 11-12). It overlaps the arity check in HandlerConventionTestsBase but scopes specifically to service facades and lets a repo pin an exact number rather than take the shared default.
- Where it's used: subclassed in Store and ADC against their Application services.
AggregateConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/AggregateConventionTestsBase.cs:8· Level 4 · abstract class
- What it is: the minimal DDD aggregate fitness base for repos with no business modules (MMCA.Common itself): it asserts the Domain layer exposes aggregate roots, each built through a static
Create(...)factory returningResult<T>with no public constructor. - Depends on: IArchitectureMap, ArchitectureRules,
[Fact]. - Concept introduced, the thin delegating test base shared by most Level-4 types in this group: a
protected abstract IArchitectureMap Mapplus one[Fact]per rule that forwards to an ArchitectureRules method, with no logic of its own. The factory-returning-Result idiom on AuditableAggregateRootEntity<TIdentifierType> is what these rules verify.[Rubric §4, DDD]assesses aggregate discipline. - Walkthrough: three
[Fact]s, each a one-line delegate:Domain_ShouldExpose_AggregateRoots(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/AggregateConventionTestsBase.cs:13),AggregateRoots_ShouldHave_ResultReturningCreateFactory(line 16), andAggregateRoots_ShouldHave_NoPublicConstructors(line 19, which targets the framework-specificDomainAggregateRootsHaveNoPublicConstructorsrule rather than the module one). - Why it's built this way: module-bearing repos use the fuller EntityConventionTestsBase instead (lines 4-6); this base exists so a module-less repo still guards its aggregates.
- Where it's used: subclassed in MMCA.Common's architecture test project.
ConcurrencyConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ConcurrencyConventionTestsBase.cs:8· Level 4 · abstract class
- What it is: a one-rule delegating base asserting that every
*UpdateRequestimplements IConcurrencyAware, so concurrent edits surface as 409 Conflict rather than silent last-write-wins. - Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape from AggregateConventionTestsBase.
[Rubric §8, Data Architecture]assesses optimistic-concurrency handling; carrying a RowVersion on every update request is how that concern is enforced at the contract level. - Walkthrough: one
[Fact]UpdateRequests_ShouldImplement_IConcurrencyAware(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ConcurrencyConventionTestsBase.cs:13) delegating toArchitectureRules.UpdateRequestsAreConcurrencyAware(Map). The doc notes modules with no mutable aggregate are legitimately vacuous (lines 5-6). - Where it's used: subclassed in Store and ADC.
ControllerConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ControllerConventionTestsBase.cs:7· Level 4 · abstract class
- What it is: the presentation-layer convention base: controllers are thin and sealed, never reach Infrastructure or EF Core directly, and inherit the framework ApiControllerBase for consistent Result-to-HTTP mapping.
- Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); it adds a
protected virtual ControllersExemptFromApiControllerBaselist (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ControllerConventionTestsBase.cs:12) for controllers that legitimately bypass the base (for example a webhook endpoint that owns its own response semantics).[Rubric §9, API & Contract Design]assesses consistent controller shape. - Walkthrough: four
[Fact]s:Controllers_ShouldNotDependOn_Infrastructure(line 15),Controllers_ShouldNotDependOn_EntityFrameworkCore(line 18),Controllers_ShouldBe_Sealed(line 21), andControllers_ShouldInherit_ApiControllerBase(line 24, passing the exempt list). The underlying rules live inArchitectureRules.Controllers.cs. - Where it's used: subclassed per repo with business modules (Store, ADC).
DataResidencyTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/DataResidencyTestsBase.cs:14· Level 4 · abstract class
- What it is: a compliance-drift fitness function: the data-residency statement in a repo's
PRIVACY.mdmust match the region where personal data is actually provisioned, and known-stale region claims must not reappear. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot,System.IO(File.ReadAllText), AwesomeAssertions. - Concept introduced, a document-versus-infrastructure consistency gate.
[Rubric §30, Compliance / Privacy / Data Governance]assesses whether privacy claims track reality; this base fails the build if either the deployed region or the privacy policy changes without the other, closing the gap where a policy once claimed a region the data never lived in (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/DataResidencyTestsBase.cs:3-13). - Walkthrough: the subclass supplies
Map(line 16), the optionalForbiddenResidencyClaimslist (line 23), and implementsExtractDeployedRegion(repoRoot)(line 53) against its own source of truth (the doc cites ADC parsing the SQL region default out ofdeploy.ymland Store parsinginfra/DISASTER-RECOVERY.md). The single[Fact]PrivacyPolicy_DataStorageRegion_MatchesDeployedRegion(line 26) locates the repo root viaFindRepoRoot($"{Map.RepoToken}.slnx")(line 28), asserts the extracted region is non-blank (lines 31-32), readsPRIVACY.md, then asserts the normalized policy contains the region (line 37) and none of the forbidden claims (lines 40-44).Normalize(line 57) strips whitespace and upper-cases, so "West US 2" matches the "westus2" token. - Where it's used: subclassed in Store and ADC (module-less MMCA.Common has no deployed region).
DependencyVersionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/DependencyVersionTestsBase.cs:15· Level 4 · abstract class
- What it is: a dependency-pin fitness function guarding two commercial-license traps at build time: MassTransit must stay below v9 and SixLabors.ImageSharp below v4, both parsed out of
Directory.Packages.props. - Depends on: ArchitectureRules
.PinnedPackageMajorBelow,[Fact]. Note there is noMapon this base; it reads the props file directly through the rule. - Concept introduced, enforcing a policy pin as a test.
[Rubric §32, Dependency & Supply-Chain]assesses whether risky upgrades are guarded. The doc explains both traps: MassTransit v9 fails the startup license check and crashes every broker-enabled host while CI never starts a broker, so a blanket bump otherwise stays green (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/DependencyVersionTestsBase.cs:3-6); ImageSharp v4's MSBuild targets fail without$(SixLaborsLicenseKey), so a blanket bump breaks every build (lines 39-43). - Walkthrough:
MassTransit_MustNotExceed_MajorVersion8(line 25) loopsMassTransitPackageIds(line 17:MassTransit,MassTransit.RabbitMQ,MassTransit.Azure.ServiceBus.Core) and callsPinnedPackageMajorBelow(packageId, exclusiveMajorCeiling: 9, ...).ImageSharp_MustNotExceed_MajorVersion3(line 48) does the same forImageSharpPackageIds(line 45) with ceiling 4. Both id lists arevirtualso a repo can override to an empty list when it does not pin the package. - Why it's built this way: the doc is explicit (lines 8-13): the consumer repos (ADC, Store) do NOT pin MassTransit (it flows transitively via
MMCA.Common.Infrastructure), so they must not subclass this base with the default list, or the "must remain pinned" assertion would fail on a pin they do not declare. The v8 pin is enforced only in MMCA.Common, where MassTransit is actually pinned. - Where it's used: subclassed only in MMCA.Common's
DependencyVersionTests.
DomainPurityTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/DomainPurityTestsBase.cs:8· Level 4 · abstract class
- What it is: a framework-independence base: Domain and Shared stay free of infrastructure frameworks, and Application stays host-agnostic (no EF Core, no ASP.NET Core).
- Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); it adds an
ExtraForbiddenDomainDependencieshook (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/DomainPurityTestsBase.cs:12) so a repo bans its own frameworks (the doc cites Store banning "Stripe" and ADC banning "RabbitMQ").[Rubric §3, Clean Architecture]and[Rubric §4, DDD]assess the framework-free core. - Walkthrough: four
[Fact]s:Domain_ShouldBe_FrameworkFree(line 15) andShared_ShouldBe_FrameworkFree(line 18), both passing the extra-forbidden list, thenApplication_ShouldNotDependOn_EntityFrameworkCore(line 21) andApplication_ShouldNotDependOn_AspNetCore(line 24). - Where it's used: subclassed in all three repos.
EntityConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/EntityConventionTestsBase.cs:8· Level 4 · abstract class
- What it is: the fuller DDD entity and aggregate convention base (the module-bearing counterpart to AggregateConventionTestsBase): entities are sealed and live only in Domain, aggregate roots use a
Create(...)factory returningResult<T>with no public constructor, and DTOs and requests stay out of Domain and Infrastructure. - Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase).
[Rubric §4, DDD]and[Rubric §3, Clean Architecture]apply. - Walkthrough: six
[Fact]s:Domain_ShouldExpose_AggregateRoots(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/EntityConventionTestsBase.cs:13),AggregateRoots_ShouldHave_ResultReturningCreateFactory(line 16),AggregateRoots_ShouldHave_NoPublicConstructors(line 19),DomainEntities_ShouldBe_Sealed(line 22),DomainEntities_ShouldReside_InDomainLayer(line 25),DtosAndRequests_ShouldNotResideIn_DomainOrInfrastructure(line 28). - Where it's used: subclassed in Store and ADC.
EventConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/EventConventionTestsBase.cs:8· Level 4 · abstract class
- What it is: an integration-event convention base (the doc cites ADR-010): every concrete integration event inherits BaseIntegrationEvent, declares an
int SchemaVersion, and lives in a*.IntegrationEventsnamespace in the Shared layer. - Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase).
[Rubric §6, CQRS & Event-Driven]and[Rubric §9, API & Contract Design]assess versioned, discoverable cross-service event contracts. It pairs with IntegrationEventContractTestsBase, which freezes the exact shape. - Walkthrough: three
[Fact]s:IntegrationEvents_ShouldDeclare_SchemaVersion(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/EventConventionTestsBase.cs:13),IntegrationEvents_ShouldInherit_BaseIntegrationEvent(line 16),IntegrationEvents_ShouldResideIn_SharedIntegrationEventsNamespace(line 19). - Where it's used: subclassed in repos that publish integration events (Store, ADC).
FormsConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/FormsConventionTestsBase.cs:15· Level 4 · abstract class
- What it is: a UX-safety fitness function: every admin
*Create.razorform underSource/Modulesmust keep its unsaved-changes guard, dirty tracking, and validatedMudForm, so those protections cannot silently regress. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot,System.IOfile enumeration, AwesomeAssertions. - Concept introduced, the markup-scanning fitness function (it reads
.razortext, not assemblies).[Rubric §24, Forms / Validation / UX Safety]assesses whether navigate-away data loss and missing validation are prevented; the base checks for six literal markers (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/FormsConventionTestsBase.cs:27-35):UnsavedChangesGuard,IsDirtyAccessor(bound through the live accessor to pre-empt the one-render stale-IsDirtylag, a §19 concern),_isDirty,<MudForm,Required="true", andRequiredError. - Walkthrough: the subclass supplies
Map(line 17) and optionally a higherMinimumCreateFormscount (line 24, default 1).AdminCreateForms_KeepUnsavedChangesGuardAndValidation(line 38) resolves the repo root, enumerates*Create.razorunderSource/Modulesexcludingobjandbin(lines 43-48), asserts the discovered count meets the floor (lines 50-51), and records a violation naming each missing marker per form (lines 53-67). - Why it's built this way: self-service forms with no navigate-away step (for example a single-section Profile password or delete form) carry no guard by design and simply must not match the
*Create.razorglob (lines 10-13). - Where it's used: subclassed in repos with admin create forms (Store, ADC).
FrameworkVersionConsistencyTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/FrameworkVersionConsistencyTestsBase.cs:13· Level 4 · abstract class
- What it is: an evolvability and drift fitness function that makes ADR-016 executable: all
MMCA.Common.*packages in a consumer'sDirectory.Packages.propsmust be pinned to one version, so a partial sweep is caught at CI time. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot,System.Xml.Linq(XDocument), AwesomeAssertions. - Concept introduced, enforcing the lockstep release policy.
[Rubric §16, Maintainability]and[Rubric §32, Dependency & Supply-Chain]assess coordinated versioning; the framework releases in lockstep with no phased rollout (ADR-016), and this gate fails if anyMMCA.Common.*entry diverges (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/FrameworkVersionConsistencyTestsBase.cs:3-11). - Walkthrough: the subclass supplies
Map(line 15) and optionallyMinimumCommonPackageCount(line 22, default 13).AllMmcaCommonPackages_ArePinnedToOneVersion(line 25) loadsDirectory.Packages.propsfrom the repo root (lines 27-30), selects everyPackageVersionelement whoseIncludestarts withMMCA.Common.(lines 31-41), asserts the count meets the floor (lines 43-44), asserts none has an empty version (lines 46-48), and asserts the distinct-version count is exactly one, listing what it found (lines 50-56). - Why it's built this way: MMCA.Common itself does not subclass this, because it declares no
MMCA.Common.*pins; only consumers do (lines 9-11). The default of 13 is a deliberately loose floor: the doc points atMMCA.Common/FACTS.mdfor the released package count (15 as of framework v1.121.0,MMCA.Common/FACTS.md:19), and each consumer is expected to override it to its own known count. - Where it's used: subclassed in Store and ADC.
HandlerConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/HandlerConventionTestsBase.cs:8· Level 4 · abstract class
- What it is: the CQRS handler convention base: handlers and validators live only in Application, handlers and services do not broker other handlers, and no
*Serviceexceeds the god-class constructor-arity ceiling. - Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); it adds a
MaxServiceConstructorParametersoverride (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/HandlerConventionTestsBase.cs:12, default 8).[Rubric §6, CQRS]and[Rubric §1, SOLID]apply; the CQRS decorator pipeline itself is taught in primer §2. - Walkthrough: six
[Fact]s:Handlers_ShouldResideIn_ApplicationLayer(line 15),Handlers_ShouldNotInject_OtherHandlers(line 18),ApplicationServices_ShouldNotInject_Handlers(line 21),ApplicationServices_ShouldNotExceed_ConstructorArity(line 24, passing the max),Validators_ShouldResideIn_ApplicationLayer(line 27),EventHandlers_ShouldResideIn_ApplicationLayer_AndBeSealed(line 30). - Where it's used: subclassed in all three repos. ConstructorDependencyCountTestsBase is the narrower, per-repo-pinned version of the arity check.
HandlerResultConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/HandlerResultConventionTestsBase.cs:16· Level 4 · abstract class
- What it is: an opt-in base asserting that every concrete command or query handler's
TResultis Result orResult<T>(or a type derived from them), turning a runtime-only constraint into a build-time gate. - Depends on: IArchitectureMap and ArchitectureRules (
ApplicationLayersDeclareHandlers,CommandHandlersReturnResult,QueryHandlersReturnResult, atMMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.HandlerResults.cs:18,:39,:48). - Concept introduced, closing a deliberately unconstrained generic. The CQRS interfaces carry no compile-time constraint on
TResult(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/HandlerResultConventionTestsBase.cs:6-7), but the decorator pipeline's short-circuit paths (feature gate, validation) fabricate failures through ResultFailureFactory, which throwsInvalidOperationExceptionat runtime for any non-ResultTResult(lines 7-9). A handler with the wrong result type therefore compiles cleanly and only fails when a gate short-circuits it. This base moves that failure to CI.[Rubric §6, CQRS],[Rubric §14, Testability], and[Rubric §15, Best Practices]apply. - Walkthrough: three
[Fact]s.ApplicationLayers_DeclareAtLeastOneHandler(line 21) is the non-vacuity guard the doc calls out (lines 12-13): a mis-pinned assembly cannot make the other two pass by finding nothing.CommandHandlers_Return_ResultTypes(line 24) andQueryHandlers_Return_ResultTypes(line 27) delegate to the matching rules. - Why it's built this way: it is opt-in and map-driven like the rest of the family, so a repo adds it next to its other architecture test classes with the same
Mapand no other wiring. - Where it's used: subclassed in the repos' architecture test projects alongside the other
*Testsclasses.
ImmutabilityTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ImmutabilityTestsBase.cs:8· Level 4 · abstract class
- What it is: an immutability convention base: DTOs, command and query messages, domain events, integration events, and value objects expose no public mutable (non-
init) setter; value objects are additionally sealed and confined to the Shared layer. - Depends on: IArchitectureMap, ArchitectureRules, which uses RuleHelpers
.HasPublicMutableSetterunderneath. - Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); the
init-only versus mutable distinction is exactly whatHasPublicMutableSetterdetects via theIsExternalInitmodifier.[Rubric §15, Best Practices]and[Rubric §4, DDD]assess immutable contracts and value objects. - Walkthrough: five
[Fact]s:Dtos_ShouldBe_Immutable(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ImmutabilityTestsBase.cs:13),CommandsAndQueries_ShouldBe_Immutable(line 16),DomainEvents_ShouldBe_Immutable(line 19),IntegrationEvents_ShouldBe_Immutable(line 22),ValueObjects_ShouldBe_ImmutableSealedAndInShared(line 25). - Where it's used: subclassed in all three repos.
IntegrationEventContractTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/IntegrationEventContractTestsBase.cs:11· Level 4 · abstract class
- What it is: a frozen wire-contract guard: it rebuilds the live integration-event contract (one line per event,
FullName { Prop:Type, ... }) and compares it to a committed snapshot the subclass supplies, so a renamed, removed, or retyped property (or a new event shipped without its consumer) fails the build. - Depends on: IArchitectureMap, ArchitectureRules
.BuildIntegrationEventContract, AwesomeAssertions. - Concept introduced, the snapshot fitness function.
[Rubric §9, API & Contract Design]and[Rubric §7, Microservices Readiness]assess whether cross-service contracts stay stable; because a consumer in another service deserializes by shape, this gate makes any contract change a deliberate, coordinated commit. - Walkthrough: the subclass supplies
Map(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/IntegrationEventContractTestsBase.cs:13) and the committedExpectedContractsnapshot (line 16).IntegrationEventContracts_ShouldMatch_TheFrozenSnapshot(line 19) builds the actual contract (line 21) and assertsactual.Should().Equal(ExpectedContract, ...)(lines 23-28), the message instructing the author to version the event and updateExpectedContractin the same commit when a change is intentional. - Where it's used: subclassed in repos publishing integration events (Store, ADC). It complements EventConventionTestsBase.
LayerDependencyTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/LayerDependencyTestsBase.cs:7· Level 4 · abstract class
- What it is: the Clean Architecture layer-flow base: fifteen
[Fact]s asserting that the map declares the expected layers at all, and that each layer references only layers below it (Domain not on Application, Infrastructure, or API; Application not on Infrastructure or API; Shared on nothing above it; UI only on Shared). - Depends on: IArchitectureMap, ArchitectureRules (
ArchitectureRules.Layers.cs). - Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); this is the runtime half of the two-gate layer enforcement, the compile-time half being
Source/Build/MMCA.Common.LayerEnforcement.targets.[Rubric §3, Clean Architecture]is the whole point. - Walkthrough
- Two overridable declarations come first:
RequiredLayers(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/LayerDependencyTestsBase.cs:16, defaulting to the five core layers Shared, Domain, Application, Infrastructure, Api) andRequiredModuleLayers(line 24, defaulting to the same list and trimmable for a deliberately thin module). - Two non-vacuity
[Fact]s guard the rest:LayerMap_DeclaresEveryExpectedLayer(line 27) andLayerMap_ModulesDeclareEveryExpectedLayer(line 30). Without them a map that forgot an assembly would satisfy every dependency rule by having nothing to check. - Thirteen forbidden-edge
[Fact]s follow, each a one-line delegate onto anArchitectureRules.Layers.csmethod:Domain_ShouldNotDependOn_Application(line 33) throughUi_ShouldNotDependOn_Infrastructure(line 69). The UI trio (lines 63-69) encodes the documented exception that UI depends only on Shared for Blazor WASM compatibility.
- Two overridable declarations come first:
- Where it's used: subclassed in all three repos.
LocalizationResourceTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/LocalizationResourceTestsBase.cs:10· Level 4 · abstract class
- What it is: an opt-in translation-coverage gate (ADR-027): a repo that ships localized
.resxresources subclasses this and lists its required cultures; the build fails if any base.resxunderSource/lacks a complete, non-empty sibling for a required culture. - Depends on: ArchitectureRules
.ResourceTranslationsAreComplete,[Fact]. There is noMapon this base; it scansSource/directly through the rule. - Concept introduced, a coverage fitness function for i18n.
[Rubric §27, i18n]assesses translation completeness; this gate ensures a new English string can never ship without its translation (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/LocalizationResourceTestsBase.cs:3-8). - Walkthrough: the subclass supplies
RequiredCultures(line 13, for example["es"]) and optionallyMinimumBaseResources(line 21, a non-vacuity floor whose default of 0 skips the guard). The single[Fact]Translations_AreComplete_ForEveryRequiredCulture(line 24) passes both to the rule. - Why it's built this way: single-locale repos need not subclass it (the rule is vacuous for an empty list). It pairs with LocalizedTextConventionTestsBase: this gate keeps the extracted resources translated, that gate keeps literals out of markup.
- Where it's used: subclassed in localized repos.
LocalizedTextConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/LocalizedTextConventionTestsBase.cs:13· Level 4 · abstract class
- What it is: a localized-text convention gate (ADR-027): user-visible literals must not be hard-coded in
.razoror.razor.csunderSource/(snackbar messages, pageTitleproperties,<PageTitle>markup, breadcrumb labels) but resolve throughIStringLocalizerresources. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot, ArchitectureRules.UserVisibleTextIsLocalized. - Concept: cross-references the markup-scanning gate idea from FormsConventionTestsBase.
[Rubric §27, i18n]assesses that visible strings follow the selected language. - Walkthrough: the subclass supplies
Map(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/LocalizedTextConventionTestsBase.cs:15) and optionallyMinimumScannedFiles(line 21, default 1) andAllowedFiles(line 28, whole-file exemptions; the preferred exemption is a per-linei18n: allowcomment).UserVisibleText_IsLocalized(line 31) resolves the repo root and delegates to the rule with theSourcedirectory, the allowlist, and the floor (lines 33-37). - Where it's used: subclassed in localized repos. It pairs with LocalizationResourceTestsBase.
MicroserviceExtractionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/MicroserviceExtractionTestsBase.cs:8· Level 4 · abstract class
- What it is: a transport-boundary base for the modular-monolith to microservices path: MassTransit, gRPC, and Protobuf must never leak into Domain, Application, or Shared, so a module behaves identically in-process or extracted and the split stays reversible.
- Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); the extraction invariant (application and domain code talks to abstractions, transport choices live at the edges) is the ADR-006/007/008 story the doc cites (
MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/MicroserviceExtractionTestsBase.cs:3-6).[Rubric §7, Microservices Readiness]assesses exactly this reversibility. - Walkthrough: one
[Fact]CoreLayers_ShouldNotDependOn_Transport(line 13) delegating toArchitectureRules.TransportDoesNotLeakIntoCoreLayers(Map). - Where it's used: subclassed in all three repos.
ModuleIsolationTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ModuleIsolationTestsBase.cs:8· Level 4 · abstract class
- What it is: a modular-monolith boundary base: a module must not reach another module's internal layers; cross-module communication goes only through the Shared (contract) layer. It is vacuous for single-module or module-less repos.
- Depends on: IArchitectureMap, ArchitectureRules, which uses
OtherModuleNamespacesto compute the forbidden targets. - Concept: cross-references the delegating-base shape (AggregateConventionTestsBase).
[Rubric §5, Vertical Slice]and[Rubric §7, Microservices Readiness]assess module autonomy. The IModule system is taught in Group 14. - Walkthrough: six
[Fact]s covering each layer's isolation:ModuleDomains_ShouldBe_Isolated(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/ModuleIsolationTestsBase.cs:13),ModuleApplications_ShouldBe_Isolated(line 16),ModuleInfrastructures_ShouldBe_Isolated(line 19),ModuleApis_ShouldBe_Isolated(line 22), plus the two cross-layer reach rulesModuleDomains_ShouldNotReach_OtherModuleInfrastructures(line 25) andModuleApplications_ShouldNotReach_OtherModuleInfrastructures(line 28). - Where it's used: subclassed in multi-module repos (Store, ADC).
NamingConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/NamingConventionTestsBase.cs:8· Level 4 · abstract class
- What it is: a naming and sealing convention base across the CQRS plus DDD building blocks: handlers, command and query messages, validators, DTOs, domain events, invariants, EF configurations, specifications, and repositories each follow their established suffix and sealing convention.
- Depends on: IArchitectureMap, ArchitectureRules, which uses RuleHelpers
.SimpleNameto match suffixes on generic types. - Concept: cross-references the delegating-base shape (AggregateConventionTestsBase).
[Rubric §15, Best Practices]and[Rubric §16, Maintainability]assess consistent, discoverable naming. - Walkthrough: ten
[Fact]s:Handlers_ShouldBeSealed_WithHandlerSuffix(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/NamingConventionTestsBase.cs:13),Commands_ShouldHave_CommandOrRequestSuffix(line 16),Queries_ShouldHave_QuerySuffix(line 19),Validators_ShouldHave_ValidatorOrRulesSuffix(line 22),SharedDtos_ShouldHave_DtoOrLookupSuffix(line 25),DomainEvents_ShouldBeSealed_InDomainEventsNamespace(line 28),InvariantClasses_ShouldBe_Static(line 31),EfConfigurations_ShouldBeSealed_WithConfigurationSuffix(line 34),Specifications_ShouldBeSealed_WithSpecificationSuffix(line 37),Repositories_ShouldHave_RepositorySuffix(line 40). - Where it's used: subclassed in all three repos.
PiiConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/PiiConventionTestsBase.cs:7· Level 4 · abstract class
- What it is: a GDPR/CCPA right-to-erasure base (ADR-005): any domain entity that declares a PiiAttribute-marked property must implement IAnonymizable, so it has an erasure path.
- Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); the
[Pii]plusIAnonymizablesoft-delete-versus-erasure model is taught in Group 02 (ADR-005).[Rubric §30, Compliance / Privacy]and[Rubric §11, Security]assess erasure discipline. - Walkthrough: one
[Fact]EntitiesWithPiiProperties_ShouldImplement_IAnonymizable(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/PiiConventionTestsBase.cs:12) delegating toArchitectureRules.EntitiesWithPiiImplementAnonymizable(Map). - Where it's used: subclassed in repos with PII-bearing entities (Store, ADC).
RawQueryableConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/RawQueryableConventionTestsBase.cs:30· Level 4 · abstract partial class
- What it is: an opt-in extraction-readiness gate: Application-layer code must not use the repository's raw
IQueryablesurfaces (Table,TableNoTracking,TableNoTrackingSingleQuery,TableNoTrackingSplitQueryon IReadRepository<TEntity, TIdentifierType>), because a handler written against a raw queryable is EF-coupled and its query shape cannot cross a gRPC boundary. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot, ArchitectureAssert,System.IOenumeration, and a source-generatedRegex(System.Text.RegularExpressions). - Concept introduced, the honest textual scan (and its stated limits). The doc is unusually candid about the tradeoff (
MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/RawQueryableConventionTestsBase.cs:13-23): NetArchTest and plain reflection cannot see member usage inside method bodies, and this package deliberately carries no IL or Roslyn dependency, so the rule reads.cstext instead. It cannot see through variable indirection (an interface alias re-exposing the queryable is missed), and because it skips only whole-line//comments, a match inside a string literal or trailing comment is a rare false positive.[Rubric §7, Microservices Readiness]is the invariant being protected;[Rubric §8, Data Architecture]and[Rubric §16, Maintainability]apply to the handler style it pushes toward (focused repository methods, readers, queriers, specifications). - Walkthrough
AllowedFiles(line 38) is the adoption ratchet: a repo with existing violations subclasses, runs once, and moves the reported file names in, so new files stay clean while the list shrinks (lines 24-28).ApplicationSourceDirectories()(line 45) defaults to locating each declared module's Application project directory under the repo'sSource/tree by project name (lines 47-57), and isvirtualfor a custom layout.- The
[Fact]ApplicationLayer_DoesNotUseRawQueryableSurfaces(line 61) first asserts the directory list is non-empty, with a message telling the author to override the directory hook (lines 65-66), then enumerates every.csfile, skippingobj,bin, and allowlisted file names (lines 71-79), and reports throughArchitectureAssert.NoViolations(line 84). ScanFile(line 88) yieldsfileName:lineNumber: trimmed linefor every non-comment line matchingRawQueryableAccessRegex(line 103), a[GeneratedRegex]partial property matching\.Table(NoTracking(SingleQuery|SplitQuery)?)?\bwith a 2000 ms match timeout.
- Why it's built this way: making the offender message carry the file and line (line 98) is what makes a textual gate actionable; combined with the allowlist ratchet it can be adopted in a repo that is not yet clean.
- Where it's used: subclassed opt-in by repos on the extraction path. Because it is a ratchet, a repo's
AllowedFilesoverride is the record of its remaining debt.
SharedLayerTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/SharedLayerTestsBase.cs:7· Level 4 · abstract class
- What it is: a Shared (contract) layer base: a module's Shared is contracts-only, so it must not depend on its own internal layers, on another module's Shared, or on EF Core.
- Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase).
[Rubric §3, Clean Architecture]and[Rubric §5, Vertical Slice]assess a clean contract boundary a would-be extracted consumer can reference safely. - Walkthrough: three
[Fact]s:ModuleShared_ShouldNotDependOn_OwnInternalLayers(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/SharedLayerTestsBase.cs:12),ModuleShared_ShouldBe_Isolated(line 15),ModuleShared_ShouldNotDependOn_EntityFrameworkCore(line 18). - Where it's used: subclassed in multi-module repos (Store, ADC).
SliceCohesionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/SliceCohesionTestsBase.cs:10· Level 4 · abstract class
- What it is: a vertical-slice cohesion base: a use-case slice keeps its command or query, its handler, and its validator together in one namespace, so a feature is a cohesive unit rather than spread across horizontal
Handlers/andValidators/folders. - Depends on: IArchitectureMap, ArchitectureRules.
- Concept: cross-references the delegating-base shape (AggregateConventionTestsBase).
[Rubric §5, Vertical Slice]assesses feature cohesion. The doc notes MMCA.Common scopes to its Notifications slices while ADC and Store scope to their module Application layers (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/SliceCohesionTestsBase.cs:6-8). - Walkthrough: two
[Fact]s:Handlers_ShouldBeCoLocatedWith_TheirContracts(line 15) andValidators_ShouldBeCoLocatedWith_TheirContracts(line 19). - Where it's used: subclassed in all three repos.
SpecificationConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/SpecificationConventionTestsBase.cs:10· Level 4 · abstract class
- What it is: an opt-in base for the Specification pattern in polyglot / database-per-service repos: it guarantees no specification filters by navigating to another entity, which would not translate when that entity lives in a different physical source.
- Depends on: IArchitectureMap, ArchitectureRules
.SpecificationsDoNotNavigateToOtherEntities, backed by CrossEntityNavigationFinder. - Concept: cross-references the delegating-base shape (AggregateConventionTestsBase); the Specification<TEntity, TIdentifierType> pattern is taught in Group 03.
[Rubric §8, Data Architecture]assesses engine-portable query design. - Walkthrough: one
[Fact]Specifications_ShouldNotNavigate_ToOtherEntities(MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/SpecificationConventionTestsBase.cs:16) delegating to the rule. The doc notes single-engine repos need not subclass it (lines 4-8). - Where it's used: subclassed only in polyglot-capable repos.
StateManagementConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/StateManagementConventionTestsBase.cs:17· Level 4 · abstract class
- What it is: a Blazor Server state-management gate: user and session state must live in per-circuit scoped services, never in mutable
staticmembers (which leak one user's state to another) or in singleton-registered stateful services. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot, reflection over the UI assemblies, aSource/file scan, andSystem.Runtime.CompilerServices.CompilerGeneratedAttribute. - Concept introduced, a reflection plus source-scan combined gate.
[Rubric §19, State Management]assesses per-circuit state safety; Blazor Server shares one process across every circuit, so a static member is shared across every user (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/StateManagementConventionTestsBase.cs:5-16). - Walkthrough: the subclass supplies
Map(line 19, whose UI assemblies must be registered underLayer.Ui) and optionallyAllowedStaticMembers(line 25).UiAssemblies_CarryNoMutableStaticState(line 28) reflects overMap.OfLayer(Layer.Ui), first asserting the set is non-empty (lines 32-33), then flagging any declared static field that is notreadonly, notconst, and not compiler-generated, plus any settable static property, minus the exempted members (lines 45-56).UiProjects_RegisterStatefulServicesScoped(line 66) scansSource/.csfiles (skippingobj,bin, non-.UIpaths, andTestingpaths, lines 74-80) for a line containing bothAddSingletonand aStateService/StateContainername, recordingfileName:lineNumberas an offender (lines 85-90).- The private
GetLoadableTypes(line 99) repeats the tolerant load locally rather than using the internal RuleHelpers, andIsCompilerGenerated(line 111) treats any member name containing<as generated.
- Where it's used: subclassed in repos with Blazor Server UI (Store, ADC).
UIArchitectureConventionTestsBase
MMCA.Common.Testing.Architecture ·
MMCA.Common.Testing.Architecture·MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:14· Level 4 · abstract class
- What it is: a UI-architecture convention gate holding the container and presentational split with two mechanical line-count caps: a
*.razor.cscode-behind stays withinMaxCodeBehindLines, and a.razorfile's inline@codeblock stays withinMaxInlineCodeLines. - Depends on: IArchitectureMap, ArchitectureMapBase
.FindRepoRoot,System.IOfile enumeration, AwesomeAssertions. - Concept introduced, enforcing a design convention by file metrics.
[Rubric §18, UI Architecture]assesses the container/presentational discipline; a ballooning code-behind signals page logic that belongs in an injected UI service or an extracted sub-component, and putting a number on it moves the judgement from review to CI (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:3-13). - Walkthrough: the subclass supplies
Map(line 16); the capsMaxCodeBehindLines(line 22, default 400),MaxInlineCodeLines(line 29, default 120),MinimumCodeBehindFiles(line 35, a non-vacuity floor, default 1), andExcludedPathFragments(line 41) are all overridable.CodeBehinds_StayWithinTheLineCap(line 44) enumerates*.razor.cs, asserts the floor (lines 48-49), and flags files over the cap with their line counts (lines 51-59).RazorFiles_KeepInlineCodeBlocksSmall(line 63) finds each.razorfile's@codeline and measures the tail block from there to end of file (line 77), flagging it when it exceeds the inline cap.EnumerateSourceFiles(line 89) drives both, resolving the repo root and excludingobj,bin, and the excluded fragments.
- Caveats / not-in-source: the inline-
@codemeasurement assumes the block is the file's tail (stated in the doc at lines 25-27), so a.razorfile with markup after its@codeblock would over-count. - Where it's used: subclassed in repos with Blazor UI (Store, ADC).
AccessibilityViolationException
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/AccessibilityViolationException.cs:7· Level 0 · class
- What it is: the sealed exception thrown when an axe-core accessibility scan finds one or more WCAG violations on the page under test.
- Depends on:
System.Exception(BCL) only. It is raised by PageExtensions.AssertNoAccessibilityViolationsAsyncand referenced in that method's XML doc (MMCA.Common.Testing.E2E/Infrastructure/AccessibilityViolationException.cs:4). - Concept introduced, the accessibility gate as a hard failure. Rather than logging a warning or returning a result object, a violated a11y scan throws, so a consumer E2E
[Fact]that callsAssertNoAccessibilityViolationsAsyncgoes red and names the offending elements.[Rubric §21, Accessibility]assesses whether accessibility is verified rather than assumed; a dedicated exception type makes an a11y regression a first-class, catchable build failure.[Rubric §28, Front-End Testing]assesses whether the UI is exercised through realistic automated checks; this is the failure primitive those checks throw. - Walkthrough: three constructors, the parameterless, message, and message-plus-inner overloads (
AccessibilityViolationException.cs:10,:15,:21), following the standard exception shape. It carries no extra state; the human-readable violation summary is baked into themessagestring the thrower builds. - Why it's built this way: a purpose-named exception (not a bare
ExceptionorInvalidOperationException) lets a test that deliberately probes a known-inaccessible page assert on exactly this type, and reads clearly in a failure log. - Where it's used: thrown only by PageExtensions
.AssertNoAccessibilityViolationsAsync, which is in turn called by theScanAsync/ScanGridAsynchelpers on E2ETestBase and the*_ShouldHaveNoAccessibilityViolationsfacts on the workflow bases.
AdminCredentials
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/E2ETestConfiguration.cs:66· Level 0 · class
- What it is: a nested static class on E2ETestConfiguration that resolves the seeded admin login (email and password) for E2E runs, with an environment-variable override in front of a per-app default.
- Depends on:
System.Environment(BCL). It is the admin half of the credential pair; UserCredentials is its structurally identical regular-user twin. - Concept: the env-over-default resolution taught in E2ETestConfiguration.
DefaultEmail/DefaultPasswordare settable so a downstream app seeds its own admin identity (via a[ModuleInitializer]), whileE2E_ADMIN_EMAIL/E2E_ADMIN_PASSWORDwin when set.[Rubric §11, Security]assesses how test credentials are handled; keeping them out of source and injectable per environment is the safe end of that. - Walkthrough:
DefaultEmail="admin@localhost"andDefaultPassword="Admin123!"(E2ETestConfiguration.cs:68-69);EmailandPasswordreadE2E_ADMIN_EMAIL/E2E_ADMIN_PASSWORDand fall back to the defaults (:71-75). - Where it's used: read by E2ETestBase
.LoginAsAdminAsync(MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:79-80).
AxeOptions
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/AxeOptions.cs:9· Level 0 · static class
- What it is: the shared axe-core run options that scope every accessibility scan to one documented target, WCAG 2.1 AA, so the gallery and all downstream apps scan against the same rule set.
- Depends on:
Deque.AxeCore.Commons(NuGet:AxeRunOptions,RunOnlyOptions,RuleOptions,AxeOptions.cs:1). - Concept introduced, the scoped accessibility target. A raw axe run also emits "best-practice" advisories that are not conformance failures; pinning
RunOnlyto the WCAG tag set (wcag2a,wcag2aa,wcag21a,wcag21aa) makes the gate fail only on real WCAG 2.1 AA violations.[Rubric §21, Accessibility]assesses whether the accessibility bar is explicit and enforced; freezing the target in one shared object is how three repos stay honest to the same standard.[Rubric §22, Responsive/Cross-Browser]also applies via the pager exception below, which documents a specific cross-component limitation. - Walkthrough: two static presets.
Wcag21Aa(AxeOptions.cs:17) setsRunOnlytoType = "tag"with the four WCAG A/AA tag values (:19-23), the default target for every strict scan.Wcag21AaExceptMudPagerCombobox(:35) is the same tag set plus thearia-input-field-namerule disabled (:42-45), for grid list pages whose only violation is MudBlazor 9.6.0's unlabeledMudTablePager"rows per page" select. The XML doc (:26-34) records this as an accepted upstream limitation, not reachable from app markup, and warns it must be used only where the sole combobox is a pager.
- Why it's built this way: shipping the options in the package (not re-declaring them per test) guarantees every consumer scans the identical rule set; the narrowly scoped pager exception keeps one known third-party gap from forcing a blanket rule-disable across all scans.
- Where it's used: passed to PageExtensions
.AssertNoAccessibilityViolationsAsyncthrough E2ETestBase.ScanAsync(strict) and.ScanGridAsync(pager exception), and directly by the*_ShouldHaveNoAccessibilityViolationsfacts on the workflow bases.
E2ETestConfiguration
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/E2ETestConfiguration.cs:8· Level 0 · static class
- What it is: the single environment-variable-driven configuration surface for the whole E2E package: base URL, headless mode, timeouts, browser engine, slow-motion and trace capture, plus the nested AdminCredentials and UserCredentials.
- Depends on:
System.Environment(BCL) only. - Concept introduced, environment-driven test configuration. Every knob resolves as "read an
E2E_*environment variable, else use a default", so the same compiled test suite runs against localhost on a developer box and against a CI-provisioned host without a code change. A fewDefault*properties are settable so a consuming app can supply app-specific defaults through a[ModuleInitializer]while environment variables always take precedence (E2ETestConfiguration.cs:3-7).[Rubric §17, DevOps]assesses whether the suite is CI-portable and configurable outside the binary; this class is that story.[Rubric §22, Responsive/Cross-Browser]applies becauseBrowserselects the engine CI iterates over. - Walkthrough: teaching order.
DefaultBaseUrl(settable,https://localhost:7108,E2ETestConfiguration.cs:10) andBaseUrl, which prefersE2E_BASE_URL(:12-13).Headlesstreats any value other than"false"as headless (:15-16);SlowMoslows each Playwright action by an env-set millisecond count for visual debugging (:45-46).DefaultTimeout(30_000 ms, fromE2E_TIMEOUT,:18-19) is the general action timeout;AuthTimeout(:27-28) is a separately tunable ceiling for the slowest step, the post-auth wait, inheritingDefaultTimeoutunlessE2E_AUTH_TIMEOUTis set;AuthGraceTimeout(15_000 ms,E2E_AUTH_GRACE,:38-39) is the extra grace window that de-flakes the register/login success-detection race (the transient error-alert flash during a Server-modeforceLoad).Browserselectschromium(default),firefox, orwebkitfromE2E_BROWSER(:53-54);TracePathreturns a non-emptyE2E_TRACEpath or null for full-speed Playwright trace capture (:63-64).
- Why it's built this way: separating
AuthTimeoutandAuthGraceTimeoutfrom the generalDefaultTimeoutis deliberate: the auth round-trip (full sign-in plusforceLoadreload plus re-render) can spike past a normal action budget on a contended CI runner, so it is tuned independently rather than inflating every timeout in the suite. The doc ties the grace window to the TD-06/07 contention cluster (:33-36). - Where it's used: read throughout PlaywrightFixture (engine, headless, slow-mo) and E2ETestBase (base URL, timeouts, trace path, credentials).
LoginPage
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.PageObjects·MMCA.Common.Testing.E2E/PageObjects/LoginPage.cs:6· Level 0 · sealed class
- What it is: the Page Object for the shared
/loginscreen: it exposes the login form's controls as namedILocatorproperties and offersGotoAsync/LoginAsyncactions, so a test saysloginPage.LoginAsync(email, password)instead of hand-querying the DOM. - Depends on:
Microsoft.Playwright(IPage,ILocator,AriaRole) and the PageExtensions helpersGotoAndWaitForBlazorAsyncandFillAndVerifyAsync(LoginPage.cs:1-2). - Concept introduced, the Page Object Model. A Page Object wraps one screen behind an intention-revealing API, locating controls by their accessible name (
GetByLabel("Email"),GetByRole(AriaRole.Button, Name = "Sign in to your account")) rather than by brittle CSS. That keeps tests coupled to what a user sees, not to MudBlazor's internal class names, and it centralizes each selector in one place.[Rubric §28, Front-End Testing]assesses whether E2E tests are maintainable; the Page Object is the canonical pattern for that.[Rubric §21, Accessibility]applies indirectly: locating by role and label only works if the component renders proper accessible names, so the test style pressures accessible markup. - Walkthrough: a private
IPagefield set in the constructor (LoginPage.cs:8-10); locator properties forEmailField,PasswordField,LoginButton,ErrorAlert, and theCreateAccountLink(a MudButton withHref, so it renders as an<a>located by link role,:12-18).GotoAsyncnavigates viaGotoAndWaitForBlazorAsync("/login")(:20-21);LoginAsyncfills both fields through the sharedFillFieldAsyncthen clicks (:23-28). The privateFillFieldAsyncdelegates to PageExtensions.FillAndVerifyAsync(:31-32), guarding the Blazor re-hydration race without a fixed delay. - Where it's used: instantiated by UserLoginTestsBase for the invalid-password, create-account-link, and a11y facts.
ProfilePage
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.PageObjects·MMCA.Common.Testing.E2E/PageObjects/ProfilePage.cs:6· Level 0 · sealed class
- What it is: the Page Object for the authenticated
/profilescreen, exposing the name, address, and password sections' fields and buttons as named locators. - Depends on:
Microsoft.Playwrightand PageExtensions.BlazorNavigateAsync(ProfilePage.cs:1-2). - Concept: the Page Object Model taught in LoginPage. One difference is load-bearing:
GotoAsyncusesBlazorNavigateAsync("/profile")(client-side routing,:34-35), not a full page load, because/profileis[Authorize]and server-side rendering cannot read the JWT from browser storage (a full load would bounce to/login).[Rubric §28, Front-End Testing]and[Rubric §11, Security]both apply: exercising the authenticated page correctly requires respecting the client-token boundary. - Walkthrough: three grouped sets of locators, name (
FirstNameField,LastNameField,SaveNameButton,:13-15), address (AddressLine1FieldthroughCountryFieldplusSaveAddressButton,:18-24), and password (CurrentPasswordField,NewPasswordFieldwithExact = trueto disambiguate from "Confirm New Password",ConfirmNewPasswordField,ChangePasswordButton,:27-30), plus a genericErrorAlertby alert role (:32). This Page Object has no bulk action method; each fact drives the individual locators. - Where it's used: instantiated throughout ProfileManagementTestsBase.
RegisterPage
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.PageObjects·MMCA.Common.Testing.E2E/PageObjects/RegisterPage.cs:6· Level 0 · sealed class
- What it is: the Page Object for the
/registerscreen, exposing the registration form (name, email, password, optional address panel) and aRegisterAsyncaction. - Depends on:
Microsoft.Playwrightand PageExtensions.GotoAndWaitForBlazorAsync/FillAndVerifyAsync(RegisterPage.cs:1-2). - Concept: the Page Object Model taught in LoginPage, applied to a longer form.
PasswordFieldusesGetByLabel("Password", Exact = true)so it does not also match "Confirm Password" (RegisterPage.cs:15), and the optional address fields sit inside an expansion panel located by its text (:23-29).[Rubric §28, Front-End Testing]applies. - Walkthrough: locator properties for the five required fields plus
RegisterButtonandErrorAlert(:12-18), theAlreadyHaveAccountLinksign-in link (:21), and the optional address panel and fields (:24-29).GotoAsyncfull-loads/register(:31-32);RegisterAsyncfills the five required fields via the shared helper (using the same password for confirm) and clicks (:34-42); the privateFillFieldAsyncdelegates to PageExtensions.FillAndVerifyAsync(:48-49). - Where it's used: instantiated by UserRegistrationTestsBase and UserLoginTestsBase.
UserCredentials
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/E2ETestConfiguration.cs:78· Level 0 · class
- What it is: the regular (non-admin) counterpart to AdminCredentials: a nested static class on E2ETestConfiguration resolving the seeded customer login, env-override in front of a per-app default.
- Depends on:
System.Environment(BCL). - Concept: identical in shape to AdminCredentials; only the environment-variable names and defaults differ.
- Walkthrough:
DefaultEmail="user@localhost",DefaultPassword="User123!"(E2ETestConfiguration.cs:80-81);Email/PasswordpreferE2E_CUSTOMER_EMAIL/E2E_CUSTOMER_PASSWORD(:83-87). - Where it's used: read by E2ETestBase
.LoginAsUserAsync(MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:82-83).
WebVitalsSample
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/WebVitalsCollector.cs:73· Level 0 · record
- What it is: an immutable record holding one page's measured Core Web Vitals:
Lcp,Cls,Fcp,Ttfb, andInp(milliseconds, except unitless CLS). - Depends on:
System.Text.Json.Serialization.JsonPropertyName(BCL) for the lowercase wire names. - Concept introduced, the vitals value object. Each property is
init-only with a short JSON name (lcp,cls,fcp,ttfb,inp,WebVitalsCollector.cs:75-83), so the record deserializes directly from thewindow.__vitalsJSON the browser observers accumulate.[Rubric §23, Front-End Performance]assesses whether client-side performance is measured; this record is the typed shape those measurements land in. - Walkthrough: a sealed record with five
initdoubles (WebVitalsCollector.cs:73-84); it has no behavior, it is the deserialization target of WebVitalsCollector.CollectAsync(:53). - Where it's used: produced by WebVitalsCollector
.CollectAsyncand wrapped by WebVitalsArtifact for the JSON artifact.
PageExtensions
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/PageExtensions.cs:19· Level 1 · static class
- What it is: the interactivity toolbox of the E2E package: C#
extension(T)members over Playwright'sIPageandILocatorthat wait for Blazor to become interactive, navigate its InteractiveAuto pages correctly, fill and click through the re-hydration race, and run an axe-core accessibility scan. - Depends on:
Microsoft.Playwright(IPage,ILocator,Assertions),Deque.AxeCore.Playwright/Commons(RunAxe,AxeRunOptions), andSystem.Text.RegularExpressions(PageExtensions.cs:1-5). It throws AccessibilityViolationException. - Concept introduced, waiting for Blazor InteractiveAuto interactivity. The apps render with InteractiveAuto plus prerendering, so a page first appears as static HTML before the WASM runtime (or SignalR circuit) wires event handlers; a click or fill that lands in that window is silently ignored. These helpers replace fixed sleeps with signal-based waits, which is the difference between a flaky suite and a deterministic one.
[Rubric §28, Front-End Testing]assesses whether the suite is reliable against real render timing;[Rubric §21, Accessibility]applies via the axe scan;[Rubric §22, Responsive/Cross-Browser]because the same waits must hold on all three engines. The type also carries theextension(T)DI-style member syntax used across the framework. - Walkthrough: two extension blocks.
extension(IPage page):WaitForBlazorAsyncpolls forwindow.Blazor?._internalthen waits two animation frames plus a short delay for the render pipeline to flush (PageExtensions.cs:27-41);GotoAndWaitForBlazorAsyncnavigates, waits forLoadState.Load(notNetworkIdle, which never settles under a persistent SignalR socket), then waits for interactivity (:47-54);BlazorNavigateAsyncdrives Blazor's client-side router viaBlazor.navigateTo, tolerating the context-destroyed race from aforceLoad, then pollswindow.location.pathnamerather thanWaitForURLAsync(whose default Load wait hangs on same-document nav) (:62-95);GotoProtectedAsyncreaches an[Authorize]page by first ensuring Blazor is up (loading a public page if needed) then client-navigating, because SSR cannot read the JWT from storage (:104-135);WaitForPageAndBlazorAsynccovers a full-page navigation's load-plus-render settle (:141-149);AssertNoAccessibilityViolationsAsyncrunsRunAxe(with optional AxeOptions), and on any violation builds a per-node summary and throws AccessibilityViolationException (:157-182).extension(ILocator locator):FillAndVerifyAsyncfills then auto-waitsToHaveValueAsync, and on a wiped value re-types character-by-character and re-asserts, the single shared fill helper that defeats the re-hydration race (:197-216);ClickAndVerifyAsyncclicks a submit button and polls for the visible effect, re-asserting interactivity and re-clicking a no-op click without double-submitting a successful one (:230-261);ClickAndWaitForUrlAsyncclicks a navigating link and re-clicks until the URL matches, for grid rows whose cells wrap content inMudLink(:273-296).- The private
CompactHtmlcollapses a violating node's markup to one trimmed line for the failure message (:305-314).
- Why it's built this way: the fill and click helpers exist because InteractiveAuto's prerender-then-hydrate model makes a bare fill or click a race on a fast host; auto-waiting assertions with a bounded re-type/re-click are strictly safer than fixed delays (they succeed as soon as the value or effect appears, and only retry a genuine no-op). Two
[SuppressMessage]attributes document analyzer false positives across theextension(T)boundary: CA1708 on the class (:15-18) and IDE0051 onCompactHtml(:301-304). - Where it's used: throughout the Page Objects (LoginPage, ProfilePage, RegisterPage), E2ETestBase (its
FillFieldAsync,ScanAsync,ScanGridAsync), and the workflow bases.
PlaywrightFixture
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:6· Level 1 · sealed class
- What it is: the xUnit collection fixture that owns the Playwright driver and one launched browser for the whole E2E collection, selecting the engine from the environment.
- Depends on:
Microsoft.Playwright(IPlaywright,IBrowser,BrowserTypeLaunchOptions) and xUnit'sIAsyncLifetime(PlaywrightFixture.cs:1-2). It reads E2ETestConfiguration. - Concept introduced, the shared browser fixture. Launching a browser is expensive, so one instance is created once per test collection and shared across every test, rather than per test.
InitializeAsyncbuilds the Playwright driver, mapsE2E_BROWSERto Chromium/Firefox/WebKit (unknown values fall back to Chromium), and launches it with the headless and slow-mo settings;DisposeAsynctears both down.[Rubric §22, Responsive/Cross-Browser]assesses cross-engine coverage; env-selecting the engine here is what lets CI run the identical suite once per browser.[Rubric §14, Testability]applies: sharing one costly resource keeps the suite fast. - Walkthrough:
PlaywrightandBrowserare exposed with private setters (PlaywrightFixture.cs:8-9).InitializeAsynccreates the driver (:13), switches onE2ETestConfiguration.Browser.ToUpperInvariant()to pick the browser type (:17-22), and launches withHeadless/SlowMofrom config (:24-28).DisposeAsyncsuppresses finalization, disposes the browser, then the driver (:31-36). - Where it's used: bound to the collection by E2ETestCollection and injected into every E2ETestBase subclass, which creates a fresh browser context per test off the shared
Browser.
WebVitalsArtifact
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/WebVitalsCollector.cs:87· Level 1 · record
- What it is: the small envelope record written to disk as
web-vitals-{label}.json: aLabel, the pagePath, and the measured WebVitalsSample. - Depends on: WebVitalsSample; serialized with
System.Text.Json. - Concept: the citable-artifact wrapper. Pairing the raw vitals with the label and path they were taken on makes the JSON file self-describing for a CI reviewer.
[Rubric §23, Front-End Performance]assesses whether performance evidence is captured and traceable; the envelope is what makes an uploaded artifact interpretable. - Walkthrough: a three-parameter positional
sealed record(WebVitalsCollector.cs:87), constructed inside WebVitalsCollector.WriteArtifactAsyncand serialized indented (:66-68). - Where it's used: only by WebVitalsCollector
.WriteArtifactAsync.
E2ETestCollection
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:40· Level 2 · sealed class
- What it is: the xUnit
[CollectionDefinition]that binds PlaywrightFixture to the named"E2E"collection, so every E2E test class shares the one launched browser. - Depends on: xUnit's
ICollectionFixture<PlaywrightFixture>and the[CollectionDefinition]attribute (PlaywrightFixture.cs:39-40). - Concept introduced, the xUnit collection fixture binding. A collection fixture is instantiated once and shared by all test classes that opt into the collection by name. This class carries a
public const string Name = "E2E"(PlaywrightFixture.cs:42) used both in its own[CollectionDefinition(Name)]and in each test's[Collection(E2ETestCollection.Name)], so the string is declared once and cannot drift.[Rubric §14, Testability]assesses fixture design; a single named constant binding is the robust way to share a fixture. - Walkthrough: an empty class body carrying the collection definition and the
Nameconstant (PlaywrightFixture.cs:39-43); it exists purely as an xUnit marker. - Where it's used: referenced by E2ETestBase's
[Collection(E2ETestCollection.Name)](MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:7), so every workflow base inherits collection membership.
WebVitalsCollector
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/WebVitalsCollector.cs:17· Level 2 · static class
- What it is: the measurement infrastructure for client-side Core Web Vitals: it installs browser
PerformanceObserverscripts before first paint, reads the accumulated values back off a live page, and writes them as a citable JSON artifact. - Depends on:
Microsoft.Playwright(IPage),System.Text.Json, andSystem.IO(WebVitalsCollector.cs:1-3). It produces WebVitalsSample and WebVitalsArtifact. - Concept introduced, in-browser performance measurement with no third-party JS. Rather than shipping an analytics SDK, it injects a small init script that installs
PerformanceObservers for LCP, CLS, FCP, and INP, each wrapped in try/catch so an engine lacking an entry type leaves that metric at 0 instead of throwing, and accumulates intowindow.__vitals(WebVitalsCollector.cs:23-32). This is the client-side analogue of a backend load test, not a cross-engine field measurement (LCP/CLS are Chromium-only, so on Firefox/WebKit those fields stay 0 and budget assertions pass).[Rubric §23, Front-End Performance]and[Rubric §12, Performance & Scalability]assess whether real user-centric performance is measured; observing the vitals APIs directly, with no network egress, is a self-contained way to do it. The type-level doc states it is only the measurement infrastructure: consumers keep their own budget-asserting tests (:14-15). - Walkthrough:
InstallAsyncregisters the observers viaAddInitScriptAsyncso they are active on the next navigation (WebVitalsCollector.cs:37-41);CollectAsyncevaluates a script that stamps TTFB from Navigation Timing and returnswindow.__vitalsas JSON, deserialized to a WebVitalsSample (:44-54);WriteArtifactAsyncresolves the output directory fromWEB_VITALS_OUTPUT_DIR(orartifacts/under the CWD), wraps the sample in a WebVitalsArtifact, and writesweb-vitals-{label}.jsonindented (:60-69). - Why it's built this way: the observers install before the document's own scripts (via
AddInitScript) so early metrics like FCP are not missed; the per-observer try/catch is what makes the same code run green on all three engines despite Chromium-only metrics. The init script is kept as one concatenated string to stay clear of the MA0136 analyzer (:19-22). - Where it's used: called by the consumer repos' Web-Vitals budget tests (for example ADC's E2E suite), which install, navigate, collect, assert against a budget, and write the artifact for CI upload.
E2ETestBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Infrastructure·MMCA.Common.Testing.E2E/Infrastructure/E2ETestBase.cs:8· Level 3 · abstract class
- What it is: the shared base every E2E test class derives from. It gives each test a fresh isolated browser context and page off the shared PlaywrightFixture browser, plus the load-bearing auth helpers (login, register, deterministic logout cleanup) and the accessibility scan helpers.
- Depends on: PlaywrightFixture, E2ETestConfiguration, AxeOptions, the PageExtensions helpers, xUnit's
IAsyncLifetimeandTestContext, andMicrosoft.Playwright(E2ETestBase.cs:1-3). - Concept introduced, the per-test browser context. The fixture launches one browser; this base opens a new
IBrowserContext(an isolated cookie/storage jar) per test inInitializeAsyncand disposes it inDisposeAsync, so tests do not leak session state into each other (E2ETestBase.cs:19-49). It is the E2E analogue of the integration-test base's per-test database reset.[Rubric §28, Front-End Testing]assesses realistic, isolated UI tests;[Rubric §21, Accessibility]via the scan helpers;[Rubric §14, Testability]via the shared, correctly-sequenced auth helpers that every workflow reuses. The auth-result handling also touches[Rubric §22, Responsive/Cross-Browser]since the same waits must survive Server-mode and WASM render timing on any engine. - Walkthrough: teaching order.
- Lifecycle:
InitializeAsynccreates a context withIgnoreHTTPSErrorsand the base URL, sets the default timeout, optionally starts trace capture whenTracePathis set, and opens thePage(E2ETestBase.cs:19-37);DisposeAsyncstops tracing (writing a failure-only trace whenE2E_TRACEnames a directory), closes the page, and disposes the context (:39-77). - Auth entry points:
LoginAsAdminAsync/LoginAsUserAsync(:79-83) delegate toLoginAsync, which first clears any existing session (covering both localStorage tokens and the HttpOnly session cookie via aDELETE /auth/session-cookie, guarded against the context-destroyed race), navigates to/login, fills throughFillFieldAsync, clicks, then awaitsWaitForAuthResultAsyncandWaitForInteractiveOrReloadAsync(:85-132).RegisterNewUserAsyncgenerates a unique email, fills the register form, submits, and runs the same post-auth waits, returning the created credentials (:134-167). - Post-auth robustness:
WaitForInteractiveOrReloadAsyncwaits for interactivity and, on either aPlaywrightExceptionor aTimeoutException, reloads once and re-waits rather than re-watching a stuck boot (the fix documented against a real contended-runner failure,:169-191);WaitForAuthResultAsyncraces three signals (leaving the auth page, the logout button appearing, or an error alert) so success detection does not depend on the interactive button having hydrated, treating only a persistent on-page error alert as a real failure (:193-224);AuthSucceededWithinGraceAsyncextends that with the grace window (:226-247). - Helpers:
NavigateAndWaitAsync(:249-250), the sharedFillFieldAsyncdelegating to PageExtensions.FillAndVerifyAsync(:257-258),UniqueId(:260), and the two scan helpers:ScanGridAsyncwaits for a data row and zero progressbars then scans with AxeOptions.Wcag21AaExceptMudPagerCombobox(:271-277), whileScanAsyncscans the settled page strictly withWcag21Aa(:281-285).
- Lifecycle:
- Why it's built this way: the auth helpers encode hard-won timing knowledge (the
forceLoadreload, the Server-vs-WASM hydration lag, the cookie-vs-localStorage dual session store) once, so every consumer workflow inherits a deterministic sign-in rather than re-deriving the races. Clearing both token stores is essential: the Blazor Server host is cookie-only, so a localStorage clear alone would leave the next login authenticated as the wrong user (:88-92). The scan split lets grid pages accept the documented pager-combobox exception while every other page stays strict. - Where it's used: the base class of all six workflow bases in this unit (AuthorizationTestsBase, LogoutTestsBase, ProfileManagementTestsBase, UserLoginTestsBase, UserPreferencesTestsBase, UserRegistrationTestsBase) and, through them, every consumer repo's E2E suite.
AuthorizationTestsBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Workflows.Identity·MMCA.Common.Testing.E2E/Workflows/Identity/AuthorizationTestsBase.cs:18· Level 4 · abstract class
- What it is: the reusable authorization workflow fitness base: authored once, re-run as a thin subclass per repo. It asserts anonymous users are redirected off protected paths, public paths stay reachable, a registered non-admin can reach an authenticated page, and a non-admin probing admin routes hits the Forbidden page.
- Depends on: E2ETestBase, PageExtensions (
GotoAndWaitForBlazorAsync,GotoProtectedAsync), AwesomeAssertions, andMicrosoft.Playwright(AuthorizationTestsBase.cs:1-6). - Concept introduced, the authored-once workflow fitness base. The pattern shared by all six bases in this unit: the framework owns the assertions and the SSR/client-navigation mechanics, and each consumer supplies only its own route lists through abstract/virtual members, so identical security behavior is verified across repos without copying test bodies.
[Rubric §11, Security]assesses whether authorization is actually exercised; this base machine-checks both the anonymous-redirect and the authenticated-non-admin-escalation directions.[Rubric §25, Navigation & IA]applies because it pins which routes are public versus gated. - Walkthrough: the subclass supplies
ProtectedPaths,PublicPaths(abstract,AuthorizationTestsBase.cs:26-29), and optionallyAuthenticatedUserPathandAdminPaths(virtual,:35,:44). Four facts:AnonymousUser_ProtectedPages_ShouldRedirectToLoginasserts each protected path bounces to/login(:46-58);AnonymousUser_PublicPages_ShouldBeAccessibleasserts each public path stays put (:60-72);RegisteredUser_AuthenticatedPage_ShouldBeAccessibleregisters a non-admin then client-navigates viaGotoProtectedAsync(SSR cannot read the JWT), passing vacuously when no path is declared (:74-93);RegisteredUser_AdminPages_ShouldBeForbiddenregisters a non-admin then asserts each admin path renders the shared Forbidden page (h1[role='alert']containing "Access Denied"), noting role denial is not a redirect so the page content is the only reliable signal (:95-120). - Why it's built this way: the two optional-path members use a no-dynamic-skip convention (an app with no such page simply passes) because the shipped library deliberately does not reference xunit.v3.assert for a declared skip (
:77-78). The non-empty assertions onProtectedPaths/PublicPaths(:49-50,:63-64) are non-vacuity guards: a repo that declares no paths fails rather than passing silently. - Where it's used: subclassed in each consumer repo's E2E suite (Store, ADC) with that app's route lists.
LogoutTestsBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Workflows.Identity·MMCA.Common.Testing.E2E/Workflows/Identity/LogoutTestsBase.cs:9· Level 4 · abstract class
- What it is: the reusable logout workflow base: it verifies sign-out returns to
/loginand that a logged-out user can no longer reach a protected page. - Depends on: E2ETestBase, PageExtensions (
WaitForBlazorAsync), AwesomeAssertions, andMicrosoft.Playwright(LogoutTestsBase.cs:1-5). - Concept: the authored-once workflow base taught in AuthorizationTestsBase.
[Rubric §11, Security]assesses session teardown; this base guards that logout genuinely revokes access, not just visually returns to a login screen. - Walkthrough: two facts.
Logout_ShouldRedirectToLoginPageregisters, confirms the sign-out button, clicks it, and asserts the sign-in button reappears (LogoutTestsBase.cs:16-29).Logout_ShouldPreventAccessToProtectedPagesregisters, waits for interactivity, then clicks sign-out insideRunAndWaitForResponseAsyncso it blocks until the best-effortDELETE /auth/session-cookiecompletes (:31-51), then re-requests/profileup to six times until the server redirects to/login(:64-72), falling back to a clear URL assertion if it never does (:75). - Why it's built this way: waiting for the cookie-clear response is the fix for a real full-speed race: at speed the test otherwise reaches
/profilebefore the DELETE finishes, so the HttpOnly cookie is still present and SSR re-authenticates. The bounded re-request loop converges deterministically where a slowdown (slow-mo or trace capture) would have hidden the race entirely (:42-46,:57-63). - Where it's used: subclassed in each consumer repo's E2E suite.
ProfileManagementTestsBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Workflows.Identity·MMCA.Common.Testing.E2E/Workflows/Identity/ProfileManagementTestsBase.cs:11· Level 4 · abstract class
- What it is: the reusable profile workflow base: it verifies name, address, and password changes persist, that the profile page loads with the registered data, an opt-in email-change journey, and that the profile page is accessibility-clean.
- Depends on: E2ETestBase, ProfilePage, PageExtensions, AwesomeAssertions, and
Microsoft.Playwright(ProfileManagementTestsBase.cs:1-7). - Concept: the authored-once workflow base taught in AuthorizationTestsBase, here driving a ProfilePage.
[Rubric §24, Forms/Validation/UX Safety]assesses whether edit-and-persist journeys work end to end;[Rubric §21, Accessibility]via the a11y fact. - Walkthrough: a
ProfileSupportsEmailChangevirtual, off by default (ProfileManagementTestsBase.cs:24). Six facts:ChangeName_ShouldUpdateProfileNameandChangeAddress_ShouldUpdateProfileAddressedit, save, reload, and assert persistence (:26-78);ChangePassword_WithValidCurrentPassword_ShouldSucceedchanges the password, then logs out and back in with the new one, waiting for the logoutforceLoad's/loginURL rather thanLoadState.Loadto avoid racing the in-flight navigation (:80-110);ChangeEmail_ShouldUpdateEmailis opt-in and passes vacuously unlessProfileSupportsEmailChangeis overridden true (:112-146);ProfilePage_ShouldLoadWithUserDataasserts the form is pre-filled from registration (:148-166);ProfilePage_ShouldHaveNoAccessibilityViolationsscans with AxeOptions.Wcag21Aa(:168-181). - Why it's built this way: the email-change fact is a declared opt-in rather than a DOM probe because the previous probing version passed vacuously when the field was absent, reporting coverage for a journey the app does not offer; overriding the flag makes a missing field fail loud (
:18-23). The logout-then-login URL wait is called out as the last site still on the racy pattern, fixed to matchUserLoginTestsBase(:100-105). - Where it's used: subclassed in each consumer repo's E2E suite.
UserLoginTestsBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Workflows.Identity·MMCA.Common.Testing.E2E/Workflows/Identity/UserLoginTestsBase.cs:10· Level 4 · abstract class
- What it is: the reusable login workflow base: valid credentials reach the home page and show the authenticated app bar, invalid credentials show an error and stay on
/login, the create-account link navigates to/register, and the login page is accessibility-clean. - Depends on: E2ETestBase, LoginPage, PageExtensions, and
Microsoft.Playwright(UserLoginTestsBase.cs:1-6). - Concept: the authored-once workflow base taught in AuthorizationTestsBase, here driving a LoginPage.
[Rubric §28, Front-End Testing]and[Rubric §11, Security]apply. - Walkthrough: four facts.
Login_WithValidCredentials_ShouldNavigateToHomePageregisters (which auto-logs-in), logs out waiting for the/loginURL (notLoadState.Load, to avoid racing the in-flight logout), logs back in, and asserts the URL left/login, the sign-out button is visible, and the sign-in link is not (UserLoginTestsBase.cs:17-41).Login_WithInvalidPassword_ShouldShowErrordrivesLoginPage.LoginAsyncwith bad credentials and asserts the error alert plus staying on/login(:43-57).Login_NavigateToCreateAccount_ShouldGoToRegisterPageclicks the create-account link and asserts/register(:59-71).LoginPage_ShouldHaveNoAccessibilityViolationsscans with AxeOptions.Wcag21Aa(:73-84). - Why it's built this way: the explicit wait for the logout
forceLoad's/loginURL (:23-28) is the fix (v1.103.1) for the sign-out-then-login race where the pre-login cleanup evaluate died with "execution context was destroyed". - Where it's used: subclassed in each consumer repo's E2E suite.
UserPreferencesTestsBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Workflows.Preferences·MMCA.Common.Testing.E2E/Workflows/Preferences/UserPreferencesTestsBase.cs:21· Level 4 · abstract class
- What it is: the reusable culture-switch and theme-toggle workflow base: it verifies switching to Spanish localizes and persists, toggling dark mode applies and persists, and that both controls are reachable on a mobile viewport.
- Depends on: E2ETestBase, PageExtensions (
GotoAndWaitForBlazorAsync), AwesomeAssertions, andMicrosoft.Playwright(UserPreferencesTestsBase.cs:1-5). - Concept introduced, the self-contained preferences fitness base. Unlike the identity bases it needs no app-specific overrides: the probe page is the shared
/login, the probe string is the localized "Welcome Back"/"Bienvenido de nuevo", and persistence is the anonymous cookie pair (.AspNetCore.Cultureplusmmca_theme), all owned by Common UI in every app (UserPreferencesTestsBase.cs:9-20).[Rubric §27, i18n]assesses whether localization actually switches and persists;[Rubric §20, Design System & Theming]for the theme toggle;[Rubric §22, Responsive/Cross-Browser]for the mobile-parity fact. - Walkthrough: a dark-background probe script accepting either the hex or rgba palette value (
UserPreferencesTestsBase.cs:25-28), and desktop/mobile action-cluster locators scoped by container to disambiguate the duplicated NavMenu controls (:37-39). Three facts:CultureSwitch_ToSpanish_ShouldLocalizeAndPersistpicks Espanol from the language menu and asserts the Spanish probe survives a reload (:41-64);ThemeToggle_ToDark_ShouldApplyAndPersisttoggles dark, asserts the palette variable flipped andmmca_themeis persisted in localStorage, then survives a reload (:66-85);MobileViewport_CultureAndTheme_ShouldBeReachablesets a 390x844 viewport and asserts the controls come from NavMenu's top row (:87-101). - Why it's built this way: the mobile fact pins the v1.103.0 regression where the controls lived only in the app bar, hidden below 1024px; the selectors mirror the gallery's own tests exactly because the MudMenu activator exposes no literal
aria-label, so raw CSS attribute selectors do not match it (:14-19). It cites ADR-027/028 for the localization and theming mechanics. - Where it's used: subclassed in each consumer repo's E2E suite.
UserRegistrationTestsBase
MMCA.Common.Testing.E2E ·
MMCA.Common.Testing.E2E.Workflows.Identity·MMCA.Common.Testing.E2E/Workflows/Identity/UserRegistrationTestsBase.cs:10· Level 4 · abstract class
- What it is: the reusable registration workflow base: valid data navigates to the home page and logs in, mismatched passwords show the validation message and stay on
/register, a duplicate email shows an error, and the register page is accessibility-clean. - Depends on: E2ETestBase, RegisterPage, PageExtensions (
FillAndVerifyAsync), andMicrosoft.Playwright(UserRegistrationTestsBase.cs:1-6). - Concept: the authored-once workflow base taught in AuthorizationTestsBase, here driving a RegisterPage.
[Rubric §24, Forms/Validation/UX Safety]assesses client-side validation and duplicate handling;[Rubric §21, Accessibility]via the a11y fact. - Walkthrough: four facts.
Register_WithValidData_ShouldNavigateToHomePageregisters a unique user and asserts the URL left/registerand the sign-out button is visible (UserRegistrationTestsBase.cs:17-34).Register_WithMismatchedPasswords_ShouldShowErrorfills through the re-hydration-safe helper, submits once, and asserts the inline "Passwords do not match" validation text plus staying on/register(:36-63).Register_WithDuplicateEmail_ShouldShowErrorregisters, then re-registers the same email and asserts the error alert (:65-79).RegisterPage_ShouldHaveNoAccessibilityViolationsscans with AxeOptions.Wcag21Aa(:81-92). - Why it's built this way: the mismatched-passwords fact submits exactly once and asserts the field-level validation text (present in both Server and WASM render modes) rather than re-clicking, because the
[Compare]validation firesOnInvalidSubmitand a re-clicking helper would make the alert flicker out from under the wait (:43-47,:56-60). - Where it's used: subclassed in each consumer repo's E2E suite.
BunitInteractionExtensions
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/BunitInteractionExtensions.cs:12· Level 0 · class
- What it is: A static class of intention-revealing helpers over bUnit's rendered-component API so component tests read as user actions ("click the Save button", "does this text appear") instead of hand-rolled DOM queries. It deliberately prefers accessible visible text over brittle CSS-path selectors (
BunitInteractionExtensions.cs:7-11). - Depends on: bUnit's
IRenderedComponent<TComponent>and itsFindAll/Markupsurface,AngleSharp.Dom.IElement(the DOM node type bUnit exposes), andMicrosoft.AspNetCore.Components.IComponent(the generic constraint). No first-party dependencies. - Concept introduced: C#
extension(T)members applied to a test API. Rather than classicthis-parameter extension methods, this class uses the previewextension<TComponent>(IRenderedComponent<TComponent> cut)block (BunitInteractionExtensions.cs:14-16), the same construct the codebase uses for DI registration (see primer). Every member inside the block readscutas if it were an instance receiver.[Rubric §28 - Front-End Testing]assesses whether the UI has real component-level coverage that is cheap to write and read; grounding assertions in visible text is what keeps those tests resilient to markup refactors. - Walkthrough:
FindButtonByText(text)(line 18) scanscut.FindAll("button")and returns the first<button>whoseTextContentcontainstextcase-insensitively (lines 20-21); on no match it throws anInvalidOperationExceptionthat lists every button text present (lines 22-24), so a failing test names the actual buttons instead of a bare null-reference.ClickButtonByText(text)(line 28) delegates toFindButtonByTextand calls.Click()on the result.HasText(text)(line 32) is a boolean overcut.Markup.Contains(text, StringComparison.OrdinalIgnoreCase)for simple presence assertions. - Why it's built this way: The diagnostic-rich throw (listing available button texts) turns the most common component-test failure, a label that moved, from an opaque null into a self-explaining message, which is the whole point of a shared test-helper layer.
- Where it's used: By every bUnit test class that subclasses BunitComponentTestBase across the MMCA repos.
CapturedRequest
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:129· Level 0 · record
- What it is: An immutable snapshot of one HTTP request a UI service sent through the test handler: method, full URI, absolute path, path plus query, Authorization header, and body text (
CapturingHttpMessageHandler.cs:125-135). - Depends on: BCL
System.Net.Http.HttpMethodandSystem.Urionly. - Concept introduced: The "record the interaction, assert on it later" side of a test double. Where a mock verifies calls inline, this positional
record(line 129) preserves each request so a test can assert on the wire-level shape after the fact.[Rubric §14 - Testability]assesses how observable a component's outbound behavior is under test; capturing the exact Authorization header and serialized body is what lets a test prove the UI service attached the bearer token and posted the right payload. - Walkthrough: Six positional members:
Method,Uri(nullable),Path,PathAndQuery,Authorization(nullable,nullwhen the request carried no Authorization header), andBody(nullable,nullwhen the request had no content). The handler populates it inCaptureAsync(CapturingHttpMessageHandler.cs:81-87), readinguri?.AbsolutePathanduri?.PathAndQueryand the stringifiedrequest.Headers.Authorization. - Where it's used: Exposed as the element type of CapturingHttpMessageHandler's
Requestslist andRequestsFor(...)query.
FreshApiClientFactory
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:73· Level 0 · class
- What it is: An
IHttpClientFactorytest double that returns a brand-newHttpClienton everyCreateClientcall, all wired to one shared handler at a fixed base address. It is a primary-constructor class taking the sharedHttpMessageHandlerand the baseUri(UiHttpServiceHarness.cs:73). - Depends on: BCL
System.Net.Http.IHttpClientFactory/HttpClient/HttpMessageHandler. - Concept introduced: the "fresh instance per call is load-bearing" contract. The real MMCA UI HTTP services dispose their
HttpClientafter each request. A factory that cached and returned the same instance would hand the second call a disposed client. SoCreateClient(name)(line 79) always constructsnew HttpClient(handler, disposeHandler: false)(line 80), ignoring the requested name (typically"APIClient") and passingdisposeHandler: falseso the shared handler outlives each short-lived client.[Rubric §14 - Testability]: matching the production disposal contract in the double is what keeps the test faithful to how the service actually manages its clients. - Walkthrough: A single member,
CreateClient(string name)(line 79), returning a new client on the sharedhandlerwithBaseAddress = baseAddressso services can issue relative URIs. The name argument is accepted for interface compatibility but unused. - Why it's built this way: See the class remarks (
UiHttpServiceHarness.cs:66-72): caching the client would leak a disposed instance into later calls, so a fresh client per call is a correctness requirement, not an optimization. - Where it's used: Constructed inside UiHttpServiceHarness (
UiHttpServiceHarness.cs:47) and offered standalone through HttpTestDoubles.ClientFactory(...).
IsAuthenticatedAuthorizationService
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:111· Level 0 · class
- What it is: A private nested
IAuthorizationServiceused inside BunitComponentTestBase that authorizes any authenticated user and refuses anyone anonymous, regardless of the specific policy or requirement asked for (BunitComponentTestBase.cs:111). - Depends on:
Microsoft.AspNetCore.Authorization.IAuthorizationService/AuthorizationResult/IAuthorizationRequirement, and BCLSystem.Security.Claims.ClaimsPrincipal. - Concept introduced: A coarse authorization stub for component tests. Component tests care about the two branches every
<AuthorizeView>or[Authorize]page has, signed in versus signed out, not about reproducing the app's real policy set. This double collapses all policies to a single question: is the principal authenticated.[Rubric §11 - Security]assesses how authorization is modeled; substituting a real policy evaluator with an is-authenticated check is a deliberate test-time simplification, so a bUnit test verifies a component's render response to authz, not the authz rules themselves (those are pinned by the fitness bases in the overview). - Walkthrough: The requirements overload of
AuthorizeAsync(line 113) returnsAuthorizationResult.Success()whenuser.Identity?.IsAuthenticated == true, elseAuthorizationResult.Failed()(lines 115-117). The policy-name overload (line 119) forwards to the requirements overload with an empty requirement array, so a policy name and a requirement set both resolve to the same authenticated check. - Where it's used: Registered as a singleton
IAuthorizationServicein the BunitComponentTestBase constructor (BunitComponentTestBase.cs:45).
MarkupSnapshotResult
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs:104· Level 0 · record struct
- What it is: The outcome of a MarkupSnapshot comparison: a
readonly record structpairingIsMatch(true when the markup matched the committed baseline, or was refreshed) with a human-readableMessage(MarkupSnapshot.cs:101-104). - Depends on: nothing beyond the BCL.
- Concept introduced: the dependency-free result object. Because
MMCA.Common.Testing.UIships as a NuGet package,MarkupSnapshot.Matchdeliberately returns this value type instead of throwing an assertion-library exception, so the package pulls in no assertion dependency of its own (MarkupSnapshot.cs:11-12). The caller asserts on.IsMatchwith whatever library it already uses, passing.Messageas the failure text.[Rubric §28 - Front-End Testing]: keeping the shipped helper assertion-agnostic is what lets every consumer repo adopt golden-markup testing without a forced test-framework choice. - Walkthrough: Two positional members,
bool IsMatchandstring Message. Areadonly record structgives value semantics with no heap allocation per comparison. - Where it's used: Returned from MarkupSnapshot
.Match(...); the caller readsIsMatch/Message.
MudProviderHandles
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:92· Level 0 · record
- What it is: A protected sealed record nested in BunitComponentTestBase that bundles the three rendered MudBlazor infrastructure providers (popover, dialog, snackbar) so a test can query their markup after opening a dialog or raising a toast (
BunitComponentTestBase.cs:91-95). - Depends on: bUnit's
IRenderedComponent<T>and MudBlazor'sMudPopoverProvider/MudDialogProvider/MudSnackbarProvider. - Concept introduced: The MudBlazor overlay providers render outside a component's own markup subtree, so a component that opens a
MudMessageBoxor raises a snackbar has nowhere to render unless those providers are mounted in the test root first. This record is just the return channel for those three handles.[Rubric §14 - Testability]: exposing the providers as named handles is what lets a test click into a dialog's confirm button or read a toast's text. - Walkthrough: Three positional members,
Popover,Dialog, andSnackbar, each anIRenderedComponent<...>for the corresponding provider. The record is produced byRenderMudProviders()(BunitComponentTestBase.cs:83-89), which renders each provider and wraps the three in this record. - Where it's used: Returned by BunitComponentTestBase
.RenderMudProviders(); tests queryDialogfor message-box markup andSnackbarfor toasts.
MutableAuthenticationStateProvider
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:97· Level 0 · class
- What it is: A private nested
AuthenticationStateProviderinside BunitComponentTestBase whose current principal can be swapped mid-test, notifying listeners each time (BunitComponentTestBase.cs:97-109). - Depends on:
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider/AuthenticationState, and BCLClaimsPrincipal. - Concept introduced: Why mutable rather than a hardcoded-anonymous stub. The class remarks (
BunitComponentTestBase.cs:20-23) explain it is a superset of a fixed provider: it serves both cascadingAuthenticationStateconsumers and pages that callGetAuthenticationStateAsync()on the injected service directly, and it can flip the principal after render to simulate a login/logout during a test.[Rubric §19 - State Management]assesses how auth state flows through the component tree; a single mutable provider that raisesNotifyAuthenticationStateChangedis what makes both the cascade and mid-test transitions observable. - Walkthrough: A primary constructor takes the
initialprincipal into the private_principalfield (lines 97-99).SetPrincipal(principal)(line 101) stores the new principal and callsNotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal)))(line 104) so subscribed<AuthorizeView>andCascadingAuthenticationStatere-evaluate.GetAuthenticationStateAsync()(line 107) returns the current principal wrapped in anAuthenticationState. - Where it's used: Held as the
_authProviderfield in BunitComponentTestBase (BunitComponentTestBase.cs:38), registered as the singletonAuthenticationStateProvider(line 46) and driven bySetUser/RenderAs.
Route
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:110· Level 0 · record
- What it is: A private nested record inside CapturingHttpMessageHandler representing one registered canned response: the matching HTTP method and absolute path plus the status code and optional JSON body to return (
CapturingHttpMessageHandler.cs:110). - Depends on: BCL
System.Net.Http.HttpMethod,System.Net.HttpStatusCode,StringContent, andSystem.Text.Encoding. - Concept introduced: A canned-response registration as an immutable value with a small behavior attached. Beyond holding the match key, the record carries
ToResponse()(line 112) that materializes a freshHttpResponseMessageeach call, attaching the JSON body asStringContentwithapplication/jsonwhen present (lines 114-120).[Rubric §14 - Testability]: building a new response per call is what keeps a canned route reusable across a Polly retry, which would otherwise re-read an already-consumedHttpContent. - Walkthrough: Four positional members,
Method,Path,StatusCode,JsonBody(nullable).ToResponse()createsnew HttpResponseMessage(StatusCode)and, whenJsonBody is not null, setsContentto a UTF-8StringContentof the JSON. - Where it's used: Registered into the handler's
_routeslist bySetResponse(CapturingHttpMessageHandler.cs:56) and matched (last registration wins) inRespond(CapturingHttpMessageHandler.cs:93-99).
TestPrincipal
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/TestPrincipal.cs:6· Level 0 · class
- What it is: A static factory for
ClaimsPrincipalinstances used in bUnit component tests: it mints authenticated principals with the claims the app's pages actually read (TestPrincipal.cs:5-6). - Depends on: BCL
System.Security.Claims.ClaimsPrincipal/ClaimsIdentity/Claim/ClaimTypes. - Concept introduced: The distinction between an anonymous and an authenticated identity in claims-based auth turns on whether the
ClaimsIdentitycarries an authentication type.AuthenticatedUser(line 13) passesauthenticationType: "TestAuth"(line 21) soIdentity.IsAuthenticatedis true (contrast BunitComponentTestBase'sAnonymous, which passes none). The identity carries aClaimTypes.Name, auser_idclaim read by pages such as Identity's Profile, and oneClaimTypes.Roleper supplied role so<AuthorizeView Roles="...">matches (TestPrincipal.cs:15-21).[Rubric §11 - Security]: modeling the exact claim shape the pages consume is what lets a component test exercise the authorized branch faithfully rather than a hand-waved "logged in" flag. - Walkthrough:
AuthenticatedUser(userId = "1", name = "Test User", params string[] roles)(line 13) builds the claim list and returns the authenticated principal.Organizer(userId = "1")(line 25) is a convenience wrapper callingAuthenticatedUserwith theOrganizerrole for the common admin-branch case. - Where it's used: Passed to BunitComponentTestBase
.RenderAs<TComponent>(principal, ...)to render a component as an authenticated (or organizer) user.
BunitComponentTestBase
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/BunitComponentTestBase.cs:33· Level 1 · class
- What it is: The shared base class for bUnit component tests across the MMCA repos. It boots a bUnit render context pre-wired with real MudBlazor services, loose-mode JSInterop, permissive-but-real auth doubles, and localization, so a derived test can render one Blazor component in isolation and drive it (
BunitComponentTestBase.cs:12-32). - Depends on: bUnit's
BunitContext(the v2 base it extends), MudBlazor services (AddMudServices, the popover/dialog/snackbar providers),Microsoft.AspNetCore.Components.Authorization,Microsoft.AspNetCore.Authorization, andMicrosoft.Extensions.Localization(AddLocalization). It composes the nested MutableAuthenticationStateProvider, IsAuthenticatedAuthorizationService, and MudProviderHandles, and pairs with TestPrincipal. - Concept introduced: component testing (the bUnit tier of the pyramid). This is the shared substrate the overview names as moving part #3. A component test renders a single Blazor component with its real dependencies (actual MudBlazor, actual localization) but faked network and auth edges, then asserts on the produced markup. Two setup details are load-bearing:
JSInterop.Mode = JSRuntimeMode.Loose(line 43) so MudBlazor components that probe JS during render return defaults instead of throwing, andAddLocalization()(line 52) so components injectingIStringLocalizer<T>(ADR-027) render localized markup with no per-test setup.[Rubric §28 - Front-End Testing]assesses real component-level coverage;[Rubric §14 - Testability]assesses how cheaply a unit renders in isolation, and registering real MudBlazor with faked edges is the balance this base strikes. - Walkthrough:
Anonymous(line 36) is astatic readonly ClaimsPrincipalover an emptyClaimsIdentity(no authentication type, so not authenticated). The constructor (lines 40-53) callsAddMudServices(), sets loose JSInterop,AddAuthorizationCore(), registers the IsAuthenticatedAuthorizationService singleton and the MutableAuthenticationStateProvider singleton, thenAddLogging()andAddLocalization().SetUser(principal)(line 56) swaps the injected provider's principal mid-test.RenderUnderTest<TComponent>(parameters)(line 59) renders asAnonymous;RenderAs<TComponent>(principal, parameters)(line 65) sets the provider's principal, then renders adding a cascadingAuthenticationStatevalue so both the cascade and the injected provider agree (lines 70-75).RenderMudProviders()(line 83) mounts the three overlay providers and returns them as MudProviderHandles. - Why it's built this way: The class is deliberately pinned to bUnit v2 (the line compatible with xUnit v3 and Microsoft Testing Platform) and isolates every version-specific symbol,
BunitContextandRender<T>, in this one file, so a future bUnit restore that resolves v1.x needs changes only here while derived tests keep callingRenderUnderTest/RenderAs(BunitComponentTestBase.cs:24-31). Driving both the cascade and the injected provider from one principal (rather than picking one) is what makes the base serve both<AuthorizeView>cascades and pages that inject the provider directly. - Where it's used: The base for every bUnit test class in
MMCA.Common.*.Tests,MMCA.ADC.*.Tests, andMMCA.Store.*.Tests, cataloged in the companion rollup.
CapturingHttpMessageHandler
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/CapturingHttpMessageHandler.cs:18· Level 1 · class
- What it is: A canned-response, request-capturing
HttpMessageHandlerfor unit-testing HTTP-backed UI services with no server. It answers requests from registered routes or a responder delegate, returns 404 for anything unmatched, and records every request it saw (CapturingHttpMessageHandler.cs:7-18). - Depends on: BCL
System.Net.Http.HttpMessageHandler,System.Text.Json,HttpStatusCode. It produces CapturedRequest records and holds Route registrations. - Concept introduced: faking the HTTP edge at the handler. UI services take an
IHttpClientFactoryand talk to the WebAPI overHttpClient. Substituting this handler under the client lets a test drive those services with zero network, controlling every response and inspecting every request. Two configuration styles coexist: a responder delegate passed to the ctor (invoked once per request, so repeated calls get fresh responses), or route registration viaSetResponse; registered routes are consulted first and an unmatched request falls through to the responder, or returns a 404 with an empty body, which mirrors the WebAPI's not-found behavior and keeps incidental refresh calls out of each test's setup (CapturingHttpMessageHandler.cs:7-17).[Rubric §14 - Testability]: a single handler that both stubs responses and records requests is what lets one test control inputs and assert outputs at the wire boundary. - Walkthrough: The static
WebJsonoptions (line 20) useJsonSerializerDefaults.Webso serialized bodies match what the WebAPI sends. The parameterless ctor (line 30) selects route-registration mode; theFunc<HttpRequestMessage, HttpResponseMessage>ctor (line 38) selects responder-delegate mode.Requests(line 41) exposes every recorded CapturedRequest in order.SetResponse(method, absolutePath, statusCode, body)(line 48) registers a Route, serializingbodyvia a switch:nullstays null, a rawstringpasses through as-is, any other object is serialized with web defaults (lines 50-56).RequestsFor(method, absolutePath)(line 60) filters the recorded requests by method and case-insensitive path.SendAsync(line 66) captures the request first (awaitingCaptureAsync, lines 72-88, which reads the body and pulls the Authorization header) then callsRespond.Respond(line 90) matches the last route registered for the method and path (last-wins, lines 93-99), else invokes the responder, else returns a 404 (lines 102-107). - Why it's built this way: Building each response fresh (via Route
.ToResponse()or a per-request responder) is required so a Polly retry pipeline in the service under test never reuses a consumedHttpContent(CapturingHttpMessageHandler.cs:14-16); last-registration-wins on routes lets a test override an earlier default without clearing state. - Where it's used: Wrapped by UiHttpServiceHarness, offered directly to tests, and its response bodies are commonly built with HttpTestDoubles.
MarkupSnapshot
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs:21· Level 1 · class
- What it is: A minimal, dependency-free render-snapshot (golden-markup) regression helper for bUnit tests. It captures a component's rendered markup, normalizes the non-deterministic per-render bits MudBlazor injects, and compares against a committed baseline under a
Snapshots/folder next to the calling test (MarkupSnapshot.cs:6-20). - Depends on: BCL
System.Runtime.CompilerServices([CallerFilePath]),System.Text.RegularExpressions(source-generated regexes), andSystem.IO. Returns a MarkupSnapshotResult. - Concept introduced: golden-markup snapshot testing. Instead of pixel screenshots (OS-dependent, per-platform golden management), this compares normalized markup, which is deterministic and OS-independent, so it runs identically on every CI platform (
MarkupSnapshot.cs:14-16). A committed.htmlbaseline is the golden; an unintended structural change to a shared primitive fails the build. The workflow is review-and-commit:UPDATE_SNAPSHOTS=1rewrites baselines after an intentional change, and a missing baseline is written but reported as a non-match so a regression can never slip through on an absent snapshot (MarkupSnapshot.cs:16-19).[Rubric §28 - Front-End Testing]: markup snapshots are the low-cost regression net for the shared UI primitives. - Walkthrough:
Match(markup, snapshotName, [CallerFilePath] callerFilePath)(line 31) guards its inputs, normalizes the markup, and resolves the baseline path asSnapshots/{snapshotName}.htmlnext to the caller (lines 36-39). IfUPDATE_SNAPSHOTS=1it writes the actual and returns a match (lines 41-46); if the baseline is absent it writes it and returns a non-match with a review-and-commit message (lines 48-54); otherwise it reads the expected, normalizes line endings, and returns match or a diff message (lines 56-59).Normalize(line 64) collapses per-render GUIDs, both dashed and 32-char forms, to a stable{guid}token and trims trailing whitespace so the comparison reacts only to real markup changes (lines 66-69).BuildDiffMessage(line 72) walks lines to report the first differing line with expected/actual text (lines 78-88). Two source-generated regexes,GuidRegex(line 95) andHex32Regex(line 98), do the GUID normalization. - Why it's built this way: Normalizing MudBlazor's per-render element-id and ARIA GUIDs is what makes the comparison stable; without it every render would differ and the test would be worthless. Keeping the helper assertion-library-free (returning MarkupSnapshotResult) is what lets the shipped package impose no test-framework choice on consumers.
- Caveats / not-in-source: The baseline location depends on
[CallerFilePath]resolving to the source path present at compile time; snapshots are meant to be run and refreshed from the repo checkout that compiled the tests.
StubTokenStorageService
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/StubTokenStorageService.cs:13· Level 1 · class
- What it is: A canned ITokenStorageService for UI HTTP-service tests. It returns fixed access/refresh tokens (which the services attach as the Bearer header) with no platform storage, and its token values mutate through set/clear so login/logout flows can be asserted (
StubTokenStorageService.cs:5-12). - Depends on: ITokenStorageService from
MMCA.Common.UI.Services.Auth. - Concept introduced: A stateful test double that is also fault-injectable. Beyond returning canned tokens,
AccessTokenProvider(line 36) is a mutable delegate backingGetAccessTokenAsync, so a test can swap in a throwing delegate to simulate the prerender window where JS-interop storage access is unavailable (StubTokenStorageService.cs:8-10,31-35).[Rubric §26 - Front-End Security]assesses how auth tokens are handled; the double lets a test exercise the UI service's token-attachment and its failure handling without real secure storage. - Walkthrough: The ctor (line 18) seeds
AccessToken/RefreshToken(defaults"test-token"/"test-refresh-token",nullfor an anonymous client) and setsAccessTokenProviderto return the currentAccessToken.GetAccessTokenAsync()(line 39) invokes the provider delegate;GetRefreshTokenAsync()(line 42) returnsRefreshToken.SetTokensAsync(accessToken, refreshToken)(line 45) andClearTokensAsync()(line 53) mutate the canned values (clear sets both tonull) so a test can assert the post-login/post-logout state viaAccessToken/RefreshToken. - Where it's used: Exposed as
TokenStorageon UiHttpServiceHarness (UiHttpServiceHarness.cs:61) and built by HttpTestDoubles.TokenStorage(...).
UiHttpServiceHarness
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/UiHttpServiceHarness.cs:12· Level 2 · class
- What it is: A disposable facade that owns the whole HTTP plumbing a UI HTTP-service test needs: the capturing handler, an
IHttpClientFactorythat hands out a fresh client per call, and a fixed-token storage stub, all on a shared base address (UiHttpServiceHarness.cs:3-12). - Depends on: CapturingHttpMessageHandler, FreshApiClientFactory, and StubTokenStorageService; implements BCL
IDisposable. - Concept introduced: the harness (assemble the doubles once, hand a service its edges). Where the individual doubles are the parts, this is the one object a test constructs to get a ready-to-inject set. It mirrors CapturingHttpMessageHandler's two configuration styles through two public ctors, then wires the client factory and token stub around one shared handler.
[Rubric §14 - Testability]: collapsing the setup to a single harness (and a singleDispose) is what keeps each HTTP-service test's arrange block small and consistent. - Walkthrough:
DefaultBaseAddress(line 15) ishttps://gateway.test/so services can use relative URIs. The route-mode ctor (line 23, defaultaccessToken = "test-token") constructs a plain CapturingHttpMessageHandler; the responder-mode ctor (line 35) constructs one from aresponddelegate. Both chain to the private ctor (line 43) that storesHandler, resolvesBaseAddress(falling back to the default), builds a FreshApiClientFactory on the handler and address, and a StubTokenStorageService with the token. The propertiesHandler(line 52),BaseAddress(line 55),ClientFactory(line 58, typedIHttpClientFactory), andTokenStorage(line 61, typed StubTokenStorageService so tests can mutate it) expose the wired pieces.Dispose()(line 63) disposes the shared handler. - Why it's built this way: A single owned handler behind both the client factory and the recorder means a test configures responses in one place and reads requests in the same place; the fresh-client-per-call factory is what keeps the harness compatible with services that dispose their clients (see FreshApiClientFactory).
- Where it's used: Constructed by UI HTTP-service test classes across the repos; tests that wire the pieces individually instead use HttpTestDoubles.
HttpTestDoubles
MMCA.Common.Testing.UI ·
MMCA.Common.Testing.UI·MMCA.Common.Testing.UI/Infrastructure/HttpTestDoubles.cs:12· Level 3 · class
- What it is: A static class of factory helpers for UI HTTP-service tests: standalone client-factory and token-storage doubles for tests that wire the pieces by hand rather than through UiHttpServiceHarness, plus the canned-response builders both styles share (
HttpTestDoubles.cs:7-12). - Depends on: FreshApiClientFactory, StubTokenStorageService, ITokenStorageService, UiHttpServiceHarness (for
DefaultBaseAddress), and BCLSystem.Net.Http.Json(JsonContent). - Concept introduced: The a-la-carte counterpart to the all-in-one harness. Some tests need only a response builder or only a client factory, so this class exposes the same building blocks as free functions.
[Rubric §14 - Testability]: offering both a bundled harness and loose factories keeps the setup ergonomics right whether a test wants everything or one piece. - Walkthrough:
BaseAddress(line 15) reuses UiHttpServiceHarness.DefaultBaseAddressso both styles share one base URL.ClientFactory(handler, baseAddress)(line 23) returns a FreshApiClientFactory on the given handler.TokenStorage(accessToken)(line 28) returns a StubTokenStorageService. The response builders:JsonResponse<T>(payload, statusCode = OK)(line 33) wraps aJsonContent.Create(payload)(web serializer defaults);EmptyResponse(statusCode = NoContent)(line 37) is a body-less response;ProblemResponse(detail, title = "Domain Exception", statusCode = BadRequest)(line 44) emits a ProblemDetails-shaped{ title, detail }body the way the WebAPI emits domain failures, so the UI-side error mapping sees the shape it expects. - Why it's built this way:
ProblemResponsedeliberately mirrors the WebAPI's domain-failure envelope so a test can prove the UI service's error path (for example aServiceExceptionHelper) parses real failure shapes, not an invented one (HttpTestDoubles.cs:40-43). - Where it's used: By UI HTTP-service test classes that hand-wire doubles or build canned responses for a CapturingHttpMessageHandler responder delegate.
GalleryFakeAuthenticationHandler
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/GalleryFakeAuthenticationHandler.cs:19· Level 0 · class
- What it is: A cookie-toggled fake ASP.NET Core authentication handler for the backend-less gallery. A request that carries
gallery_auth=1authenticates as a fixed "Gallery Visitor" principal; every other request stays anonymous (MMCA.Common.UI.Gallery/Stubs/GalleryFakeAuthenticationHandler.cs:30-39). - Depends on:
Microsoft.AspNetCore.Authentication.AuthenticationHandler<AuthenticationSchemeOptions>(the abstract base),IOptionsMonitor<AuthenticationSchemeOptions>,ILoggerFactory, andSystem.Text.Encodings.Web.UrlEncoder(the base constructor triple, lines 20-23), plus BCLSystem.Security.Claims. No first-party dependencies. - Concept introduced: the "backend-less gallery stub" pattern, and its one non-inert member. The gallery host (see GalleryHost) renders the real
MMCA.Common.UIcomponents with no live API behind them, so every consumer-supplied service the shared UI expects is replaced by a benign stub. This type is the exception that proves the rule: it is not inert, because two of the scanned page families carry a real[Authorize].MapRazorComponentssurfaces that attribute as endpoint metadata, and the authorization middleware then requires a genuine authentication scheme to be registered (without one it throws) and a genuine authenticated principal (without one the pages redirect to/logininstead of rendering). The doc comment at lines 8-18 records exactly that reasoning. Rather than removing the guard for testability, the gallery supplies a real scheme whose only decision input is a cookie.[Rubric §28 - Front-End Testing]assesses whether the UI has real-browser render and a11y coverage; toggling sign-in per test with a cookie is what lets one host scan both the anonymous chrome (/login,/register,/components) and the signed-in guarded pages.[Rubric §11 - Security]assesses how authentication is implemented; note the deliberate inversion here, the handler trusts an unsigned cookie value, which is acceptable only because this assembly is unpackaged test infrastructure (the doc comment closes at line 17 with "never copy into a real host"). - Walkthrough: Two internal constants pin the contract shared with the host and the tests:
SchemeName = "GalleryFake"(line 25) andCookieName = "gallery_auth"(line 26).HandleAuthenticateAsync()(line 28) is the single override. It first short-circuits: ifRequest.Cookies["gallery_auth"]is not exactly"1"it returnsAuthenticateResult.NoResult()(lines 30-33), which means "this scheme has no opinion", leaving the request anonymous rather than failing it. Otherwise it builds aClaimsIdentitycarrying oneClaimTypes.Nameclaim of"Gallery Visitor"and, critically, passesSchemeNameas the authentication type (lines 35-37): supplying an authentication type is what makesIdentity.IsAuthenticatedtrue. It wraps that principal in anAuthenticationTicketand returnsAuthenticateResult.Success(lines 38-39). Everything is synchronous, returned throughTask.FromResult, so no I/O occurs. - Why it's built this way: Keeping the guard real and faking only the credential means the E2E scan exercises the same authorization pipeline the deployed hosts run, so an accidental loss of
[Authorize]on the shared notification pages is not papered over by a permissive test host. - Where it's used: Registered as the default (and only) authentication scheme by GalleryHost at
MMCA.Common.UI.Gallery/GalleryHost.cs:69-72, followed byAddAuthorization()at line 73. The cookie is set on the Playwright browser context byNotificationPagesE2ETests.SeedSignedInCookieAsync(MMCA.Common.UI.E2E.Tests/NotificationPagesE2ETests.cs:63-70) before each of the three guarded-page scans.
NullTokenRefresher
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/NullTokenRefresher.cs:9· Level 1 · class
- What it is: An ITokenRefresher implementation that has no session to refresh. The gallery has no API to refresh a token against; the stub exists only so the DI graph stays complete (doc comment, lines 5-8).
- Depends on: ITokenRefresher from
MMCA.Common.UI.Services.Auth. Nothing else. - Concept introduced: A pure instance of the backend-less gallery stub pattern introduced in GalleryFakeAuthenticationHandler: replace an outbound boundary with a constant.
[Rubric §14 - Testability]assesses how cheaply a component can be exercised in isolation; the token-refresh boundary collapses to a constant so the refresh plumbing the shared UI registers never contacts a token endpoint during the scan. - Walkthrough: One member.
AcquireAccessTokenAsync(CancellationToken = default)(line 11) is an expression body returningTask.FromResult<string?>(null): no token, therefore no refresh attempt, and callers take their null-token path. - Where it's used: Registered scoped by GalleryHost at
MMCA.Common.UI.Gallery/GalleryHost.cs:61, ahead ofAddUIShared.
NullTokenStorageService
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/NullTokenStorageService.cs:10· Level 1 · class
- What it is: An empty ITokenStorageService: there is no stored session in the gallery. It exists so the
AuthDelegatingHandlerthatAddUISharedregisters resolves cleanly, even though it is never actually invoked because the gallery makes no API calls (doc comment, lines 5-9). - Depends on: ITokenStorageService from
MMCA.Common.UI.Services.Auth. - Concept introduced: The same stub pattern as NullTokenRefresher, covering storage rather than refresh.
[Rubric §26 - Front-End Security]assesses how tokens are stored and handled; this stub deliberately holds nothing, so no credential material is ever persisted by the test host, not even in memory. - Walkthrough: Four members, each inert but shape-complete so DI binds:
GetAccessTokenAsync()andGetRefreshTokenAsync()(lines 12, 14) returnTask.FromResult<string?>(null);SetTokensAsync(accessToken, refreshToken)(line 16) andClearTokensAsync()(line 18) discard their inputs and returnTask.CompletedTask. - Where it's used: Registered scoped by GalleryHost at
MMCA.Common.UI.Gallery/GalleryHost.cs:60, beforeAddUISharedso the shared UI'sTryAdd*registration defers to this stub.
GalleryUIModule
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/GalleryUIModule.cs:13· Level 3 · class
- What it is: A minimal IUIModule whose
Assemblyis the gallery itself, so the shared Blazor Router (Routes.razor, which scansUIModules.Select(m => m.Assembly)) discovers the gallery's own/componentsshowcase page. Its nav items make the host browsable when run interactively (doc comment, lines 8-12). - Depends on: IUIModule and NavItem from
MMCA.Common.UI.Common, plusMudBlazor.Iconsand BCLSystem.Reflection.Assembly. - Concept introduced: The UI module contribution pattern (introduced with IUIModule in the Common UI framework group) reused here for a test host: a module contributes route-bearing assemblies and nav links to the shared shell.
[Rubric §18 - UI Architecture]assesses how the front end composes independently-owned UI slices; the gallery participates in the exact same module-discovery mechanism the real apps use, which is what makes the scan evidence about that mechanism rather than about a bespoke test shell.[Rubric §25 - Navigation & IA]: the three nav entries flow through the sameNavItemcontract the deployed apps' menus are built from. - Walkthrough:
NavItems(line 15) is a collection-expressionIReadOnlyList<NavItem>of three entries, Login (/login), Register (/register), and Components (/components), each pairing a label, a route, and a MudBlazor Material icon (lines 17-19).Assembly(line 22) is an expression-bodied property returningtypeof(GalleryUIModule).Assembly, so the Router additionally scans the gallery assembly for routable components, which is howPages/ComponentsGallery.razorbecomes reachable. - Where it's used: Registered as a singleton
IUIModuleby GalleryHost atMMCA.Common.UI.Gallery/GalleryHost.cs:85.
StubNotificationInboxUIService
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/StubNotificationInboxUIService.cs:11· Level 3 · class
- What it is: A canned INotificationInboxUIService that returns fixed inbox data so
NotificationBelland the notification inbox page render populated, real markup for the axe and render E2E scans, with no backend. - Depends on: INotificationInboxUIService from
MMCA.Common.UI.Services.Notifications, the PagedCollectionResult<T> and PaginationMetadata result types fromMMCA.Common.Shared.Abstractions, the UserNotificationDTO contract, and theUserNotificationIdentifierTypealias. - Concept introduced: The stub pattern extended from inert no-ops to canned data. For components whose whole purpose is displaying content, an empty stub would render an empty (and therefore untested) tree, so this stub returns representative rows instead.
[Rubric §28 - Front-End Testing]: populated markup is what lets axe evaluate contrast, roles, and the read/unread affordances against a realistic notification list rather than an empty state. - Walkthrough:
GetInboxAsync(pageNumber = 1, pageSize = 20, cancellationToken)(lines 13-14) builds a two-itemUserNotificationDTO[](lines 16-29): an unread "Welcome to MMCA" with a fixed UTCSentOnof 2026-01-02 09:00, and a read "Scheduled maintenance" carrying bothReadOnandSentOn, so the inbox exercises both visual states. It wraps them in aPagedCollectionResult<UserNotificationDTO>withnew PaginationMetadata(items.Length, pageSize, pageNumber)(lines 30-31), so the pager renders from real metadata.GetUnreadCountAsync()(line 34) returns a constant3so the bell badge renders non-empty.MarkReadAsync(id, ct)(line 36) andMarkAllReadAsync(ct)(line 39) are no-ops returningTask.CompletedTask, so the buttons are present and clickable without any state change. - Where it's used: Registered scoped by GalleryHost at
MMCA.Common.UI.Gallery/GalleryHost.cs:79, alongside the scoped NotificationState (line 78). The scan that consumes it isNotificationPagesE2ETests.NotificationInbox_Renders_AndHasNoWcag21AaViolations(MMCA.Common.UI.E2E.Tests/NotificationPagesE2ETests.cs:36-44), which asserts the "Welcome to MMCA" row is visible before running axe. - Caveats / not-in-source: The badge count
3(line 34) is a hard-coded display value and does not reconcile with the two rowsGetInboxAsyncreturns; it exists purely to render a non-empty badge for the scan, not to be internally consistent.
StubPushNotificationUIService
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/StubPushNotificationUIService.cs:11· Level 3 · class
- What it is: A canned IPushNotificationUIService so the notification history and compose pages render populated, real markup for the axe and render E2E scans, with no backend.
- Depends on: IPushNotificationUIService from
MMCA.Common.UI.Services.Notifications, PagedCollectionResult<T> and PaginationMetadata, plus the PushNotificationDTO and SendPushNotificationRequest contracts. - Concept introduced: The same canned-data variant of the stub pattern as StubNotificationInboxUIService, applied to the send and history side.
[Rubric §24 - Forms/Validation/UX Safety]: the compose page is a form, and echoing the submittedTitle/Bodyback in the returned DTO lets the render smoke exercise the post-submit state without a real send. - Walkthrough:
SendAsync(request, cancellationToken)(lines 13-14) is an expression body returning a singlePushNotificationDTOthat echoesrequest.Titleandrequest.Bodyand fixes the rest:Id = 99,SentByUserId = 1,RecipientCount = 42,Status = "Sent",CreatedOn2026-01-04 10:00 UTC (lines 15-19).GetHistoryAsync(pageNumber = 1, pageSize = 10, cancellationToken)(lines 21-22) builds a two-item history array (lines 24-36): one row withStatus = "Sent"and one withStatus = "Failed", both withRecipientCount = 128, so the history table renders both status treatments, then wraps them in aPagedCollectionResult<PushNotificationDTO>withPaginationMetadata(lines 37-38). - Where it's used: Registered scoped by GalleryHost at
MMCA.Common.UI.Gallery/GalleryHost.cs:80. The scans that consume it areNotificationHistory_Renders_AndHasNoWcag21AaViolationsandNotificationCompose_Renders_AndHasNoWcag21AaViolations(MMCA.Common.UI.E2E.Tests/NotificationPagesE2ETests.cs:20-57); the history scan runs under theWcag21AaExceptMudPagerComboboxaxe options because the MudBlazor 9.6.0 pager combobox has no accessible name and is not fixable from app markup (MMCA.Common.UI.E2E.Tests/NotificationPagesE2ETests.cs:29-32).
NoOpAuthUIService
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/NoOpAuthUIService.cs:12· Level 6 · class
- What it is: A no-op IAuthUIService for the backend-less gallery. The gallery renders the real Login and Register pages for a11y and render-smoke scanning only, so every operation returns a benign default.
- Depends on: IAuthUIService from
MMCA.Common.UI.Services.Auth, and the LoginRequest, RegisterRequest, and AuthenticationResponse contracts fromMMCA.Common.Shared.Auth. - Concept introduced: registration order as the override mechanism. This stub is registered before
AddUIShared, whose internalTryAddScoped<IAuthUIService, AuthUIService>()then defers to it, exactly as the class doc comment states (lines 6-11).TryAdd*is first-registration-wins, so a test host overrides only the boundaries it names and inherits every other registration the shared UI makes, with no fork of the composition root.[Rubric §11 - Security]assesses how authentication is handled; here the client-side auth boundary is neutralized entirely so the scan touches the real login and register markup without any credential flow.[Rubric §14 - Testability]: substituting the top-level UI auth service for constants is what makes those pages renderable in isolation. - Walkthrough:
LastError(line 14) is alwaysnull, so no error alert renders.LoginAsync,RegisterAsync, andExchangeOAuthCodeAsync(lines 16, 19, 22) each returnTask.FromResult<AuthenticationResponse?>(null), meaning "no authenticated session" on the interface's own terms.LogoutAsync()(line 25) returnsTask.CompletedTask.TryRefreshTokenAsync(ct)(line 27) andChangePasswordAsync(currentPassword, newPassword, ct)(line 30) both returnTask.FromResult(false). Every path is inert but shape-complete, so the pages bind, render, and stay in their signed-out state for the axe pass. - Why it's built this way: Placing the stub ahead of
AddUISharedexploits the shared UI'sTryAdd*idempotence rather than requiring the shared registration extension to grow test hooks, which keeps the production DI code free of test-only branches. - Where it's used: Registered scoped by GalleryHost at
MMCA.Common.UI.Gallery/GalleryHost.cs:59, the first of the pre-AddUISharedstub registrations.
GalleryAuthenticationStateProvider
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery.Stubs·MMCA.Common.UI.Gallery/Stubs/GalleryAuthenticationStateProvider.cs:16· Level 7 · class
- What it is: The gallery's Blazor
AuthenticationStateProvider. It mirrors the request's authentication in both render phases, soAuthorizeViewandCascadingAuthenticationStateagree with what GalleryFakeAuthenticationHandler decided for the request. Without thegallery_authcookie both phases yield anonymous, preserving the deliberate signed-out chrome of the login, register, and components scans (doc comment, lines 6-15). - Depends on:
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider(the abstract base) andIHostEnvironmentAuthenticationStateProvider(the interface the Blazor Server host calls into),IHttpContextAccessorinjected as a primary-constructor parameter (line 16), and BCLClaimsPrincipal/ClaimsIdentity. - Concept introduced: the two render phases of interactive-server Blazor. A page first renders as static SSR inside the HTTP request, then, once the circuit connects, re-renders interactively over a WebSocket where there is no ambient
HttpContext. An auth-state provider therefore has to answer correctly in two different worlds. This class handles both: SSR reads the request user throughIHttpContextAccessor, and for the interactive circuit the framework pushes the handshake user in throughIHostEnvironmentAuthenticationStateProvider.SetAuthenticationState. The doc comment (lines 7-9) records that this replaced a former always-anonymous stub, which could not represent the signed-in state the guarded notification pages now need.[Rubric §19 - State Management]assesses how client state is owned and propagated; auth state is the canonical cascading state, and this shows the two supply routes it has under interactive server rendering.[Rubric §28 - Front-End Testing]: getting both phases right is what stops a guarded page from flipping to a signed-out tree mid-scan and producing a false axe pass on the wrong markup. - Walkthrough:
Anonymous(lines 19-20) is astatic readonly AuthenticationStatewrapping an emptyClaimsPrincipal(new ClaimsIdentity()), unauthenticated precisely because no authentication type is supplied (contrast GalleryFakeAuthenticationHandler, which passes one)._hostState(line 22) is the nullable task the framework may have pushed in.GetAuthenticationStateAsync()(line 24) checks_hostStatefirst and returns it verbatim when present (lines 26-29): the circuit's handshake user always wins. Otherwise it falls back to the SSR path, readinghttpContextAccessor.HttpContext?.User(line 31) and returning a newAuthenticationState(user)only whenuser?.Identity?.IsAuthenticated == true, else the sharedAnonymous(lines 32-34). Both branches useTask.FromResult, so no async machinery is allocated per call.SetAuthenticationState(Task<AuthenticationState>)(lines 37-38) is theIHostEnvironmentAuthenticationStateProviderimplementation and simply stores the task; note that it does not callNotifyAuthenticationStateChanged, because the framework sets it before the first interactive render. - Why it's built this way: The gallery deliberately mirrors rather than fabricates. Deriving the component-tree state from whatever the request actually authenticated as keeps one source of truth (the cookie) for the middleware, the endpoint authorization, and the render tree, so a scan cannot land in a split-brain state where the endpoint admitted the request but the tree still renders signed-out.
- Where it's used: Registered scoped as the
AuthenticationStateProviderby GalleryHost atMMCA.Common.UI.Gallery/GalleryHost.cs:63, immediately afterAddHttpContextAccessor()(line 62) which supplies its dependency. - Caveats / not-in-source: The class implements
IHostEnvironmentAuthenticationStateProviderbut is registered only under theAuthenticationStateProviderservice type (MMCA.Common.UI.Gallery/GalleryHost.cs:63). Whether the Blazor Server host resolves the same instance when it callsSetAuthenticationStateis framework behavior and is not determinable from this repository's source.
GalleryHost
MMCA.Common.UI.Gallery ·
MMCA.Common.UI.Gallery·MMCA.Common.UI.Gallery/GalleryHost.cs:21· Level 8 · class
- What it is: A static builder that assembles the entire backend-less Blazor gallery host. It renders the real
MMCA.Common.UIauth pages (/login,/register), the shared notification pages (/notifications,/notifications/inbox,/notifications/send), and a primitives showcase (/components), all against stub implementations of every consumer boundary, so a real-browser axe accessibility scan can run against the shared UI insideMMCA.Common's own CI. - Depends on: ASP.NET Core
WebApplication/WebApplicationBuilder, MudBlazor (AddMudServices, line 53), the sharedMMCA.Common.UIsurface (AddUIShared,App,_Imports), and SupportedCultures fromMMCA.Common.Shared.Globalization. It wires in every stub in this unit: NoOpAuthUIService, NullTokenStorageService, NullTokenRefresher, GalleryAuthenticationStateProvider, GalleryFakeAuthenticationHandler, StubNotificationInboxUIService, StubPushNotificationUIService, and GalleryUIModule, plus the shared NotificationState. - Concept introduced: a self-hostable test host as one buildable unit. The whole host build lives in
BuildApp(string[] args)rather than inProgram.cs, so two callers share the identical configured app: thedotnet runentry point, whichRunAsync()s it (MMCA.Common.UI.Gallery/Program.cs:7-8), and the E2E collection fixture GalleryHostFixture, whichStartAsync()s it on an ephemeral Kestrel port.[Rubric §28 - Front-End Testing]assesses real-browser UI coverage; this host is the render target for the cross-browserui-e2eaxe and render matrix.[Rubric §33 - Developer Experience]:MMCA.Common.UI.Gallery/Program.cs:3-6records the rationale, oneBuildAppfor both entry points avoids the separatedotnet runplus health-poll that made ADC's cold start fragile. - Walkthrough:
- Assembly name and base dir (lines 33-34):
typeof(GalleryHost).Assembly.GetName().Nameis captured without a null-forgiving operator; the comment at lines 30-32 explains that CI's nullable analysis treatsAssemblyName.Nameas non-null and would flag!as an unnecessary suppression (IDE0370), and the value is only interpolated into a filename, which is null-safe either way. - Static web assets (lines 45-48): the load-bearing fix. RCL
_content/*CSS and JS and_framework/blazor.web.jsresolve from the entry assembly's manifests and auto-load only in Development; when the E2E suite self-hosts in-process the entry assembly is the test host and the environment is Production, so neither default holds. The loader is pointed explicitly at{galleryAssemblyName}.staticwebassets.runtime.jsonand forced on withUseStaticWebAssets(). Without it (comment, lines 38-44) the pages render unstyled and never become interactive, so axe's contrast checks would be meaningless and the page would never signal Blazor readiness. - Rendering services (lines 50-53):
AddRazorComponents().AddInteractiveServerComponents()thenAddMudServices(). - Boundary stubs, before
AddUIShared(lines 59-63): scopedIAuthUIService,ITokenStorageService, andITokenRefresher, thenAddHttpContextAccessor()and the scopedAuthenticationStateProvider. The ordering comment (lines 55-58) states the mechanism:AddUIShared'sTryAdd*registrations defer to whatever is already present. - Real authentication and authorization (lines 69-73):
AddAuthentication(GalleryFakeAuthenticationHandler.SchemeName)plusAddScheme<AuthenticationSchemeOptions, GalleryFakeAuthenticationHandler>(...), thenAddAuthorization(), because the notification pages'[Authorize]surfaces as endpoint metadata (comment, lines 65-68). - Canned notification boundaries (lines 78-80): scoped
NotificationState,INotificationInboxUIService, andIPushNotificationUIService, so the notification pages discovered from theMMCA.Common.UIassembly render populated markup. - Module contribution (line 85): the singleton
IUIModule, so the shared Router discovers the gallery's own/componentspage. - Shared UI (line 90):
AddUIShared(builder.Configuration)registersApiSettings/LayoutSettingsbinding, the"APIClient"HttpClient, and the remaining shared services; the in-memoryApi:ApiEndpointfromappsettings.jsonsatisfies validation, and the client is never invoked becauseIAuthUIServiceis stubbed (comment, lines 87-89). - Request localization (lines 99-103): builds
galleryCulturesas[.. SupportedCultures.All, SupportedCultures.PseudoLocale]and applies it as the supported and supported-UI culture set overSupportedCultures.Default. The comment (lines 94-98) is explicit that this mirrors the real hosts' ADR-027 allowlist but additionally enablesqps-Plocunconditionally, because this host is unpackaged test infrastructure that is never deployed and the pseudo pass here is a required CI gate (PseudoLocalizationE2ETests, the rubric §27 resource-round-trip and text-expansion evidence). Production keepsqps-PlocDevelopment-only viaUseCommonRequestLocalization. - Middleware (lines 107-112):
UseAuthentication()thenUseAuthorization()(WebApplication insertsUseRoutingahead of them automatically), thenUseAntiforgery(), required because Razor Component endpoints carry anti-forgery metadata even though the gallery's interactive forms never POST over HTTP. - Endpoints (lines 116-125):
MapStaticAssetsis given the gallery's own{galleryAssemblyName}.staticwebassets.endpoints.jsonfor the same in-process self-host reason as above; a/healthendpoint returnsResults.Ok("Healthy")(line 119); andMapRazorComponents<App>().AddInteractiveServerRenderMode().AddAdditionalAssemblies(typeof(MMCA.Common.UI._Imports).Assembly)makes the real shared pages routable alongside the gallery's own. - Return (line 127): the built-but-not-started
WebApplication, leaving the start mode to the caller.
- Assembly name and base dir (lines 33-34):
- Why it's built this way: Keeping the whole build in
BuildApprather thanProgram.cslets the E2E fixture host the identical configured app in-process on a real bound port viaStartAsync, notWebApplicationFactory's in-memory TestServer, which Playwright cannot reach over the wire. GalleryHostFixture therefore bindshttp://127.0.0.1:0and reads the ephemeral address back fromIServerAddressesFeature(MMCA.Common.UI.E2E.Tests/Infrastructure/GalleryHostFixture.cs:26-38). This is deliberate CI infrastructure and is never deployed. - Where it's used: Consumed by
MMCA.Common.UI.Gallery/Program.cs:7(thedotnet runentry) and by every E2E test through GalleryHostFixture (MMCA.Common.UI.E2E.Tests/Infrastructure/GalleryHostFixture.cs:26), which the axe and render suite (Login, Register, Components, Notifications, DarkMode, MobileTopRow, WebVitals, PseudoLocalization) runs against. - Caveats / not-in-source:
MMCA.Common.UI.GalleryandMMCA.Common.UI.E2E.Testsare deliberately excluded fromMMCA.Common.slnx(per the repoCLAUDE.md) so the unit-test run stays fast; they build only by csproj path and run only in CI'sui-e2ejob.
NavigationContractTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/NavigationContractTests.cs:17· Level 1 · class
- What it is - a documentation-drift gate for navigation (rubric §25): it asserts that the "Routes shipped by the framework" table in
NavigationFlow.mdstays in lockstep with the routable pages theMMCA.Common.UIassembly actually ships, and that each route's documented auth posture matches the[Authorize]reality on the page. - Depends on - UISharedAssemblyReference (the reflection anchor for the shared UI assembly,
NavigationContractTests.cs:82), plus externals: ASP.NET CoreRouteAttributeandAuthorizeAttribute(the route/guard metadata reflected over,:84,:90), a source-generatedRegexvia[GeneratedRegex](:116), the embeddedNavigationFlow.mdmanifest resource (:102), xUnit[Fact], and AwesomeAssertions. - Concept introduced - documentation as an executable contract. This is the first place a test asserts a hand-authored markdown doc against live code reality rather than the reverse.
NavigationFlow.mdlives next to the framework code (it is an embedded resource of this test project, perMMCA.Common/CLAUDE.md) precisely so this gate can parse it; a page added, removed, or re-routed without the doc moving in the same change fails the build, and so does an auth-posture lie in either direction. [Rubric §25 - Navigation & IA] assesses whether routing and information architecture are deliberate and documented; this test turns the route table into a build-enforced invariant instead of review discipline. [Rubric §11 - Security] / [Rubric §26 - Front-End Security] also apply: a route the doc calls Authenticated must carry[Authorize], and a route it calls Anonymous must not, so a mis-documented guard cannot pass silently. [Rubric §34 - Architecture Governance & Documentation] is the umbrella: the documentation is proven current by CI. - Walkthrough - two constants pin the contract:
MinimumRoutes = 8(:19) andDocResource = "NavigationFlow.md"(:20). Three[Fact]s enforce it.RoutablePages_AreDiscovered_GateIsNotVacuous(:22-:26) asserts reflection finds at least eight routed pages, so a broken anchor cannot let the whole gate pass having scanned nothing.EveryRoutablePage_IsDocumented_AndEveryDocumentedRoute_Exists(:28-:41) computes the set difference both ways:undocumentedroutes (real but missing from the doc) andphantomroutes (documented but no longer real) must both be empty.EveryDocumentedAuthPosture_MatchesTheRouteAttributeReality(:43-:77) walks each documented route, classifies its auth cell asAuthenticated...,Anonymous, orAny(:57-:59), and flags three violation kinds: an unrecognized posture string (:61-:64), a doc that promises authentication where the page carries no[Authorize](:65-:68), and a doc that promises an open route where the page is actually guarded (:69-:72). Two private helpers supply the two sides:DiscoverRoutedPages(:79-:98) reflects over the shared-UI assembly, collecting eachRouteAttribute.Templateand whether the type carries[Authorize];DiscoverDocumentedRoutes(:100-:114) reads the embedded doc and extracts route rows via the source-generatedRouteRowRegex(:116-:117, a 2000ms-timeout[GeneratedRegex]partial property). - Why it's built this way -
NavigationFlow.mdis deliberately kept in MMCA.Common next to the routed pages (not in the Website docs library) so this gate can embed and parse it; making route and auth-posture documentation a compiled assertion is what keeps a public navigation contract from rotting. - Where it's used - an independent class in the
MMCA.Common.Architecture.Testssuite; it runs in CI'sbuild-and-testjob (fast, no database) and has no Store/ADC counterpart because it guards the framework's own shared-UI route table.
FitnessPrincipal
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SpecificationFitnessTests.cs:43· Level 4 · class
- What it is - a throwaway "principal" entity used only as test data for the specification-navigation fitness function. It is the entity that a dependent record points at, so a specification can be written that tries to reach across the navigation into it.
- Depends on - AuditableBaseEntity<TIdentifierType> (it is a
public sealed class FitnessPrincipal : AuditableBaseEntity<int>,SpecificationFitnessTests.cs:43). - Concept introduced - test fixture entities for a fitness function. A fitness function is an executable architecture rule (see ArchitectureRules); to prove such a rule actually fires you feed it a deliberately-crafted model rather than the real domain.
FitnessPrincipalis one half of that crafted model: a bare aggregate-shaped type carrying a single scalar (IsActive,SpecificationFitnessTests.cs:45) so a specification can navigate to it. [Rubric §14 - Testability] assesses whether rules are provable with focused inputs; this fixture exists precisely so the guard is tested against a known-unsafe shape instead of hoping a real specification trips it. - Walkthrough - one auto-property,
bool IsActive(SpecificationFitnessTests.cs:45). That is the only member; identity and audit fields come from the base. It is the navigation target referenced by FitnessDependent.Principal. - Why it's built this way - the fitness test must be non-vacuous: it needs a real cross-entity navigation to flag. A minimal principal with a single scalar is the smallest thing a specification can legally navigate into.
- Where it's used - referenced by FitnessDependent and, through it, by NavigatingSpec; the whole fixture set drives SpecificationFitnessTests.
DataSubjectSample
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PiiErasureContractFitnessTests.cs:79· Level 5 · class
- What it is - a representative "data subject" fixture: a single object that carries
[Pii]members alongside non-PII fields and implements an in-place erasure path, so the framework's privacy machinery can be exercised end to end against a realistic shape. - Depends on - IAnonymizable (it is
private sealed class DataSubjectSample : IAnonymizable,PiiErasureContractFitnessTests.cs:79), PiiAttribute (marksEmail/FullName,PiiErasureContractFitnessTests.cs:91,:94), and Result (returned fromAnonymize,PiiErasureContractFitnessTests.cs:99). - Concept introduced - closing a vacuous fitness function with a stand-in data subject. The framework's
[Pii] => IAnonymizablescan (PiiConventionTests) has nothing to assert because MMCA.Common ships no PII-bearing domain entity of its own. Rather than invent a fake aggregate in the Domain layer, this fixture models the exact contract a consumer PII aggregate (for example MMCA.ADC'sUser) must satisfy, and lets PiiErasureContractFitnessTests prove the three §30 mechanisms compose. [Rubric §30 - Compliance/Privacy/Data Governance] assesses whether erasure, redaction, and masking actually work together; this sample is the vehicle that keeps the framework's proof of that non-vacuous. - Walkthrough - public constants publish the expected before/after values so the tests can assert without magic literals:
SampleId = 7,PublicCity = "Atlanta",OriginalEmail,OriginalFullName(PiiErasureContractFitnessTests.cs:81-:84), plus private anonymized placeholders (:86-:87).Id(:89) andCity(:97) are non-PII pass-through fields;EmailandFullNameare[Pii]with private setters (:91-:95).Anonymize()(:99-:105) overwrites both PII fields with the fixed placeholders and returnsResult.Success(); because it re-applies constants, calling it twice is idempotent by construction. - Why it's built this way - ADR-005 (soft-delete versus erasure) requires that a right-to-erasure path be idempotent and leave no clear-text behind. The fixture encodes that contract in the smallest object that can be pushed through
PiiRedactorandAnonymizetogether. - Where it's used - the sole fixture for PiiErasureContractFitnessTests.
DependencyVersionTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DependencyVersionTests.cs:9· Level 5 · class
- What it is - the MMCA.Common binding of the shared dependency-pin rule: a one-line sealed subclass that turns on the check enforcing the MassTransit-v8-only policy for this repo.
- Depends on - DependencyVersionTestsBase (
public sealed class DependencyVersionTests : DependencyVersionTestsBase;,DependencyVersionTests.cs:9). - Concept introduced - the thin-subclass fitness pattern. Almost every rule in this project lives once in the reusable
MMCA.Common.Testing.Architecturepackage as an abstract*TestsBase, and each repo activates it with a near-empty subclass. This is the first of many such subclasses; the body-less form here is the extreme case (no configuration at all). [Rubric §32 - Dependency & Supply-Chain] assesses guarding against risky dependency drift; the base parses this repo'sDirectory.Packages.propsand fails the build if the MassTransit major reaches 9 (v9 requires a commercial license), so a "just bump the version" edit cannot slip through. - Walkthrough - no members; the entire behavior is inherited. The base reads
Directory.Packages.propsand asserts the pin. This subclass exists only so xUnit discovers and runs it in the Common test assembly. - Why it's built this way - the pin is real only in MMCA.Common (where MassTransit is actually declared); ADC and Store inherit it transitively and deliberately do not subclass the base, because the default rule would fail parsing a pin they do not declare.
- Where it's used - run by the
MMCA.Common.Architecture.Testssuite in CI'sbuild-and-testjob.
FitnessDependent
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SpecificationFitnessTests.cs:34· Level 5 · class
- What it is - the other half of the specification-navigation fixture: an entity that holds a foreign-key scalar and a navigation to a FitnessPrincipal, so both a safe (scalar-only) and an unsafe (navigating) specification can be written over it.
- Depends on - AuditableBaseEntity<TIdentifierType> (
public sealed class FitnessDependent : AuditableBaseEntity<int>,SpecificationFitnessTests.cs:34) and FitnessPrincipal (thePrincipal?navigation,:38). - Concept introduced - cross-references the fixture concept introduced by FitnessPrincipal. This type adds the parts a specification can filter on:
PrincipalId(scalar FK,:36), a nullablePrincipalnavigation (:38), and aFlagscalar (:40). The scalar-versus-navigation split is the whole point: it lets one fixture support both the pattern the rule must flag and the pattern it must leave alone. - Walkthrough - three auto-properties:
int PrincipalId(:36),FitnessPrincipal? Principal(:38),bool Flag(:40). ScalarOnlySpec filters onPrincipalId/Flag; NavigatingSpec reaches throughPrincipal. - Where it's used - the entity type parameter for both NavigatingSpec and ScalarOnlySpec in SpecificationFitnessTests.
LocalizationResourceTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/LocalizationResourceTests.cs:12· Level 5 · class
- What it is - the MMCA.Common binding of the resource-completeness rule: it asserts the framework's own
.resxfiles fully translate every supported non-default culture, deriving the required-culture set from the live allowlist rather than restating it. - Depends on - LocalizationResourceTestsBase (base rule,
LocalizationResourceTests.cs:12) and SupportedCultures (source of the required cultures,:15). - Concept introduced - deriving a gate's expectations from the same allowlist production uses. Instead of hardcoding "translate Spanish," the override computes
RequiredCulturesasSupportedCultures.AllminusSupportedCultures.Default(:14-:17), so adding a locale to the app automatically extends the coverage requirement. [Rubric §27 - i18n] assesses whether localization is complete and enforced; this gate makes a missing translation a build failure and self-updates when the supported set grows. - Walkthrough - two overrides.
RequiredCultures(:14-:17) filtersSupportedCultures.Allto the non-default entries (today justes).MinimumBaseResources => 3(:21) sets a non-vacuous floor: the scan must find at least three base resources (ErrorResources for the API, plus SharedResource and MudTranslations for the UI), so a wrong scan root or repo re-layout cannot let the gate pass having checked nothing. - Why it's built this way - ADR-027 (localization) requires supported cultures to be fully translated; the derived allowlist plus the minimum-count floor turn that into a self-maintaining CI gate.
- Where it's used - run by the
MMCA.Common.Architecture.Testssuite.
NavigatingSpec
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SpecificationFitnessTests.cs:49· Level 6 · class
- What it is - the deliberately-unsafe fixture specification: its
Criterianavigates from the dependent into a related entity, the exact pattern the fitness function must flag. - Depends on - Specification<TEntity, TIdentifierType> (
private sealed class NavigatingSpec : Specification<FitnessDependent, int>,SpecificationFitnessTests.cs:49) and FitnessDependent/FitnessPrincipal (the entities it filters over). - Concept introduced - why cross-entity navigation in a specification is unsafe across data sources. Under database-per-service (ADR-006), a
d => d.Principal!.IsActivepredicate (:51) assumes the related entity lives in the same queryable model; once the principal is extracted to another physical source, that navigation cannot translate to SQL. The fitness functionSpecificationsDoNotNavigateToOtherEntitiestreats it as a violation. [Rubric §7 - Microservices Readiness] assesses whether the code stays extractable; this fixture is the negative example that proves the readiness guard fires. - Walkthrough - one member, an overridden
Criteriaexpression that dereferences thePrincipalnavigation (:51). The test asserts the rule's exception message contains this type's name. - Where it's used - the "should be flagged" input to SpecificationFitnessTests; paired with ScalarOnlySpec.
PiiErasureContractFitnessTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PiiErasureContractFitnessTests.cs:19· Level 6 · class
- What it is - a non-vacuous §30 fitness test that pushes a representative
[Pii]data subject through the framework's own privacy machinery, proving that PII detection, redaction/masking, and in-place erasure compose end to end rather than each being verified in isolation. - Depends on - DataSubjectSample (the fixture), PiiRedactor, IAnonymizable, and Result. Externals: xUnit
[Fact], AwesomeAssertions. - Concept introduced - a contract-composition fitness function. Where PiiConventionTests is a structural scan (does every
[Pii]type also implementIAnonymizable), this test exercises the behavior the scan presumes. It is the pattern for proving a cross-cutting compliance contract actually holds when its parts run together. [Rubric §30 - Compliance/Privacy/Data Governance] assesses that erasure and log-masking genuinely protect subject data; this test is the framework's executable evidence. - Walkthrough - four
[Fact]s, each isolating one link in the contract.DataSubject_DeclaresPii_SoTheContractIsNotVacuous(:21-:24) assertsPiiRedactor.HasPiirecognizes the sample, so the later guards assert against something real.PiiRedactor_MasksEveryPiiMember_AndPassesThroughNonPii(:26-:35) redacts an instance and checksEmail/FullNamebecomeRedactedTokenwhileId/Citypass through unchanged.PiiRedactor_LeaksNoClearTextPii_ToLogsOrTelemetry(:37-:50) verifies neither the redacted dictionary values norRedactToStringoutput contain the original email or name.DataSubject_ImplementsErasureSeam_AndAnonymizeErasesPii_Idempotently(:52-:72) asserts the sample isIAnonymizable, thatAnonymize()succeeds and changes the PII fields, that a second call also succeeds (idempotence, ADR-005), and finally that an anonymized subject still leaks no original clear text when redacted, proving erasure and redaction compose. - Why it's built this way - the plain
[Pii] => IAnonymizablescan is vacuous in the framework (no PII entity in Common's Domain); this test closes that gap by forcing the machinery through a stand-in subject, so the §30 guarantee is proven, not merely assumed. Consumers (MMCA.ADC'sUser) run the same contract against their real aggregates. - Where it's used - an independent test class in the Common architecture suite.
ScalarOnlySpec
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SpecificationFitnessTests.cs:55· Level 6 · class
- What it is - the deliberately-safe counterpart to NavigatingSpec: its
Criteriafilters only on the entity's own scalar columns, the pattern the fitness function must leave alone. - Depends on - Specification<TEntity, TIdentifierType> (
private sealed class ScalarOnlySpec : Specification<FitnessDependent, int>,SpecificationFitnessTests.cs:55) and FitnessDependent. - Concept introduced - cross-references the navigation-safety concept from NavigatingSpec; this is the positive example. A predicate over the entity's own scalars (
d => d.PrincipalId == 1 && d.Flag,:57) translates to SQL on any engine and survives extraction, so the rule must not flag it. Having both a flagged and an un-flagged fixture is what makes the test prove the rule discriminates rather than just always throwing. - Walkthrough - one overridden
Criteriafiltering onPrincipalIdandFlag(:57). The test asserts the rule's exception message does not contain this type's name. - Where it's used - the "should not be flagged" input to SpecificationFitnessTests.
CommonArchitectureMap
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/CommonArchitectureMap.cs:15· Level 7 · class
- What it is - the architecture map for the MMCA.Common framework: it names each package's layer and pins the layer to a concrete assembly, so the shared rule library knows which assembly is Shared, Domain, Application, and so on for this repo.
- Depends on - ArchitectureMapBase (
internal sealed class CommonArchitectureMap : ArchitectureMapBase,CommonArchitectureMap.cs:15), and one anchor type per package (for exampleResult,BaseEntity<>,DomainEventDispatcher,ApplicationDbContext,ApiControllerBase,ResultGrpcExtensions,UISharedAssemblyReference,CommonArchitectureMap.cs:21-:27). - Concept introduced - the map as the single point of repo-specific truth for architecture rules. The rule bodies live once in
MMCA.Common.Testing.Architectureand are parameterized by an IArchitectureMap; each repo supplies exactly one map so the same rules run identically across Common, Store, and ADC. Because Common is a module-less framework, every layer is registered as a framework layer via theFramework(...)helper rather than a module layer. [Rubric §3 - Clean Architecture] assesses whether layer boundaries are explicit and enforced; this map is the machine-readable statement of those boundaries. - Walkthrough -
RepoToken => "MMCA.Common"(:17) identifies the repo.DefineLayers()(:19-:28) returns oneFramework(Layer.X, anchorType.Assembly)entry per package, using a single anchor type to resolve each assembly (mirrors the oldPackageAssemblieshelper). The doc comment (:8-:13) records a deliberate omission:MMCA.Common.UI.Maui(ADR-042) is absent because its four MAUI TFM assemblies cannot load in the ubuntu net10.0 test process, so its UI+Shared boundary is enforced at compile time byEnforceUIMauiLayerBoundaryinstead. - Why it's built this way - one map per repo keeps the rule bodies DRY and identical everywhere (see the "Architecture Enforcement" section in
MMCA.Common/CLAUDE.md); anchoring by type keeps the assembly reference refactor-safe. - Where it's used - supplied as
Mapby nearly every*ConventionTests/*DependencyTestssubclass in this unit, and is the pattern SpecTestMap collapses to a single layer.
FrameworkSanityTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/FrameworkSanityTests.cs:13· Level 7 · class
- What it is - the home for the few architecture checks that are Common-only and do not generalize into the shared rule library: the
MMCA.Common.Grpctransport boundary and the placement of theIMessageBus,IJwksProvider, andILiveChannelPublisherabstractions. - Depends on - IMessageBus, IJwksProvider, ILiveChannelPublisher, and the NetArchTest
Types/ArchitectureAsserthelpers (ArchitectureAssert). - Concept introduced - repo-specific sanity next to the shared library. Not every rule fits the parameterized base classes; some assert facts true only of the framework repo. Keeping them in one explicitly-named class documents the boundary between "shared rule applied here" and "Common-only invariant." [Rubric §7 - Microservices Readiness] (transport isolation) and [Rubric §3 - Clean Architecture] (abstraction placement) both apply: gRPC is pure transport and must not couple to Domain/Application/Infrastructure, and the cross-cutting abstractions must sit in the layer their consumers depend on.
- Walkthrough - three private static
Assemblyaccessors anchor the Grpc, Application, and Infrastructure assemblies (:15-:19). Three[Fact]s assertMMCA.Common.Grpchas no dependency on Domain, Application, or Infrastructure (:21-:34) via theAssertNoDependencyhelper (:51-:59), which runs aTypes.InAssembly(...).ShouldNot().HaveDependencyOnAny(...)NetArchTest query and routes the result throughArchitectureAssert.NoViolations. Three more[Fact]s assert placement:IMessageBuslives in Application (:37-:39),IJwksProviderin Infrastructure because it handles crypto/PEM material (:42-:44), andILiveChannelPublisherin Application besideIPushNotificationSender(:47-:49). - Why it's built this way - the message-bus abstraction must stay in Application so application code depends on transport through it (extraction boundary, ADR-007); the JWKS provider is crypto and belongs in Infrastructure (ADR-004). These are load-bearing placements, so they get their own asserted facts.
- Where it's used - an independent class in the Common architecture suite; it has no counterpart in Store/ADC because only Common owns the Grpc package and defines these abstractions.
SpecificationFitnessTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SpecificationFitnessTests.cs:13· Level 7 · class
- What it is - the test that verifies the
SpecificationsDoNotNavigateToOtherEntitiesfitness function actually discriminates: it must flag a specification that navigates into another entity and must leave a scalar-only specification alone. - Depends on - ArchitectureRules (the rule under test), and its own nested fixtures SpecTestMap, FitnessDependent, FitnessPrincipal, NavigatingSpec, ScalarOnlySpec.
- Concept introduced - testing the test: verifying a fitness function is neither vacuous nor over-broad. A rule that never fires is useless; a rule that flags everything is worse. This class proves the specification-navigation guard does exactly one thing by feeding it both a positive and a negative fixture in a single run. [Rubric §14 - Testability] assesses whether the guardrails themselves are trustworthy; this is the meta-test that earns that trust.
- Walkthrough - one
[Fact],Rule_FlagsNavigatingSpecification_ButNotScalarSpecification(:15-:24). It invokesArchitectureRules.SpecificationsDoNotNavigateToOtherEntities(new SpecTestMap())(:18), captures the thrown exception, and asserts its message containsNavigatingSpecand the word "navigates" while not containingScalarOnlySpec(:21-:23). The nested types below the fact supply the model:SpecTestMap(:26), the two entities (:34,:43), and the two specifications (:49,:55). - Why it's built this way - the navigation rule protects future extraction (a navigating specification cannot cross a data-source boundary, ADR-006); a discriminating test keeps the rule honest as it evolves.
- Where it's used - an independent class in the Common architecture suite.
SpecTestMap
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests·MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SpecificationFitnessTests.cs:26· Level 7 · class
- What it is - a minimal architecture map used only by SpecificationFitnessTests: it registers this test assembly as the single Application layer so the specification-navigation rule has a model to scan.
- Depends on - ArchitectureMapBase (
private sealed class SpecTestMap : ArchitectureMapBase,SpecificationFitnessTests.cs:26). - Concept introduced - cross-references the map concept from CommonArchitectureMap. Where the real map spans seven packages, this one collapses to a single self-referential Application layer (
Framework(Layer.Application, typeof(SpecificationFitnessTests).Assembly),:31) because the fixtures (the two specifications and two entities) live in the test assembly itself. It is the smallest map that lets a fitness function run against hand-crafted types. - Walkthrough -
RepoToken => "MMCA.Common"(:28) and a one-entryDefineLayers()(:30-:31) pointing at this assembly. - Where it's used - instantiated once inside SpecificationFitnessTests's single fact.
AggregateConventionTests, DomainPurityTests, EventVersioningConventionTests, HandlerResultConventionTests, LayerDependencyTests, LocalizedTextConventionTests, MicroserviceExtractionTests, PiiConventionTests, RawQueryableConventionTests, SliceCohesionTests, StateManagementConventionTests, UIArchitectureConventionTests
MMCA.Common.Architecture.Tests ·
MMCA.Common.Architecture.Tests· (see per-type table) · Level 8 · class
These twelve sealed classes share one shape: each is a thin subclass of a shared *TestsBase rule from the MMCA.Common.Testing.Architecture package, supplying the repo's CommonArchitectureMap (and, for a few, one extra override) so the same rule body runs identically across MMCA.Common, MMCA.Store, and MMCA.ADC. This is the [Rubric §34 - Architecture Governance & Documentation] and [Rubric §14 - Testability] story: architecture conventions are executable and enforced in CI rather than left to review, and the rule logic lives in exactly one place. See the thin-subclass pattern introduced by DependencyVersionTests. The canonical body of each rule is the corresponding *TestsBase; these subclasses only wire in the map and any repo-specific floor or allowlist. Each fails the build-and-test CI job on violation, and several are deliberately vacuous today (they assert nothing until the framework grows a type that could break the convention, at which point they fire).
| Type | File:Line | Base rule | What it enforces / what differs |
|---|---|---|---|
AggregateConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/AggregateConventionTests.cs:9 |
AggregateConventionTestsBase | DDD aggregate-root factory rules for the framework's own aggregates. Supplies only Map (:11). [Rubric §4 - DDD.] |
DomainPurityTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/DomainPurityTests.cs:9 |
DomainPurityTestsBase | Domain/Shared stay framework-free and Application stays host-agnostic. Supplies only Map (:11). [Rubric §3 - Clean Architecture.] |
EventVersioningConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/EventVersioningConventionTests.cs:10 |
EventConventionTestsBase | Integration-event SchemaVersion/BaseIntegrationEvent/namespace rules (ADR-010). Supplies only Map (:12). Vacuous today: the framework ships no concrete integration event. [Rubric §6 - CQRS & Event-Driven.] |
HandlerResultConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/HandlerResultConventionTests.cs:12 |
HandlerResultConventionTestsBase | Every concrete command/query handler's TResult must be Result or Result<T> (the pipeline otherwise only enforces this at runtime); scans the framework's Notifications handlers. Supplies only Map (:14). [Rubric §6 - CQRS & Event-Driven.] |
LayerDependencyTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/LayerDependencyTests.cs:9 |
LayerDependencyTestsBase | Clean Architecture layer-flow (each layer references only layers below). Supplies only Map (:11). [Rubric §3 - Clean Architecture.] |
LocalizedTextConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/LocalizedTextConventionTests.cs:11 |
LocalizedTextConventionTestsBase | Shared MMCA.Common.UI ships no hard-coded user-visible literals; everything resolves through IStringLocalizer (ADR-027). Also overrides MinimumScannedFiles => 20 (:16) so a wrong scan root is caught. [Rubric §27 - i18n.] |
MicroserviceExtractionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/MicroserviceExtractionTests.cs:10 |
MicroserviceExtractionTestsBase | Domain/Application/Shared stay free of MassTransit/Grpc/Protobuf (transport lives at the edges). Supplies only Map (:12). [Rubric §7 - Microservices Readiness.] |
PiiConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/PiiConventionTests.cs:13 |
PiiConventionTestsBase | [Pii] => IAnonymizable right-to-erasure scan (ADR-005). Supplies only Map (:15). Structurally vacuous in the framework; the machinery is proven non-vacuously by PiiErasureContractFitnessTests. [Rubric §30 - Compliance/Privacy.] |
RawQueryableConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/RawQueryableConventionTests.cs:13 |
RawQueryableConventionTestsBase | Bans raw IQueryable in Application code, scanning the framework's own Source/Core/MMCA.Common.Application project (ApplicationSourceDirectories, :18-:22). Beyond Map (:15) it overrides AllowedFiles (:25-:37) to whitelist the deliberate composition root EntityQueryService.cs and the five Notifications inbox handlers whose cross-entity joins are the documented exception. [Rubric §8 - Data Architecture.] |
SliceCohesionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/SliceCohesionTests.cs:10 |
SliceCohesionTestsBase | Each Notifications use-case slice keeps command/query, handler, and validator in one namespace. Supplies only Map (:12). [Rubric §5 - Vertical Slice.] |
StateManagementConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/StateManagementConventionTests.cs:11 |
StateManagementConventionTestsBase | MMCA.Common.UI carries no mutable static state and keeps stateful services scoped (per-circuit model). Overrides AllowedStaticMembers to whitelist ErrorMessages._localizer, a write-once wiring point, not per-user state (:21-:22). [Rubric §19 - State Management.] |
UIArchitectureConventionTests |
MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/UIArchitectureConventionTests.cs:11 |
UIArchitectureConventionTestsBase | Shared pages/primitives keep code-behind within the convention cap and inline @code blocks small (container/presentational split). Supplies only Map (:13). [Rubric §18 - UI Architecture.] |
- Why they're built this way - see the two-layer "Architecture Enforcement" model in
MMCA.Common/CLAUDE.md: rules are enforced at compile time (LayerEnforcement.targets) and at runtime here, with the runtime bodies factored into one shared package so Common, Store, and ADC stay identical. Each subclass exists only so xUnit discovers the rule in this repo's assembly with this repo's map. - Where they're used - all twelve run in the
MMCA.Common.Architecture.Testsproject during CI'sbuild-and-testjob (fast, no database).
Per-project test rollup
This guide treats tests as grouped, not sectioned per [Fact] (the logged exception in the
charter): the reusable test bases, the shared architecture-fitness library and its per-repo thin
subclasses, and the component Gallery harness each get their own ### treatment in the earlier parts
of this chapter, but the bulk of the suite, 1,094 individual test types across 40 projects, is rolled
up here. Each row below names a test project (assembly), the count of test types it contributes to the
1,094, what it covers, and its style (unit / integration / component / E2E / performance-smoke).
Counts reconcile exactly to the unit input.
A few cross-cutting facts hold for every row, so they are stated once here rather than repeated:
- Stack. Every project is xUnit v3 run under the Microsoft Testing Platform (not VSTest,
global.jsonsets"runner": "Microsoft.Testing.Platform"), with AwesomeAssertions for fluent asserts, Moq for test doubles, and coverlet for coverage (see for exampleMMCA.Common/Tests/Hosting/MMCA.Common.Testing.Tests/MMCA.Common.Testing.Tests.csproj:7). The lone exception isMMCA.Common.Benchmarks, a BenchmarkDotNet executable (not a test project). See primer §3 for the platform/runner externals. - Layering mirror. The ADC module suites repeat the same seven-project shape per module
(
{Module}.{Shared,Domain,Application,Infrastructure,API,UI}.Tests+ a per-service{Module}.IntegrationTests), so once you understand the Conference column you understand Engagement and Identity: they differ only in volume, not in kind. Notification is the deliberate exception, a thinner module with onlyAPI,Application, andIntegrationTestsprojects, because its domain and persistence live inMMCA.Commonand are tested there. - Fitness tests and shared bases live elsewhere.
MMCA.Common.Architecture.TestsandMMCA.ADC.Architecture.Tests(the NetArchTest layer/purity/extraction suites, thin subclasses of the sharedArchitectureRulesrule library) are not in this table: they are covered as first-class sections earlier in this chapter. The same is true of the shared test bases (IntegrationTestBase<TFixture>,HandlerTestBase<THandler>,BunitComponentTestBase, the Playwright fixtures) and theMMCA.Common.UI.Galleryharness. - Two integration tiers, deliberately split. Each service has a per-service
*.IntegrationTestsproject that boots one host throughWebApplicationFactory<Program>with cross-service gRPC edges faked and no broker (these gate deploy via theintegration-testsCI job and need a real SQL Server named byADC_TEST_SQL_BASE). Separately,MMCA.ADC.CrossService.IntegrationTestsandMMCA.ADC.ServiceBusEmulator.IntegrationTestsrun against Testcontainers to prove the genuine broker and gRPC round-trips: both live in the non-gating nightlyMMCA.ADC/.github/workflows/cross-service-tests.yml, whose recency (not its result) gates deploys through thecross-service-freshnessjob atMMCA.ADC/.github/workflows/deploy.yml:620(a 5-day window).[Rubric §14, Testability](assesses how thoroughly and at what cost the system can be verified): the count and spread below, heavy at the inner Application/Domain layers, thinner at the edges, with a dedicated integration + E2E tier, is the classic healthy test pyramid, and the fact that the volume concentrates in fast in-memory unit layers keeps the feedback loop cheap.
MMCA.Common, the framework suite (Tests/ mirrors Source/)
| Test project (assembly) | Types | What it covers · style |
|---|---|---|
MMCA.Common.Shared.Tests |
22 | The innermost layer: the Result/Error/ErrorType pattern, value objects (Money, Email, Address, DateRange, …) and their factory-method invariants, and DTO/paging contracts. Pure unit tests, no DI or DB. |
MMCA.Common.Domain.Tests |
43 | The entity hierarchy (BaseEntity→AuditableBaseEntity→AuditableAggregateRootEntity), domain-event collection, SetItems<T>/GetChildOrNotFound<T>, specifications, and the PiiAttribute/anonymization boundary plus the logging/telemetry redaction half of the [Pii] contract (masks marked members so a data subject's values never reach logs, ADR-005 / §30). Pure unit tests over the framework domain primitives. |
MMCA.Common.Application.Tests |
161 | The CQRS engine: the decorator pipeline in its registered nesting order (FeatureGate→Logging→Caching→Validating→Transactional→handler, MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:88), the opt-in MiniProfiler decorators added by AddApplicationProfiling (.../DependencyInjection.cs:185) and the CqrsMetrics counters/histograms (MMCA.Common/Tests/Core/MMCA.Common.Application.Tests/Decorators/CqrsMetricsTests.cs:20), ModuleLoader topological ordering, DomainEventDispatcher plus the swallow-and-log SafeDomainEventHandler base (.../DomainEvents/SafeDomainEventHandlerTests.cs:9), validation, the IMessageBus abstraction, entity-query projection/paging and the per-type filter strategies, the cross-source CrossSourceSpecification helper (ADR-018), the magic-byte upload sniffer behind ImageContentSniffer (.../ImageContentSnifferTests.cs:12, ADR-045), and the notification read handlers driven by an injected TimeProvider test clock. The framework's largest suite; fast unit tests with mocked infrastructure. |
MMCA.Common.Infrastructure.Tests |
171 | The widest layer: EF repositories + Unit of Work, the multi-database resolver/registry (DataSourceResolver, EntityDataSourceRegistry, DbContextFactory) and the cross-data-source degrade convention (.../Persistence/DataSources/CrossDataSourceDegradeConventionTests.cs:24), the outbox processor (eligibility/smart-wait/retry) and the consumer-side EfInboxStore idempotency ledger (.../Persistence/Inbox/EfInboxStoreTests.cs:27, ADR-021), caching, JWT issuance + JWKS + the login-attempt lockout service (.../Auth/LoginProtectionServiceTests.cs:14), column-level encryption (.../Persistence/EncryptedStringConverterTests.cs:6), the filtered-unique-index soft-delete convention (.../Persistence/Conventions/SoftDeleteUniqueIndexConventionTests.cs:23), image processing (.../Services/ImageSharpImageProcessorTests.cs:15, ADR-045), the SignalR push + live-channel plumbing, the message-bus implementations, the polyglot Cosmos-config portability suite (ADR-018), and the in-repo disaster-recovery database-restore drill (.../Resilience/DatabaseRestoreDrillTests.cs, a CI-gated RTO baseline, ADR-009 / §29). Mostly unit with EF-InMemory/SQLite boundaries (no real SQL Server here). |
MMCA.Common.API.Tests |
65 | The presentation pipeline: ApiControllerBase.HandleFailure ErrorType→HTTP mapping, the exception-handler chain, the [Idempotent] filter + Idempotency-Key replay, the authenticated-only global rate limiter's partition logic (.../Startup/RateLimitPartitionTests.cs, ADR-019), permission policies/ownership filters, correlation, the JWKS and OIDC-discovery endpoints, the session-cookie auth handler/refresher/jar, the shared notification + device controllers, the public-endpoint output-cache policy, the database-initialization startup (the SQLite-EnsureCreated-under-Migrate path, ADR-018), and the error-message localization edge (localizes the human-readable message while leaving the machine Code/ProblemDetails title untouched and degrading to English when no localizer is present, ADR-027 / §27). Unit tests of middleware/filters/controllers in isolation. |
MMCA.Common.Grpc.Tests |
13 | The gRPC transport boundary: Result↔RpcException round-tripping, the JWT-forwarding client interceptor, and the Polly resilience pipeline on typed clients (retry, circuit-breaker, and fault-injection). Unit tests asserting ADR-007 / ADR-009 behavior. |
MMCA.Common.Aspire.Tests |
11 | The hosting/observability extensions: OutboxPollFilterProcessor (drops recurring outbox-poll spans from telemetry export), the SecurityHeadersMiddleware, the startup warm-up gate (holds readiness closed until first-request warm-up completes so a rolling deploy never serves a cold replica, §29), the head-based trace-sampling cost knob (a ratio in (0,1) opts in, anything else samples everything, §31), and the metrics-instrumentation toggle (.../Telemetry/MetricsInstrumentationToggleTests.cs:12). Unit suite over the Aspire service-defaults package. |
MMCA.Common.Testing.Tests |
9 | The suite that tests the test framework itself, so a regression in the shared scaffolding fails here rather than silently weakening every consumer suite: HandlerTestBaseTests drives HandlerTestBase<THandler> exactly as a consumer handler test would, registering repositories and relying on the pre-wired unit of work (MMCA.Common/Tests/Hosting/MMCA.Common.Testing.Tests/HandlerTestBaseTests.cs:12), and DecoratorPipelineOrderTests runs DecoratorPipelineOrderTestsBase<…> against MMCA.Common's own AddApplication → ScanModuleApplicationServices → AddApplicationDecorators sequence to prove the resolved pipelines nest in the ADR-014 order (.../DecoratorPipelineOrderTests.cs:20). Unit style. |
MMCA.Common.UI.Tests |
71 | Shared Blazor components (delete-confirmation, empty-state, the mobile card/infinite-scroll lists, notification bell/inbox/list/send pages, primitives), the MudBlazor theme/provider harness, HTTP-resilience/service-exception helpers, list-page state/query-state services, the primitive markup snapshots, the auth-form view-model validation (§24), and the i18n globalization pair (the [!!…!!] bracket-sentinel pseudo-localizer and the ResxMudLocalizer MudBlazor-chrome boundary, ADR-027 / §27) plus the auth-aware nav menu and its mobile top-row. Rendered with bUnit (component-render unit tests via BunitComponentTestBase). |
MMCA.Common.UI.Web.Tests |
4 | The Blazor Server web-host pieces: ServerTokenStorageService (during SSR prerender tokens come from the HttpOnly session cookies; on the interactive circuit the access token is held in memory, hydrated single-flight, and refreshed proactively near expiry, while the refresh token is never readable), the server form-factor probe, and BlazorCspPolicyProvider, which pins the enforced production Content-Security-Policy verbatim (connect-src locked to the configured API/Gateway origin, no unsafe-eval, permissive Report-Only degradation on an unparseable endpoint, §26). Unit tests. |
MMCA.Common.UI.E2E.Tests |
11 | Playwright axe-core (WCAG 2.1 AA) + render-smoke over the backend-less Gallery host (real Login/Register pages, the primitives/components showcase, and the shared Notification pages against stubbed collaborators), plus the dark-mode toggle, a Web-Vitals probe, and two i18n/mobile-parity gates: a qps-Ploc pseudo-locale round-trip asserting the [!! sentinel and no horizontal overflow under ~40% text expansion (ADR-027 / §27), and the culture+theme controls pinned into the mobile top-row below 1024px (ADR-028 / §22). Deliberately outside MMCA.Common.slnx; runs in CI's ui-e2e job. E2E/accessibility style. [Rubric §21, Accessibility] (assesses automated a11y gating): this is where the framework proves zero axe violations before downstream apps consume the pages. |
MMCA.Common.Benchmarks |
4 | A BenchmarkDotNet performance-smoke executable for the DB-free query hot path: SpecificationBenchmarks measures the per-instance compiled-expression cache behind Specification<TEntity, TIdentifierType>.IsSatisfiedBy (a cached-compile baseline vs. the recompile-each-call anti-pattern) and the And/Or composition cost. Deliberately outside MMCA.Common.slnx (like the Gallery), run on demand with dotnet run -c Release (append -- --job Dry for a seconds-long correctness smoke). [Rubric §12, Performance & Scalability] (assesses measured, not assumed, hot-path cost): this is the evidence harness for the spec cache. Performance-smoke style. |
MMCA.ADC, Conference module (the largest application module)
| Test project (assembly) | Types | What it covers · style |
|---|---|---|
MMCA.ADC.Conference.Shared.Tests |
17 | Conference DTOs, requests, enums, and DTO/request mappers (the manual-mapping/Mapperly boundary, ADR-001). Pure unit tests. |
MMCA.ADC.Conference.Domain.Tests |
22 | The Conference aggregates (Event, Session, Speaker, Room, Category, Question/Answer): factory-method Result<T> outcomes, invariants, state transitions, and emitted domain events. Unit tests. |
MMCA.ADC.Conference.Application.Tests |
134 | The command/query handlers for the Conference controllers, validators, navigation populators, the Sessionize import orchestrator + sync strategies (MMCA.ADC/Tests/Modules/Conference/MMCA.ADC.Conference.Application.Tests/Events/UseCases/RefreshFromSessionizeHandlerTests.cs), and the event/session live-window validation served to the live layer over gRPC (.../Events/EventLiveValidationServiceTests.cs, GetPublicSessionFilterHandler and its cross-source filter query, ADR-018). The biggest application suite in ADC; fast unit tests with mocked repositories/services. |
MMCA.ADC.Conference.Infrastructure.Tests |
7 | Conference-specific EF configurations, the module DB seeder, the Sessionize HTTP client, and the Anthropic-backed session-scoring service (.../MMCA.ADC.Conference.Infrastructure.Tests/Services/AnthropicScoringServiceTests.cs:12). Small unit suite over faked HTTP handlers. |
MMCA.ADC.Conference.API.Tests |
16 | Conference REST controllers (events, sessions, speakers, rooms, categories, questions/answers, session selection), the module's permission grants, and the Conference error-resource localization completeness check (.../MMCA.ADC.Conference.API.Tests/Localization/ConferenceErrorResourcesTests.cs:15, §27). Unit tests of the API layer. |
MMCA.ADC.Conference.UI.Tests |
27 | Conference Blazor pages and components: the public event/session/speaker detail + filtered list pages, the management CRUD forms and management-route authorization, the organizer feedback dashboards, the speaker dashboard, the session-selection dashboard with its AI-score and speaker-overlap views (.../Pages/SessionSelection/SessionSelectionAiScoresTests.cs:15), and the share/QR/add-to-calendar buttons (.../Components/QrCodeButtonTests.cs:14). Rendered with bUnit (BunitTestBase over the shared BunitComponentTestBase). Component tests. |
MMCA.ADC.Conference.IntegrationTests |
36 | Boots the Conference service host via WebApplicationFactory<Program> (gRPC peers faked, JWT re-pointed at an in-process test key) and drives real HTTP per role (Anonymous/Attendee/Speaker/Organizer), plus OpenAPI contract-snapshot, API-versioning, optimistic-concurrency, soft-delete + audit-stamp fidelity, idempotency replay, output-cache eviction, the includeChildren regression, and the in-process CrossServiceUserRegisteredTests (the Identity→Conference UserRegistered auto-link handler). Integration style; needs a real SQL Server (ADC_TEST_SQL_BASE), runs in the deploy-gating integration-tests CI job. |
MMCA.ADC, Engagement module (bookmarks, feedback, and the conference-day live layer)
| Test project (assembly) | Types | What it covers · style |
|---|---|---|
MMCA.ADC.Engagement.Shared.Tests |
2 | Bookmark/feedback/live DTOs, requests, and mappers. Unit. |
MMCA.ADC.Engagement.Domain.Tests |
6 | The UserSessionBookmark, event/session feedback, and conference-day live-layer aggregates (LivePoll + SessionQuestion): factory Result<T> outcomes, invariants, and domain events. Unit. |
MMCA.ADC.Engagement.Application.Tests |
27 | Bookmark, feedback, and live-layer (poll / session-question) add/remove/query handlers and validators, including the cross-module ISessionBookmarkValidationService / IBookmarkCountService / IEventLiveValidationService gRPC collaborators (stubbed) and the best-effort ILiveChannelPublisher ingress. Unit. |
MMCA.ADC.Engagement.Infrastructure.Tests |
4 | Engagement EF configuration plus the live-channel publish processor that fans domain changes out to the SignalR hub (MMCA.ADC/Tests/Modules/Engagement/MMCA.ADC.Engagement.Infrastructure.Tests/Live/LiveChannelPublishProcessorTests.cs:10). Unit. |
MMCA.ADC.Engagement.API.Tests |
6 | The Bookmarks/Feedback/Live REST controllers in isolation. Unit. |
MMCA.ADC.Engagement.UI.Tests |
19 | Engagement Blazor renders and their UI services: the bookmark UI, the session/event feedback pages, the conference-day live/presenter surfaces (Happening Now, live poll, session Q&A, the moderation panel), the live-channel join/reconnect path (.../Pages/LiveChannelJoinTests.cs:37), and the session-reminder planner/coordinator (.../Services/SessionReminderPlannerTests.cs:11). Component (bUnit). |
MMCA.ADC.Engagement.IntegrationTests |
13 | Boots the Engagement service host via WebApplicationFactory<Program> and exercises the bookmark/feedback/live workflows + authorization over real HTTP. Integration; real SQL Server, deploy-gating CI job. |
MMCA.ADC, Identity module (User aggregate + JWT/JWKS + external OAuth)
| Test project (assembly) | Types | What it covers · style |
|---|---|---|
MMCA.ADC.Identity.Shared.Tests |
3 | Identity DTOs/requests and mappers (User, roles, LinkedSpeakerId). Unit. |
MMCA.ADC.Identity.Domain.Tests |
4 | The User/UserRole aggregate factories, invariants, anonymization, and speaker-linking domain events. Unit. |
MMCA.ADC.Identity.Application.Tests |
21 | Registration/login/profile/role/preferences handlers and validators, the external-OAuth (Google/GitHub) exchange, and the SpeakerLinkedToUser/SpeakerUnlinkedFromUser integration-event handlers. Unit. |
MMCA.ADC.Identity.Infrastructure.Tests |
4 | Identity EF config/repository, RS256 token issuance, and the JWKS provider. Unit. |
MMCA.ADC.Identity.API.Tests |
7 | The Auth REST controller, the JWKS endpoint, and identity middleware in isolation. Unit. |
MMCA.ADC.Identity.UI.Tests |
6 | Identity Blazor pages (login/register/profile/user-management) rendered with bUnit. Component. |
MMCA.ADC.Identity.IntegrationTests |
33 | Boots the Identity service host via WebApplicationFactory<Program> and drives the full auth surface over real HTTP: registration, login and its anonymous edge cases, claims, profile, user preferences, soft-deleted-user handling, the external-OAuth challenge/exchange, GDPR user export (.../Attendee/UserExportTests.cs:16), and JWKS discovery. It also carries the two contract guards (OpenAPI snapshot and the RFC 9457 Problem Details subclass over ProblemDetailsContractTestsBase<TFixture>, .../Contract/ProblemDetailsContractTests.cs:17, §9), the compliance pair that proves erasure works end to end and that PII never reaches the log pipeline (.../Compliance/ErasureAndPiiLoggingTests.cs:19, ADR-005 / §30), an outbox-fidelity guard asserting registration atomically enqueues UserRegistered into [dbo].[OutboxMessages] (.../Data/OutboxFidelityTests.cs:17), and the in-process CrossServiceSpeakerLinkTests. Integration; real SQL Server, deploy-gating CI job. |
MMCA.ADC, Notification module (push + inbox on top of the framework's notification types)
| Test project (assembly) | Types | What it covers · style |
|---|---|---|
MMCA.ADC.Notification.API.Tests |
1 | NotificationModuleTests pins the module contract itself: its Name, its declared Dependencies on Identity, RequiresDependencies, and the RegisterDisabledStubs path that keeps the cross-module IUserNotificationExportService resolvable as a singleton DisabledUserNotificationExportService when the module is switched off (MMCA.ADC/Tests/Modules/Notification/MMCA.ADC.Notification.API.Tests/NotificationModuleTests.cs:7). Unit. |
MMCA.ADC.Notification.Application.Tests |
5 | The module's two application services plus its DI registration: AttendeeNotificationRecipientProvider resolving broadcast recipients through the Identity IAttendeeQueryService gRPC contract (.../AttendeeNotificationRecipientProviderTests.cs:7), UserNotificationExportService assembling a data-subject export from the user-notification and push-notification repositories over InMemoryQueryableExecutor on top of HandlerTestBase<THandler> (.../UserNotificationExportServiceTests.cs:11, §30), and AddModuleNotificationApplication proving both are registered against their interfaces (.../DependencyInjectionTests.cs:9). Unit. |
MMCA.ADC.Notification.IntegrationTests |
8 | Boots the Notification service host via WebApplicationFactory<Program> (the Identity recipient-lookup gRPC client faked by FakeAttendeeQueryService) and exercises the push-notification REST endpoints + inbox (NotificationsController/InboxController from MMCA.Common.API, .../Notifications/NotificationControllerTests.cs:16), an OpenAPI contract snapshot (.../Contract/OpenApiContractTests.cs:16), and the real-time SignalR NotificationHub: a live HubConnection asserts authenticated connect, anonymous rejection (the hub carries [Authorize]), and a POST-triggered broadcast reaching the connected recipient (.../Notifications/NotificationHubTests.cs:15). Integration; real SQL Server (ADC_TEST_SQL_BASE), deploy-gating CI job. |
MMCA.ADC, host, cross-service, and end-to-end suites
| Test project (assembly) | Types | What it covers · style |
|---|---|---|
MMCA.ADC.Gateway.Tests |
6 | Boots the real YARP Gateway host in-process (GatewayApplicationFactory, pinned to Production so HSTS and the realistic non-development CORS branch run) and asserts three operational guarantees: every response carries the hardened security response headers on /alive (.../SecurityHeadersTests.cs:11, §26), the host shuts down gracefully within its bounded stop timeout, firing ApplicationStopping/ApplicationStopped (.../GracefulShutdownTests.cs:14, §29), and the full YARP route table forwards each pattern to the service that owns it, asserted by swapping the real IHttpForwarder for a recording fake that echoes the destination prefix into a response header so no backends are needed (MMCA.ADC/Tests/Hosts/MMCA.ADC.Gateway.Tests/RouteMapTests.cs:19). The Gateway is a pure reverse proxy (no DbContext/broker) so the boot needs no SQL. Integration style. |
MMCA.ADC.CrossService.IntegrationTests |
12 | The real-broker + real-gRPC tier: boots all three REST hosts (Identity/Conference/Engagement) in one process against a Testcontainers SQL Server and a Testcontainers RabbitMQ, so the genuine MassTransit outbox → broker → consumer round-trip (UserRegistered auto-link, SpeakerLinked/SpeakerUnlinked back-link) and the real Conference → Engagement bookmark-count gRPC read run end to end, over a sequential env-boot fixture and a smoke gate that fails first if the container/host wiring is wrong. Integration style; needs Docker, runs in the non-gating nightly cross-service workflow (not in Integration.slnf). |
MMCA.ADC.ServiceBusEmulator.IntegrationTests |
3 | Broker-parity smoke (§33): production runs on Azure Service Bus while local development runs RabbitMQ, so Service-Bus-specific transport behavior used to be observable only in the deployed environment. This tier runs MassTransit v8 against the official Service Bus emulator container with ADC's real integration-event contracts and proves the two transport-specific behaviors: admin-plane topology provisioning (topic per message type, subscription, receive-endpoint queue) and the AMQP publish → topic → subscription → consume round-trip (MMCA.ADC/Tests/Integration/MMCA.ADC.ServiceBusEmulator.IntegrationTests/ServiceBusRoundTripSmokeTests.cs:22). One warm collection-scoped emulator serves the whole tier because of the emulator's 10-connection and roughly one-admin-operation-per-second quotas, the image is pinned to 2.0.1 (the admin plane arrived in 2.0.0 and is the tier's whole premise), and the fixture's static constructor lowers MassTransit's process-global TTL/auto-delete defaults beneath the emulator's one-hour maximum, which is why this tier lives in its own test process (.../Infrastructure/ServiceBusEmulatorFixture.cs:22). Integration style; needs Docker, runs in the same nightly workflow. |
MMCA.ADC.E2E.Tests |
60 | Playwright end-to-end against the running Aspire stack, using a Page-Object model (PageObjects/) and E2ETestBase login helpers, organized by actor workflow (Organizer/Speaker/Attendee/Identity/Preferences) plus the Engagement live-poll and feedback flows, real-time notification push, a Web-Vitals budget check, and an AccessibilityTests axe sweep. Runs once per engine via E2E_BROWSER (chromium/firefox/webkit). The largest single project here and the source of most of the chapter's recorded E2E debugging history. [Rubric §28, Front-End Testing] + [Rubric §22, Responsive/Cross-Browser]: this suite is the cross-browser, real-user-flow safety net. E2E style. |
Reconciliation. Common: 22+43+161+171+65+13+11+9+71+4+11+4 = 585 (12 projects). ADC Conference: 17+22+134+7+16+27+36 = 259 (7). ADC Engagement: 2+6+27+4+6+19+13 = 77 (7). ADC Identity: 3+4+21+4+7+6+33 = 78 (7). ADC Notification: 1+5+8 = 14 (3). ADC host/cross-service/E2E: 6+12+3+60 = 81 (4). Total = 585+259+77+78+14+81 = 1,094, across 40 projects, matching the unit input exactly.
⬅ Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters) • Index • Coverage audit ➡