# Concept: Close all testing gaps for [`apps/website`](apps/website:1) This plan defines a **route-driven** test strategy to prove the website is **fully working** without any **visual UI tests** (no screenshots/video/trace assertions). It leverages the existing unified Docker E2E environment described in [`README.docker.md`](README.docker.md:94). ## 1) Definition of done (route-driven) For **every route** defined by [`routes`](apps/website/lib/routing/RouteConfig.ts:120) and enumerated by [`WebsiteRouteManager.getWebsiteRouteInventory()`](tests/shared/website/WebsiteRouteManager.ts:41), we prove: 1. **SSR behavior** (HTTP black-box) - Correct status: 200, 302, 404, 500. - Correct redirect targets for unauthenticated and wrong-role cases. - SSR HTML sanity markers and absence of Next.js error markers. 2. **Client-side behavior** - Client navigation works for representative transitions (no brittle selectors). - No unexpected console errors (allowlist only for known benign warnings). 3. **RBAC correctness** - Roles: unauthenticated, authenticated, admin, sponsor. - For every route: allowed roles can access, disallowed roles are redirected/forbidden as defined. 4. **Negative cases are covered** - Invalid route params (non-existent IDs). - Expired/invalid session. - API 5xx/timeouts and other failure injections. - Missing/invalid env configuration (fail fast rather than silently falling back). The single source of truth is the route contract inventory, evolving from [`getWebsiteRouteContracts()`](tests/shared/website/RouteContractSpec.ts:44). ## 2) Architecture: one contract, three enforcement layers ```mermaid flowchart TD A[Route inventory RouteConfig + WebsiteRouteManager] --> B[Route contracts RouteContractSpec] B --> C[Unit tests invariants] B --> D[Integration tests SSR HTTP black box] B --> E[E2E tests Playwright in Docker] ``` ### 2.1 Single source of truth: Route contracts Current implementation: [`getWebsiteRouteContracts()`](tests/shared/website/RouteContractSpec.ts:44) Conceptual evolution: - Extend the contract to be **scenario-based**: - unauth - auth - admin - sponsor - wrong-role variants - Make expectations explicit per scenario: - expectedStatus: ok, redirect, notFoundAllowed, errorRoute - expectedRedirectTo (pathname) - SSR assertions: `ssrMustContain`, `ssrMustNotContain`, `minTextLength` - runtime assertions: `consoleErrorPolicy` (allowlist + denylist) This keeps tests from becoming a pile of one-off scripts; instead, tests are **generated** from the contract. ### 2.2 Unit tests: invariants that keep the system honest Target: - Routing classification and RBAC rules: - [`routeMatchers.isPublic()`](apps/website/lib/routing/RouteConfig.ts:263) - [`routeMatchers.requiresRole()`](apps/website/lib/routing/RouteConfig.ts:326) - API coupling correctness: - base URL selection via [`getWebsiteApiBaseUrl()`](apps/website/lib/config/apiBaseUrl.ts:6) - HTTP client error mapping via [`BaseApiClient`](apps/website/lib/api/base/BaseApiClient.ts:11) Desired outcome: - A refactor of routing or API base URL logic breaks unit tests immediately. ### 2.3 Integration tests: SSR is an HTTP contract We validate SSR using a black-box approach: - Bring up the website server using the existing harness patterns under [`tests/integration/harness`](tests/integration/harness/index.ts:1). - For each route contract: - Issue an HTTP request to the website. - Assert status/redirect. - Assert SSR markers and absence of `__NEXT_ERROR__` as already encoded in [`DEFAULT_SSR_MUST_NOT_CONTAIN`](tests/shared/website/RouteContractSpec.ts:35). Why this layer matters: - It catches failures that E2E might hide (for example, client-side redirecting after a bad SSR response). ### 2.4 E2E tests: runtime and navigation, in unified Docker We rely on the unified Docker stack described in [`README.docker.md`](README.docker.md:94) and run tests via [`npm run test:e2e:website`](package.json:120). Key properties: - Playwright visuals are disabled by configuration: [`playwright.website.config.ts`](playwright.website.config.ts:41). - E2E must validate: - route loads and final URL matches expectation - RBAC works (unauth redirects, wrong-role redirects) - no unexpected console errors using [`ConsoleErrorCapture`](tests/shared/website/ConsoleErrorCapture.ts:1) - representative navigation flows (href-based selectors) like [`navigation.e2e.test.ts`](tests/e2e/website/navigation.e2e.test.ts:7) Additionally: - Promote “nightly exhaustive” execution by enabling [`RUN_EXHAUSTIVE_E2E`](playwright.website.config.ts:25) to include heavier suites (like [`website-pages.e2e.test.ts`](tests/nightly/website/website-pages.e2e.test.ts:65)). ## 3) Close gaps by building a route coverage matrix We produce a matrix derived from [`WebsiteRouteManager.getWebsiteRouteInventory()`](tests/shared/website/WebsiteRouteManager.ts:41): - Rows: each route - Columns: scenarios - unauth → expected redirect or ok - auth → ok or role redirect - admin → ok on admin routes - sponsor → ok on sponsor routes - invalid param → notFoundAllowed or inline error behavior - API failure injection → error boundaries behave, no runtime crash This matrix becomes a checklist that prevents “coverage by vibe”. ## 4) Failure mode strategy (no visual tests) ### 4.1 Invalid IDs and missing resources Use [`WebsiteRouteManager.getParamEdgeCases()`](tests/shared/website/WebsiteRouteManager.ts:83) to ensure invalid IDs lead to: - 404 where appropriate, or - 200 with an inline not-found message for CSR-heavy pages ### 4.2 Session drift and wrong-role Drive auth contexts using [`WebsiteAuthManager.createAuthContext()`](tests/shared/website/WebsiteAuthManager.ts:1) and assert redirect behavior for: - auth user hitting admin route - auth user hitting sponsor route - unauth user hitting protected routes ### 4.3 API failures and timeouts Test website behavior when the API returns 5xx or times out: - SSR path: prove the server renders an error boundary (or specific 500 route) with the expected status. - CSR path: prove it does not crash and produces deterministic error messaging. This should be done without mocking UI visuals; we assert status, URL, and text markers. ## 5) Test isolation: prevent real external calls We must ensure tests never hit real third-party services (analytics, payments, email): - For E2E: Playwright network interception with a denylist of external hosts. - For Node tests: fetch interception in test setup like [`tests/setup.ts`](tests/setup.ts:1). Outcome: if a new external dependency is added, tests fail fast with a clear message. ## 6) CI pipeline shape One deterministic pipeline (PR gating): - Lint: [`website:lint`](package.json:145) - Typecheck: [`typecheck:targets`](package.json:141) - Unit tests: [`test:unit`](package.json:135) - Integration tests: [`test:integration`](package.json:129) - E2E unified Docker: [`test:e2e:website`](package.json:120) Nightly: - Run exhaustive E2E with [`RUN_EXHAUSTIVE_E2E`](playwright.website.config.ts:25). ## 7) Proposed implementation sequence 1. Extend the route contract structure in [`RouteContractSpec`](tests/shared/website/RouteContractSpec.ts:1) to include per-role scenarios and explicit negative-case expectations. 2. Generate tests from contracts: - integration SSR suite - e2e runtime and RBAC suite 3. Add failure-mode suites (invalid IDs, expired session, API 5xx/timeout). 4. Add network denylist guards. 5. Wire CI scripts and keep nightly exhaustive separate.