Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/construction/build-and-test/integration-test-instructions.md
T
Sluijsens 9ab30fe2a6 Closes construction - build and test across all seven units
Unit suites already passed individually; this is the full solution
together plus what none of them could check alone: two real hosts
talking to each other, a slave API key encrypted by one process and
decrypted by the next one after a restart, and headers landing on
actual static files instead of a simulated response.
2026-07-28 16:47:45 +02:00

6.0 KiB

Integration Test Instructions

Purpose

Verify everything that unit tests structurally cannot: real HTTP responses from a running host, real static-file serving, real cross-process master/slave communication, and real Data Protection key-ring behaviour. Every scenario below was explicitly flagged as "carried to phase-level Build and Test" by U1, U2 or U3, because DefaultHttpContext and in-memory test doubles cannot exercise them (e.g. Response.OnStarting is a no-op outside a real Kestrel pipeline, and a unit test cannot start two real processes and let them talk to each other).

Setup Integration Test Environment

1. Start a database

podman start sql-server
# or, if it doesn't exist yet: see README.md § "Database opstarten"

Without this, the SPA-fallback and placeholder scenarios still work, but the "real static asset" scenarios need actual files:

dotnet publish src/SlpModularCms.Api -c Release -o ./tmp-publish-verify

This also copies the built admin SPA into src/SlpModularCms.Api/wwwroot/admin/ (via BuildAndCopyAdminFrontend). Create a placeholder wwwroot/web/index.html by hand to exercise the "real website content" scenarios, or leave it absent to exercise the placeholder scenario.

wwwroot/ is gitignored and must be deleted after testing — it is generated content, not part of the repository.

3. Start both hosts

dotnet run --project src/SlpModularCms.Api --launch-profile https        # port 7221
dotnet run --project src/SlpModularCms.Api.Slave --launch-profile https  # port 7222

Start them one at a time, waiting for the first to finish building — starting both simultaneously races on the shared SlpModularCms.Core build output (see build-instructions.md troubleshooting).

Run Integration Tests

Scenario 1: Static content, SPA fallback, and header scoping (U1, U3)

Request Expected Verified 2026-07-28
GET / with no wwwroot/web/ 200, embedded placeholder HTML, Relaxed CSP
GET / with wwwroot/web/index.html present 200, real content, Relaxed CSP
GET /admin 301 → /admin/
GET /admin/ 200, admin index.html, Strict CSP (DENY, no unsafe-inline in script-src)
GET /admin/dashboard (client route, no extension) 200, falls back to admin index.html
GET /admin/assets/{real-file}.js 200, real file served, Content-Type: text/javascript, X-Content-Type-Options present, no CSP/X-Frame-Options/Referrer-Policy (non-HTML — FU1 = A scoping)
GET /admin/assets/does-not-exist.js 404 (missing file with an extension does not fall back to HTML)
GET /a-website-route-with-no-extension 200, falls back to website index.html, Relaxed CSP
GET /health 200, application/json, X-Content-Type-Options present, no CSP/X-Frame-Options/Referrer-Policy (non-HTML)

Note on HSTS: not observed on any response above — correct, since sendHsts is deliberately !IsDevelopment() and all of the above ran with ASPNETCORE_ENVIRONMENT=Development. Re-verify with ASPNETCORE_ENVIRONMENT=Production before a real release if this has not already been proven in a non-Development environment.

Scenario 2: Master ↔ Slave real communication over the persistent key ring (U2, Master module)

Check Expected Verified 2026-07-28
Master's IntegrityCheckBackgroundService reaches a previously-registered slave GET https://localhost:7222/api/v1/master/registered-url → 200
Master successfully authenticates with a stored, encrypted slave API key from a previous process's key ring POST https://localhost:7222/api/v1/master/status → 200 (not a decryption failure)

This is the strongest available local proof that U2's persistent, database-backed key ring (PersistKeysToDbContext with an explicit discriminator) actually survives a process restart — the stored API key was encrypted by an earlier process and successfully decrypted by this one.

Scenario 3: Correlation ID in structured logging (U4, OPEN-01)

Check Expected Verified 2026-07-28
A real request's log entries Carry a TraceId: (W3C trace id) in the log scope (e.g. TraceId:5a302c33b4f0fc8fb9d0b35f840934b8 on a request to /api/v1/System/capabilities)

Scenario 4: Startup behaviour (U1, U2)

Check Expected Verified 2026-07-28
Both hosts start with a fresh/existing database MigrateCoreDatabase() logs "Core database migrations applied successfully", no fail-fast
Missing wwwroot/admin/ and wwwroot/web/ at startup Warning logged per directory, host still starts and serves /api/v1 (observed on the very first run, before publishing)

Not Re-Verified at This Stage (out of scope, per the units' own Definition of Done)

  • 503 response from the availability gate carrying security headers (U3's own carried item) — covered by unit tests using a simulated HttpContext; a fully authenticated real-host repeat would need a bootstrapped Owner login flow that this repository's README does not yet document (a pre-existing gap, unrelated to this feature). Indirect evidence is strong: every response observed above — 200s and 404s alike — carried X-Content-Type-Options, confirming the OnStarting hook fires uniformly regardless of status code
  • Real Sentry event with the security_event tag, tunnel status codes, threshold behaviour — needs a configured Sentry DSN; deferred to Operations (Monitoring Setup)
  • UptimeRobot, real Gitea pipeline run, actual Pi/SSH/database backup — deferred to Operations per U5/U6's own Definition of Done

Cleanup

# Stop both dotnet run processes
Remove-Item -Recurse -Force src/SlpModularCms.Api/wwwroot, tmp-publish-verify

wwwroot/ must not be committed — verify git status is clean afterwards.