Skip to content

Commit d376aac

Browse files
LEANDERANTONYclaude
andcommitted
test(frontend): stand up Vitest baseline + wire into CI (TEST-1)
The Next.js app shipped with zero automated tests; CI ran lint + build only, so every piece of client logic (the API error humanizer, auth-session helpers, the quota meter, the tier-gate UI PR #6 changed) was unverified, and the four recent launch commits had no regression net. Stood up Vitest + React Testing Library + jsdom (the one justified new dependency group for this PR) with a jsdom config (esbuild JSX, @/ alias) and a jest-dom setup whose type augmentation keeps the existing next-build type pass green over .test files. Added a 'npm test' script and a CI Test step between Lint and Build (reusing the same npm ci install). The FE-SEC-1 security headers were extracted to src/lib/securityHeaders.ts (byte-identical) so they can be asserted without loading the Sentry-wrapped next.config. 24 tests across 7 files cover the baseline plus the deferred component tests from earlier commits: humanizeApiError status/leak mapping; api request() 429 -> TierLimitExceededError + POST/credentials wiring (the CRITICAL-2 upgrade-CTA contract); auth-session redirect/cleanup helpers; the security-header set (FE-SEC-1/F4); the useAccessibleDialog Escape + focus-trap behaviour (A11Y-1/A11Y-2); TokenUsageMeter math + over-tone; and the AnalysisRunner premium tier gate (PR #6: a Free tap fires the upgrade CTA, a Pro tap flips the run mode). Fixes: TEST-1 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6b454c6 commit d376aac

14 files changed

Lines changed: 2435 additions & 61 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ jobs:
7171
working-directory: frontend
7272
run: npm run lint
7373

74+
- name: Test
75+
working-directory: frontend
76+
run: npm test
77+
7478
- name: Build
7579
working-directory: frontend
7680
run: npm run build

frontend/next.config.ts

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,14 @@
11
import type { NextConfig } from "next";
22
import { withSentryConfig } from "@sentry/nextjs";
33

4+
// Security response headers (review FE-SEC-1). Defined in src/lib/securityHeaders
5+
// so a unit test can import and assert them without loading this Sentry-wrapped
6+
// config. X-Frame-Options is enforcing; the CSP ships Report-Only (see that file).
7+
import { securityHeaders } from "./src/lib/securityHeaders";
8+
49
const apiRewriteTarget =
510
process.env.API_REWRITE_TARGET ?? "http://127.0.0.1:8000";
611

7-
// Security response headers (review FE-SEC-1). The app shipped with NO
8-
// CSP / framing / HSTS / nosniff / referrer headers, leaving clickjacking
9-
// and zero XSS defense-in-depth on a surface that injects server-built
10-
// HTML into srcDoc iframes.
11-
//
12-
// X-Frame-Options is ENFORCING immediately (the app is never legitimately
13-
// framed). The CSP ships as Content-Security-Policy-Report-Only so a day
14-
// of real traffic reveals any origin this allowlist misses BEFORE it is
15-
// switched to enforcing — do NOT flip it to enforce in this change.
16-
const contentSecurityPolicyReportOnly = [
17-
"default-src 'self'",
18-
"base-uri 'self'",
19-
"object-src 'none'",
20-
"frame-ancestors 'none'",
21-
// Next.js injects inline bootstrap scripts; 'unsafe-inline' is the
22-
// pragmatic interim (a nonce-based policy is the tightening follow-up).
23-
"script-src 'self' 'unsafe-inline' https://va.vercel-scripts.com",
24-
"style-src 'self' 'unsafe-inline'",
25-
"img-src 'self' data: blob: https:",
26-
"font-src 'self' data:",
27-
// PostHog (analytics + replay), Sentry (errors), Supabase (auth),
28-
// Lemon Squeezy (checkout — "Coming soon"), Vercel (analytics). The
29-
// backend API is same-origin via the /api rewrite, so 'self' covers it.
30-
"connect-src 'self' https://eu.i.posthog.com https://eu-assets.i.posthog.com https://*.sentry.io https://*.supabase.co https://*.lemonsqueezy.com https://va.vercel-scripts.com https://vitals.vercel-insights.com",
31-
"frame-src 'self' https://*.lemonsqueezy.com",
32-
"worker-src 'self' blob:",
33-
"form-action 'self'",
34-
].join("; ");
35-
36-
const securityHeaders = [
37-
// Enforcing immediately — defends against clickjacking now.
38-
{ key: "X-Frame-Options", value: "DENY" },
39-
// Report-Only for now (see note above). Tighten + switch to enforcing
40-
// in a follow-up once 48h of reports confirm the allowlist.
41-
{
42-
key: "Content-Security-Policy-Report-Only",
43-
value: contentSecurityPolicyReportOnly,
44-
},
45-
{
46-
key: "Strict-Transport-Security",
47-
value: "max-age=63072000; includeSubDomains; preload",
48-
},
49-
{ key: "X-Content-Type-Options", value: "nosniff" },
50-
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
51-
];
52-
5312
const nextConfig: NextConfig = {
5413
allowedDevOrigins: ["localhost", "127.0.0.1"],
5514
async headers() {

0 commit comments

Comments
 (0)