Table of Contents
- Sprint 0 — Test Plan
- 1. Reading guide
- 2. Test overview (master table)
- 3. Unit tests (backend)
- T-UT01 — JwtService generates valid access token
- T-UT02 — JwtService generates refresh token with longer expiration
- T-UT03 — JwtService rejects invalid token
- T-UT04 — ExchangeService mints envelope
- T-UT05 — ExchangeService consumes envelope happy path
- T-UT06 — ExchangeService rejects nonce replay
- T-UT07 — ExchangeService rejects HMAC tamper
- T-UT08 — ExchangeService rejects clock skew beyond max-age
- T-UT09 — HmacEnvelope sign/verify symmetry
- T-UT10 — MembershipService computes effective rank
- T-UT11 — MembershipService rejects invalid (org_type, org_id) via SPI
- T-UT12 — InvitationService creates invitation with hashed token
- T-UT13 — AccessRequestService transitions states
- T-UT14 — PlateAuthProperties bean validation
- T-UT15 — OrgContextResolver falls back when SPI absent
- 4. Integration tests (backend, Testcontainers)
- T-IT01 — Flyway migrations apply cleanly
- T-IT02 — Auto-config wires required beans
- T-IT03 — Full exchange flow
- T-IT04 — JWT filter populates SecurityContext
- T-IT05 — MembershipRepository queries
- T-IT06 — Invitation accept flow
- T-IT07 — Access request approve flow
- T-IT08 — Login event audit row written
- T-IT09 — SPI swap: custom OrgValidator
- 5. Frontend unit tests (@platesoft/auth)
- T-FE01 — createAuthConfig factory
- T-FE02 — Edge HMAC sign + verify
- T-FE03 — Proxy strips hop-by-hop headers
- T-FE04 — Proxy handler uses NextAuth v5 auth()
- T-FE05 — Conditional exports resolve correctly
- 6. End-to-end regression (InspectFlow as test bed)
- T-E2E01 — Anonymous flow (existing)
- T-E2E02 — Google sign-in
- T-E2E03 — Password login
- T-E2E04 — Invitation accept
- T-E2E05 — Access request approve
- T-E2E06 — Admin audit endpoint visible
- 7. Security tests
- T-SEC01 — HMAC tamper rejected
- T-SEC02 — Nonce replay rejected
- T-SEC03 — Envelope max-age rejected
- T-SEC04 — Expired JWT rejected
- T-SEC05 — Missing JWT secret fails startup
- T-SEC06 — Short JWT secret fails startup
- T-SEC07 — CORS unknown origin rejected
- T-SEC08 — SQL injection probe
- T-SEC09 — Constant-time HMAC compare
- T-SEC10 — Refresh-token rotation
- 8. Performance smoke
- T-PERF01 — Exchange consume p95 < 50ms
- T-PERF02 — Login p95 < 300ms
- T-PERF03 — JWT filter overhead per request
- 9. Acceptance criteria → tests matrix
- 10. Test data + fixtures
- 11. Test infrastructure
- 12. Out of scope for Sprint 0
- 13. Open issues / risks for the test plan
- 14. Cross-references
Sprint 0 — Test Plan
Status: Draft v1
Date: 2026-06-24
Owner: Patrick
Scope: Validates Sprint 0 deliverable: de.platesoft:plate-auth-starter:0.1.0 + @platesoft/auth:0.1.0
Basis: Sprint-0-Plan.md
1. Reading guide
This test plan enumerates every test case (T-IDs) needed to validate the Sprint 0 carve-out. It does not re-test the InspectFlow product surface — the existing InspectFlow E2E suite serves as our regression net (see § 5).
Each test case has:
- ID (T-UTxx unit / T-ITxx integration / T-FExx frontend / T-E2Exx end-to-end / T-SECxx security / T-PERFxx perf)
- Type (Unit / Integration / Frontend-Unit / E2E / Security / Performance)
- Class / spec file
- Scenarios (Given / When / Then)
- Expected result
- Acceptance criterion mapped (A1..A8 from Sprint-0-Plan.md § 10.5)
Status legend: ⬜ Open · 🟡 In progress · ✅ Passed · ❌ Failed · ⏭️ Skipped
2. Test overview (master table)
| ID | Type | Class / Spec | Maps to | Status |
|---|---|---|---|---|
| T-UT01 | Unit | JwtServiceTest |
A4 | ⬜ |
| T-UT02 | Unit | JwtServiceTest (refresh) |
A4 | ⬜ |
| T-UT03 | Unit | JwtServiceTest (invalid token) |
A4 | ⬜ |
| T-UT04 | Unit | ExchangeServiceTest (mint) |
A2, A4 | ⬜ |
| T-UT05 | Unit | ExchangeServiceTest (consume happy) |
A2, A4 | ⬜ |
| T-UT06 | Unit | ExchangeServiceTest (nonce replay) |
A2 | ⬜ |
| T-UT07 | Unit | ExchangeServiceTest (HMAC tamper) |
A2 | ⬜ |
| T-UT08 | Unit | ExchangeServiceTest (clock skew) |
A2 | ⬜ |
| T-UT09 | Unit | HmacEnvelopeTest |
A2 | ⬜ |
| T-UT10 | Unit | MembershipServiceTest (rank) |
A1 | ⬜ |
| T-UT11 | Unit | MembershipServiceTest (polymorphic FK validation) |
A1, A5 | ⬜ |
| T-UT12 | Unit | InvitationServiceTest |
A1 | ⬜ |
| T-UT13 | Unit | AccessRequestServiceTest |
A1 | ⬜ |
| T-UT14 | Unit | PlateAuthPropertiesValidationTest |
A4 | ⬜ |
| T-UT15 | Unit | OrgContextResolverTest (SPI fallback) |
A5 | ⬜ |
| T-IT01 | Integration | PlateAuthFlywayMigrationIT |
A3 | ⬜ |
| T-IT02 | Integration | AuthBootstrapIT (auto-config wiring) |
A1, A4 | ⬜ |
| T-IT03 | Integration | ExchangeFlowIT (sign-in → mint → consume) |
A2 | ⬜ |
| T-IT04 | Integration | JwtAuthenticationFilterIT |
A1 | ⬜ |
| T-IT05 | Integration | MembershipRepositoryIT |
A1 | ⬜ |
| T-IT06 | Integration | InvitationFlowIT |
A1 | ⬜ |
| T-IT07 | Integration | AccessRequestFlowIT |
A1 | ⬜ |
| T-IT08 | Integration | LoginEventAuditIT |
A1 | ⬜ |
| T-IT09 | Integration | ProviderSpiSwapIT (default vs custom OrgValidator) |
A5 | ⬜ |
| T-FE01 | Frontend-Unit | createAuthConfig.test.ts |
A6 | ⬜ |
| T-FE02 | Frontend-Unit | hmac-edge.test.ts (Web Crypto sign + verify) |
A2, A6 | ⬜ |
| T-FE03 | Frontend-Unit | proxy-headers.test.ts (hop-by-hop strip) |
A6 | ⬜ |
| T-FE04 | Frontend-Unit | proxy-handler.test.ts (auth() → fetch) |
A6 | ⬜ |
| T-FE05 | Frontend-Unit | package-exports.test.ts (conditional exports) |
A6 | ⬜ |
| T-E2E01 | E2E | InspectFlow e2e/auth-flow.unauth.spec.ts |
A7 | ⬜ |
| T-E2E02 | E2E | InspectFlow Google sign-in scenario | A7 | ⬜ |
| T-E2E03 | E2E | InspectFlow password login scenario | A7 | ⬜ |
| T-E2E04 | E2E | InspectFlow invitation accept scenario | A7 | ⬜ |
| T-E2E05 | E2E | InspectFlow access-request approve scenario | A7 | ⬜ |
| T-E2E06 | E2E | InspectFlow admin audit endpoint visibility | A7 | ⬜ |
| T-SEC01 | Security | HMAC tamper rejected (envelope mutated) | A2 | ⬜ |
| T-SEC02 | Security | Nonce replay rejected within TTL | A2 | ⬜ |
| T-SEC03 | Security | Envelope rejected after max-age | A2 | ⬜ |
| T-SEC04 | Security | Expired JWT rejected | A4 | ⬜ |
| T-SEC05 | Security | Missing JWT secret fails startup | A4 | ⬜ |
| T-SEC06 | Security | Short JWT secret (<32 chars) fails startup | A4 | ⬜ |
| T-SEC07 | Security | CORS unknown origin rejected | A4 | ⬜ |
| T-SEC08 | Security | SQL injection probe on /auth/login rejected |
A1 | ⬜ |
| T-SEC09 | Security | Constant-time HMAC compare (no timing oracle) | A2 | ⬜ |
| T-SEC10 | Security | Refresh-token rotation: old refresh invalidated | A4 | ⬜ |
| T-PERF01 | Performance | /auth/exchange/consume p95 < 50ms |
A8 | ⬜ |
| T-PERF02 | Performance | /auth/login p95 < 300ms (incl. bcrypt) |
A8 | ⬜ |
| T-PERF03 | Performance | JWT filter overhead per request p95 < 5ms | A8 | ⬜ |
Total: 43 test cases — 15 Unit, 9 Integration, 5 Frontend-Unit, 6 E2E, 10 Security, 3 Performance.
3. Unit tests (backend)
T-UT01 — JwtService generates valid access token
- Class:
de.platesoft.auth.service.JwtServiceTest - Method:
generateAccessToken_validInputs_returnsParseableToken() - Given:
JwtServiceconfigured with HMAC secret ≥ 32 chars, 15min expiration, issuerplate-auth. - When:
generateAccessToken(userId=UUID, email="a@b.de", role="USER"). - Then: Returned token parses with
jjwt, contains claimssub,email,role,iss=plate-auth,exp ≈ now + 15min(±2s).
T-UT02 — JwtService generates refresh token with longer expiration
- Method:
generateRefreshToken_validInputs_hasLongerExpiration() - Given: Same config.
- When:
generateRefreshToken(userId)called. - Then: Token has
exp ≈ now + 30 days, differentjtithan access token, claimtype="refresh".
T-UT03 — JwtService rejects invalid token
- Method:
isTokenValid_tamperedToken_returnsFalse() - Given: Valid token, then last 5 chars replaced with random.
- When:
isTokenValid(tampered). - Then: Returns
false. No exception leaks out (caught internally and logged at DEBUG).
T-UT04 — ExchangeService mints envelope
- Class:
de.platesoft.auth.service.ExchangeServiceTest - Method:
mint_validUser_returnsSignedEnvelope() - Given: Exchange secret ≥ 32 chars, nonce-ttl=5min.
- When:
mint(userId, email, role, orgContext). - Then: Returns envelope JSON with fields
nonce(UUID format),iat(epoch seconds),userId,email,role,orgContext,sig(Base64 SHA-256 HMAC over canonical concat). HMAC verifies against the secret.
T-UT05 — ExchangeService consumes envelope happy path
- Method:
consume_validFreshEnvelope_returnsTokens() - When:
consume(envelope)withinmax-agewindow. - Then: Returns
TokenResponse(accessToken, refreshToken). Nonce is now in the consumed-set.
T-UT06 — ExchangeService rejects nonce replay
- Method:
consume_replayedNonce_throws() - Given: Envelope already consumed once.
- When:
consume(sameEnvelope)called again. - Then: Throws
ExchangeReplayException(HTTP-mappable to 409 Conflict). Audit event emitted.
T-UT07 — ExchangeService rejects HMAC tamper
- Method:
consume_tamperedField_throws() - Given: Envelope with
rolechanged fromUSERtoADMINpost-signing. - When:
consume(tampered). - Then: Throws
ExchangeHmacInvalidException. Audit eventEXCHANGE_HMAC_FAILEDemitted.
T-UT08 — ExchangeService rejects clock skew beyond max-age
- Method:
consume_envelopeBeyondMaxAge_throws() - Given: Envelope
iatset tonow - 70s(max-age=60s). - When:
consume(...). - Then: Throws
ExchangeExpiredException.
T-UT09 — HmacEnvelope sign/verify symmetry
- Class:
de.platesoft.auth.crypto.HmacEnvelopeTest - Method:
signThenVerify_sameSecret_succeeds()+verify_differentSecret_fails() - Then: Round-trip succeeds; wrong secret returns
false. Compare usesMessageDigest.isEqual(byte[], byte[])(constant-time).
T-UT10 — MembershipService computes effective rank
- Class:
de.platesoft.auth.service.MembershipServiceTest - Method:
effectiveRole_userWithMultipleMemberships_returnsHighest() - Given: User has membership
USERin Org A,ADMINin Org B. - When:
effectiveRole(userId, orgId=B). - Then: Returns
ADMIN. HelpereffectiveRole(userId)(no org) returnsADMIN(max).
T-UT11 — MembershipService rejects invalid (org_type, org_id) via SPI
- Method:
addMembership_orgValidatorRejects_throws() - Given: Test
OrgValidatorSPI implementation that returnsfalsefor unknown org IDs. - When: Adding a membership with
(org_type="UNKNOWN", org_id=42). - Then: Throws
OrgValidationException. No row inserted.
T-UT12 — InvitationService creates invitation with hashed token
- Class:
de.platesoft.auth.service.InvitationServiceTest - Method:
create_validInput_storesHashedTokenOnly() - Then: DB row has bcrypt or SHA-256 hash, not the plaintext token. Plaintext is returned to caller exactly once. Expiration set to
now + 7 days.
T-UT13 — AccessRequestService transitions states
- Class:
de.platesoft.auth.service.AccessRequestServiceTest - Method:
approve_pendingRequest_createsMembership() - Then: Request status →
APPROVED, Membership row created with requested role,AccessRequestMailerSPI invoked.
T-UT14 — PlateAuthProperties bean validation
- Class:
de.platesoft.auth.config.PlateAuthPropertiesValidationTest - Scenarios (parameterized):
- JWT secret 31 chars → fail (
@Size(min=32)) - Exchange secret null → fail (
@NotBlank) cors.allowed-originsmalformed URL → fail- All valid → pass
- JWT secret 31 chars → fail (
- Then: ApplicationContext fails fast with
BindValidationExceptionreferencing the invalid property path.
T-UT15 — OrgContextResolver falls back when SPI absent
- Class:
de.platesoft.auth.spi.OrgContextResolverTest - Given: No user-provided
OrgValidatorbean; defaultPermissiveOrgValidatorin effect. - When: Resolving any
(org_type, org_id)— called N times. - Then: Returns
true(default-accept) every time, and emits one WARN log entry per call with message containing"OrgValidator default permissive — override de.platesoft.auth.spi.OrgValidator bean before production". Assert WARN count == N (not throttled, not one-shot).
4. Integration tests (backend, Testcontainers)
Strategy: each integration test boots Spring with @SpringBootTest(classes = PlateAuthAutoConfiguration.class) plus a minimal test JPA entity-scan and uses a PostgreSQLContainer (Testcontainers).
T-IT01 — Flyway migrations apply cleanly
- Class:
de.platesoft.auth.flyway.PlateAuthFlywayMigrationIT - Given: Empty Postgres 16 container.
- When: Boot starter with default config; Flyway runs.
- Then: Schema contains tables
users,user_identities,memberships,invitations,access_requests,login_events, plus the indexidx_user_identities_microsoft_tenant_idfrom V5.flyway_schema_history_authtable has 6 rows (V1..V6). All migrations are non-failed.
T-IT02 — Auto-config wires required beans
- Class:
de.platesoft.auth.config.AuthBootstrapIT - Then: Context contains
JwtService,ExchangeService,JwtAuthenticationFilter,SecurityFilterChain, defaultOrgValidator, default mailers. No bean is@ConditionalOnMissingBean-overridden when no user bean is provided.
T-IT03 — Full exchange flow
- Class:
de.platesoft.auth.exchange.ExchangeFlowIT - Given: Seeded user.
- When: POST
/auth/exchange/mintthen POST/auth/exchange/consumewith returned envelope. - Then:
consumereturns 200 withaccessToken+refreshToken. Access token is a valid JWT for the seeded user.
T-IT04 — JWT filter populates SecurityContext
- Class:
de.platesoft.auth.filter.JwtAuthenticationFilterIT - Mirrors:
JwtAuthenticationFilter.javabehavior. - Then: Request with valid
Authorization: Bearer <jwt>populatesSecurityContextHolderwithAuthenticationcontaininguserId,email, and authorityROLE_<role>. Invalid token → no auth, filter chain continues, downstream returns 401 via Spring Security entry point.
T-IT05 — MembershipRepository queries
- Class:
de.platesoft.auth.repository.MembershipRepositoryIT - Then:
findByUserIdAndOrgTypeAndOrgId,findByUserId,findAdminsByOrgreturn expected rows. Unique constraint on(user_id, org_type, org_id)is enforced.
T-IT06 — Invitation accept flow
- Class:
de.platesoft.auth.invitation.InvitationFlowIT - Then: Inviting an email → token in mailer mock. Accepting with token + signup → user created, membership created, invitation marked
ACCEPTED. Second accept with same token returns 410.
T-IT07 — Access request approve flow
- Class:
de.platesoft.auth.accessrequest.AccessRequestFlowIT - Then: End-to-end: anonymous request → admin sees pending list → admin approves → membership created → requester notified.
T-IT08 — Login event audit row written
- Class:
de.platesoft.auth.audit.LoginEventAuditIT - Then: Every login attempt (success or failure) writes a
login_eventsrow with IP, user-agent, outcome (SUCCESS/BAD_CREDENTIALS/EXPIRED/LOCKED). Failed attempts do not write the password.
T-IT09 — SPI swap: custom OrgValidator
- Class:
de.platesoft.auth.spi.ProviderSpiSwapIT - Given: Test config provides a
StrictOrgValidatorthat only accepts(COMPANY, 1). - Then: Default
PermissiveOrgValidatoris not instantiated. Adding membership with(COMPANY, 2)fails.
5. Frontend unit tests (@platesoft/auth)
Vitest + jsdom. All tests live in frontend/plate-auth/test/.
T-FE01 — createAuthConfig factory
- Spec:
createAuthConfig.test.ts - Then: Returned NextAuth config has
trustHost: true,pages.signIn = opts.signInPage ?? "/login",providersarray contains entries for each provider explicitly enabled inopts,callbacks.sessionreturns JWT-derived shape.
T-FE02 — Edge HMAC sign + verify
- Spec:
hmac-edge.test.ts - Then:
signEnvelope(payload, secret)produces output identical to backendHmacEnvelope.sign(...)(golden vector fixture).verifyEnvelope(envelope, secret)round-trips. Tampered envelope returnsfalse. Uses Web Crypto (crypto.subtle.importKey+crypto.subtle.sign('HMAC', ...)) — no Nodecryptoimport.
T-FE03 — Proxy strips hop-by-hop headers
- Spec:
proxy-headers.test.ts - Then: Helper
sanitizeHopByHop(headers)removesconnection,keep-alive,proxy-authenticate,proxy-authorization,te,trailer,transfer-encoding,upgrade,host. Case-insensitive. Custom request headers are preserved.
T-FE04 — Proxy handler uses NextAuth v5 auth()
- Spec:
proxy-handler.test.ts - Then: When
auth()returns null → handler returnsResponse(401). Whenauth()returns a session withaccessToken→ handler forwards request withAuthorization: Bearer <token>andduplex: "half"on POST/PUT/PATCH bodies. Hop-by-hop headers from upstream are stripped on response.
T-FE05 — Conditional exports resolve correctly
- Spec:
package-exports.test.ts - Then:
import {createAuthConfig} from "@platesoft/auth/server"resolves;import {createProxyHandlers} from "@platesoft/auth/edge"resolves;import {AuthProvider} from "@platesoft/auth/react"resolves. Tree-shaking: edge bundle does not contain server-only imports (verified via@arethetypeswrong/clismoke).
6. End-to-end regression (InspectFlow as test bed)
After Sprint-0-Plan § 10.2 Step 2 (InspectFlow refactored onto plate-auth 0.0.1), the existing InspectFlow Playwright suite is the E2E test for plate-auth. We do not duplicate these — we add a checkmark per scenario.
T-E2E01 — Anonymous flow (existing)
- Spec:
frontend/e2e/auth-flow.unauth.spec.ts - Then: Unauth user redirected to
/login. Sign-up disabled whenplate.auth.registration.enabled=false. Password reset link visible.
T-E2E02 — Google sign-in
- Then: OAuth callback works end-to-end against a mocked Google provider. New user → access request flow triggered (via
OnboardingHookSPI). Existing user → tokens issued and redirected to dashboard.
T-E2E03 — Password login
- Then: Valid credentials → tokens issued, dashboard loads. Wrong password → error message, no token, login_event row with
BAD_CREDENTIALS.
T-E2E04 — Invitation accept
- Then: Admin sends invite from
/admin/users. Mailer mock captures URL. Invitee opens URL, sets password, lands in app with correct memberships.
T-E2E05 — Access request approve
- Then: New user requests access. Admin sees pending request in
/admin/access-requests. Approve → user receives email, can log in.
T-E2E06 — Admin audit endpoint visible
- Then: Admin can view
/admin/auditshowing login_events. Non-admin gets 403.
Pass criterion (A7): All 6 scenarios green on InspectFlow CI after the swap. If any test fails, treat as a Sprint 0 blocker — fix in plate-auth or revert.
7. Security tests
These overlap with unit/integration tests above but are extracted as a security suite for the Sprint 0 security review.
T-SEC01 — HMAC tamper rejected
- Same as T-UT07. Mutate
rolefield, expect 401 + audit event.
T-SEC02 — Nonce replay rejected
- Same as T-UT06. Hit
/auth/exchange/consumetwice with same envelope, second call returns 409.
T-SEC03 — Envelope max-age rejected
- Same as T-UT08. Clock-skew
iatby 70s (max-age=60s), expect 401.
T-SEC04 — Expired JWT rejected
- Test: Issue JWT with
exp = now - 1s, send to/api/me. - Then: 401. No SecurityContext populated.
T-SEC05 — Missing JWT secret fails startup
- Test: Run Spring Boot integration test with
plate.auth.jwt.secretunset. - Then: Context fails to start with
BindValidationExceptionmentioningjwt.secret—@NotBlankviolated.
T-SEC06 — Short JWT secret fails startup
- Test:
plate.auth.jwt.secret=tooShort(8 chars). - Then: Context fails to start —
@Size(min=32)violated. Clear error message.
T-SEC07 — CORS unknown origin rejected
- Test: Preflight
OPTIONS /auth/loginwithOrigin: https://attacker.example. - Then: No
Access-Control-Allow-Originreturned. Browser would block the actual request.
T-SEC08 — SQL injection probe
- Test: POST
/auth/loginbody{"email":"a@b.de' OR 1=1 --","password":"x"}. - Then: 401 (bad credentials), no SQL error leaks. Verifies JPA parameter binding, not string concat.
T-SEC09 — Constant-time HMAC compare
- Test: Static-analysis spot check (
HmacEnvelope.verifyusesMessageDigest.isEqual). Also a microbenchmark comparing two HMACs that differ at byte 0 vs byte 31 — timing variance < 5%.
T-SEC10 — Refresh-token rotation
- Test: Issue tokens via
/auth/exchange/consume. Use refresh once → new token pair. Old refresh used again → 401. - Note: This is a v0.2 candidate per Roadmap.md; for v0.1 we accept refresh re-use as-is and document in Open-Questions.md.
8. Performance smoke
Run a JMH or simple k6 script against a Postgres-backed test instance (Testcontainers or local Docker). Goal is regression detection, not absolute benchmarking — record numbers as baseline for v0.2.
T-PERF01 — Exchange consume p95 < 50ms
- Method: 1000 sequential
POST /auth/exchange/consume(single-replica) with valid envelopes. - Then: p95 latency < 50ms on dev hardware. If > 100ms, flag for optimization (likely DB index or nonce lookup).
T-PERF02 — Login p95 < 300ms
- Method: 500 sequential
POST /auth/loginwith valid credentials. - Then: p95 < 300ms (includes bcrypt cost factor 10 = 60-100ms baseline). If > 500ms, investigate.
T-PERF03 — JWT filter overhead per request
- Method: Microbenchmark: 10,000 requests to a no-op endpoint protected by
JwtAuthenticationFilter. - Then: Filter overhead p95 < 5ms.
9. Acceptance criteria → tests matrix
Mapping back to Sprint-0-Plan.md § 10.5:
| A# | Acceptance criterion | Test IDs |
|---|---|---|
| A1 | Backend artifact builds + publishes | Build pipeline (W6) + T-UT10..13, T-IT02, T-IT04..08 |
| A2 | Exchange flow works artifact-to-artifact | T-UT04..09, T-IT03, T-FE02, T-SEC01..03, T-SEC09 |
| A3 | Flyway applies on a fresh DB | T-IT01 |
| A4 | Config namespace plate.auth.* |
T-UT01..03, T-UT14, T-IT02, T-SEC04..06 |
| A5 | SPI seams are clean | T-UT11, T-UT15, T-IT09 |
| A6 | Frontend artifact bundles + ships ESM | T-FE01..05 |
| A7 | InspectFlow refactor green | T-E2E01..06 |
| A8 | No regressions vs Sprint 14.6 baseline | T-E2E01..06 + T-PERF01..03 |
Every Sprint 0 acceptance criterion has at least one mapped test. A7 is the integration gate — the InspectFlow E2E suite must remain green after the dependency swap.
10. Test data + fixtures
- Users:
admin@plate.test(USER+ADMIN in Org 1),user@plate.test(USER in Org 1),outsider@plate.test(no memberships). - Orgs: Test SPI declares
(COMPANY, 1)and(COMPANY, 2)as valid. - Secrets: Test profile uses 32-char hex strings (
plate.auth.jwt.secret=0000...) — not production-grade entropy. - Time: Tests requiring clock control use
java.time.Clockinjected via test config (NOTThread.sleep). - Mailers: SPI default replaced by
RecordingMailerthat captures invocations.
Fixtures live in backend/src/test/resources/sql/seed-plate-auth.sql and frontend/plate-auth/test/fixtures/.
11. Test infrastructure
| Layer | Tooling |
|---|---|
| Backend unit | JUnit 5 + Mockito + AssertJ |
| Backend integration | @SpringBootTest + Testcontainers Postgres 16 |
| Frontend unit | Vitest + jsdom |
| Frontend HMAC golden vector | Hand-built fixture pulled from backend test output (committed) |
| E2E | InspectFlow Playwright suite (no changes to plate-auth wiki) |
| Performance | k6 script or JMH (TBD — pick simplest; not blocking Sprint 0) |
| CI | Gitea Actions ci.yml (Sprint-0-Plan § 8.2) — runs unit + integration on every push; E2E runs on InspectFlow's pipeline post-swap |
12. Out of scope for Sprint 0
The following are explicitly not tested in Sprint 0 and are deferred to v0.2+:
- Multi-replica nonce store (Redis-backed
NonceStoreSPI) → v0.3 - Refresh-token rotation with revocation list → v0.2
- Microsoft Entra ID provider → v0.2 (see Open-Questions.md Q02)
- Email magic-link provider → v0.2 (see Q04)
- Account lockout after N failed logins → v0.2
- 2FA / TOTP → v1.0
- SAML / SCIM → never (see Vision.md non-goals)
- Load testing > 1000 req/s → not a v0.x concern
- Penetration testing (formal) → v1.0
13. Open issues / risks for the test plan
| ID | Issue | Mitigation |
|---|---|---|
| TR-1 | Performance numbers depend on dev hardware → not portable | Capture baseline + measure deltas, not absolutes |
| TR-2 | Testcontainers startup adds ~20s per test class | Use @Testcontainers(disabledWithoutDocker = true) + shared static container per test suite |
| TR-3 | Frontend HMAC vector must stay in sync with backend | Add a one-shot script generate-hmac-fixture.sh that re-emits the golden vector from backend test output |
| TR-4 | InspectFlow E2E couples our release to InspectFlow's CI health | Acceptable — InspectFlow is the first consumer by design (see Migration-InspectFlow.md) |
| TR-5 | No staging environment for plate-auth alone | Library has no runtime of its own — InspectFlow CI is the staging |
14. Cross-references
- Home.md
- Vision.md
- Architecture.md
- Roadmap.md
- Sprint-0-Assessment.md
- Sprint-0-Plan.md
- Open-Questions.md
- Integration-Guide.md
- Migration-InspectFlow.md
End of Sprint-0-Testplan.md (v1).