Adds SlpModularCms.Api.SlpSoftware and extracts shared CmsHost composition
Continuous Integration / config (pull_request) Successful in 11s
Continuous Integration / changes (pull_request) Successful in 21s
Continuous Integration / backend-build (pull_request) Successful in 6m10s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m59s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m27s
Continuous Integration / backend-test (pull_request) Failing after 7m48s
Continuous Integration / frontend-build (pull_request) Successful in 2m5s
Continuous Integration / frontend-test (pull_request) Successful in 4m24s
Continuous Integration / frontend-lint (pull_request) Successful in 2m0s
Continuous Integration / publish-test (pull_request) Skipped
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped

Unit 1 of the slpsoftware-api feature (FR-1/FR-2/FR-3): a new Client project
in the Clients solution folder, intended to eventually become the deployed
API for test.slpsoftware.nl/slpsoftware.nl, hosting the same four modules as
SlpModularCms.Api plus a future Offerings module.

- Extracts SlpModularCms.Api/Program.cs's hosting-pipeline composition into
  SlpModularCms.Core.Hosting.CmsHost (ConfigureServices/ConfigurePipeline),
  shared by both Client projects so they cannot drift apart
- Moves StaticContentExtensions.cs + WebsitePlaceholder.html from Api into
  Core, since CmsHost cannot live in Api but Core cannot depend on Api
- Adds SlpModularCms.Api.SlpSoftware with its own isolated local dev database
  and dev ports (5286/7223, distinct from Api's and Api.Slave's)
- Adds SlpModularCms.Api.Tests with WebApplicationFactory-based pipeline
  regression tests (security headers, health check, SPA fallback, rate
  limiting), scoped to Api per NFR Design
- Adds a frontend dev:slpsoftware pnpm script mirroring dev:slave
- Fixes GlobalExceptionHandler logging routine 401s (e.g. an expired/missing
  refresh token) as unhandled errors -- pre-existing, unrelated to this
  feature's own scope, found while testing the new instance

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
This commit is contained in:
2026-08-02 01:28:39 +02:00
co-authored by Claude Sonnet 5
parent dcc82cdf62
commit fa389e42ee
51 changed files with 3119 additions and 127 deletions
@@ -0,0 +1,21 @@
# Logical Components — Unit: SlpSoftware Client Setup
## Component: Pipeline Regression Test Suite
**Type**: Test project / test fixture (new logical component, no runtime footprint in production).
**Scope**: Targets `SlpModularCms.Api` only (Q1 = C). Exact host project (new `SlpModularCms.Api.Tests` vs. extending `SlpModularCms.Core.Tests`) is a Code Generation Planning decision — this stage fixes *what* it tests and *against which project*, not its exact file location.
**Integration pattern**: `Microsoft.AspNetCore.Mvc.Testing`'s `WebApplicationFactory<TEntryPoint>`, using `Api`'s own `Program.cs` as the entry point (requires `Api`'s `Program.cs` to be accessible to the test project via the standard `InternalsVisibleTo`/top-level-statement partial-class pattern, if not already the case for other reasons).
## Component: `CmsHostOptions`
**Type**: Plain options object (new), living in `SlpModularCms.Core.Hosting` alongside `CmsHost`.
**Shape at this stage**: empty (Q2 = B) — see nfr-design-patterns.md Pattern 2.
**Consumers**: `SlpModularCms.Api/Program.cs` and `SlpModularCms.Api.SlpSoftware/Program.cs`, both constructing a default instance.
## No Other New Logical Components
This unit does not introduce queues, caches, circuit breakers, or any other infrastructure component — it is a composition/extraction of existing pieces plus the one new options object above.
@@ -0,0 +1,25 @@
# NFR Design Patterns — Unit: SlpSoftware Client Setup
## Pattern 1: Pipeline Regression Test, Scoped to `Api` (NFR-CS-01)
**Decision** (Q1 = C): the new `WebApplicationFactory`-based integration tests target **`SlpModularCms.Api` only**. `SlpModularCms.Api.SlpSoftware` is not separately pipeline-tested in this unit — it inherits confidence transitively through the shared `CmsHost.ConfigureServices`/`ConfigurePipeline` code path that both projects call identically.
**Pattern**: a single `WebApplicationFactory<TEntryPoint>`-based test fixture, pointed at `Api`, asserting on real HTTP responses:
- Security headers present (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy) — CSP presence and required directives only, not the exact Umami/Sentry exceptions (those are `Api`-specific configuration, unrelated to `CmsHost` correctness).
- `/health` returns success.
- A non-file `/admin/*` route resolves to the admin SPA's `index.html`.
- A burst of requests against a rate-limited route eventually receives a `429`.
**Accepted trade-off**: if a future change to `CmsHost` behaves differently under `Api.SlpSoftware`'s specific module composition (Offerings module present) than under `Api`'s, this test suite alone would not catch it. This is an accepted gap per the user's decision, not an oversight — full end-to-end coverage of `Api.SlpSoftware` itself is covered later by the feature-wide Build and Test phase once Unit 2 exists.
## Pattern 2: `CmsHostOptions` as an Empty Extension Point (NFR-CS-02)
**Decision** (Q2 = B): `CmsHostOptions` is introduced as a genuinely empty class (or, in C# terms, a class with zero properties, or `internal sealed record CmsHostOptions;` — exact syntax decided at Code Generation) — a placeholder in the method signature, not a placeholder-with-a-guess-field.
**Pattern**: standard **Options Object** pattern, sized for its current job (nothing) rather than a speculative future job. Both `Api` and `Api.SlpSoftware` construct `new CmsHostOptions()` and pass it to `CmsHost.ConfigureServices(builder, options)` / `ConfigurePipeline(app, orchestrator, options)`. When a real per-project difference appears later, a property is added to this one class rather than changing either method's signature again.
**Constraint carried forward** (from NFR-CS-02 / tech-stack-decisions.md): whatever is eventually added to `CmsHostOptions`, it must never affect `DataProtectionExtensions.ApplicationDiscriminator` — that stays the hardcoded `"SlpModularCms"` constant regardless.
## Pattern 3: Security — Verification, Not New Design (NFR-CS-03)
No new security pattern is introduced by this unit. The applicable pattern is "prove equivalence," fully covered by Pattern 1's regression tests. No additional logical components (WAF, extra middleware, etc.) are needed.