@luckystack/test-runner 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.1] - 2026-09-03
11
+
12
+ ### Fixed
13
+
14
+ - Layer 5: `ctx.callApi(input)` on a `GET` route silently dropped `input`, so the server always saw `data = {}` and any `GET` route with a required field rejected the call with `api.invalidInputType` before `main()` ran. `input` now travels as the `__luckystack_data` query-string value, encoded by the shared `buildRoutedGetUrl` helper from `@luckystack/core` — the same contract the real client sends and the server reads. Non-`GET` routes are unchanged (JSON body).
15
+
10
16
  ## [0.8.5] - 2026-08-17
11
17
 
12
18
  ### Changed
package/dist/index.d.ts CHANGED
@@ -344,7 +344,15 @@ interface DiscoveredTestFile {
344
344
  }
345
345
  declare const discoverCustomTestFiles: (srcDir?: string) => DiscoveredTestFile[];
346
346
  interface TestContext {
347
- /** Invoke the route under test. Page / name / version are baked in. */
347
+ /**
348
+ * Invoke the route under test. Page / name / version are baked in.
349
+ *
350
+ * `input` travels exactly like the real client sends it: as the JSON body
351
+ * for POST / PUT / DELETE routes, and as the `__luckystack_data` query-string
352
+ * value for GET routes (a GET cannot carry a body). Either way the server
353
+ * validates it as the route's `data`, so a GET route with required fields
354
+ * receives them.
355
+ */
348
356
  callApi: <TInput = unknown, TOutput = unknown>(input: TInput) => Promise<TOutput>;
349
357
  callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: {
350
358
  receiver?: string;
package/dist/index.js CHANGED
@@ -939,7 +939,15 @@ import crypto from "crypto";
939
939
  import fs2 from "fs";
940
940
  import path2 from "path";
941
941
  import { pathToFileURL } from "url";
942
- import { getCsrfConfig, getPrismaClient, getProjectConfig as getProjectConfig3, getSrcDir, tryCatch as tryCatch5, tryCatchSync } from "@luckystack/core";
942
+ import {
943
+ buildRoutedGetUrl,
944
+ getCsrfConfig,
945
+ getPrismaClient,
946
+ getProjectConfig as getProjectConfig3,
947
+ getSrcDir,
948
+ tryCatch as tryCatch5,
949
+ tryCatchSync
950
+ } from "@luckystack/core";
943
951
 
944
952
  // src/streamWatcher.ts
945
953
  import { io as createSocketIoClient } from "socket.io-client";
@@ -1226,7 +1234,7 @@ var parseResponse = async (response, state) => {
1226
1234
  };
1227
1235
  var buildCallApi = (baseUrl, routePath, state, cookieName, method) => {
1228
1236
  return async (input) => {
1229
- const url = `${baseUrl.replace(/\/$/, "")}/api/${routePath}`;
1237
+ const path3 = `${baseUrl.replace(/\/$/, "")}/api/${routePath}`;
1230
1238
  const headers = { "Content-Type": "application/json", Origin: new URL(baseUrl).origin };
1231
1239
  if (state.token) headers.Cookie = `${cookieName}=${state.token}`;
1232
1240
  if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;
@@ -1235,7 +1243,9 @@ var buildCallApi = (baseUrl, routePath, state, cookieName, method) => {
1235
1243
  controller.abort();
1236
1244
  }, DEFAULT_CALL_TIMEOUT_MS);
1237
1245
  const init = { method, headers, signal: controller.signal };
1238
- if (method !== "GET" && method !== "HEAD") init.body = JSON.stringify(input ?? {});
1246
+ const bodyless = method === "GET" || method === "HEAD";
1247
+ const url = bodyless ? buildRoutedGetUrl(path3, input ?? {}) : path3;
1248
+ if (!bodyless) init.body = JSON.stringify(input ?? {});
1239
1249
  const [fetchError, response] = await tryCatch5(() => fetch(url, init));
1240
1250
  clearTimeout(timeoutHandle);
1241
1251
  if (fetchError || !response) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/walkEndpoints.ts","../src/probeRequest.ts","../src/contractCheck.ts","../src/testLayerHelpers.ts","../src/runContractTests.ts","../src/authEnforcementCheck.ts","../src/runAuthEnforcementTests.ts","../src/rateLimitCheck.ts","../src/resetServerState.ts","../src/runRateLimitTests.ts","../src/csrfEnforcementCheck.ts","../src/runCsrfEnforcementTests.ts","../src/fuzzCheck.ts","../src/runFuzzTests.ts","../src/resolveTestBaseUrl.ts","../src/resolveTestEnvironment.ts","../src/schemaSampleInput.ts","../src/testDataMarker.ts","../src/reporter.ts","../src/customTests.ts","../src/streamWatcher.ts","../src/runAllTests.ts","../src/extensionRegistry.ts","../src/runRegisteredLayers.ts"],"sourcesContent":["import type { EndpointDescriptor, HttpMethod, SyncMethodMap } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\n\n//? Must stay in sync with the `HttpMethod` union in types.ts. Only values that\n//? appear in both sets are valid probes — anything else is an unrecognised method.\nconst VALID_HTTP_METHODS = new Set<HttpMethod>(['GET', 'POST', 'PUT', 'DELETE']);\n\nconst toHttpMethod = (raw: string): HttpMethod | null => {\n //? Guard against a stale or hand-edited generated map carrying an unrecognised\n //? method string. An unknown value would propagate silently as a typed HttpMethod\n //? and break every probe that uses it. Return null so the caller can skip it.\n const upper = raw.toUpperCase();\n for (const m of VALID_HTTP_METHODS) {\n if (m === upper) return m;\n }\n return null;\n};\n\nexport const walkEndpoints = (apiMethodMap: ApiMethodMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(apiMethodMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, rawMethod] of Object.entries(versionMap)) {\n if (rawMethod === undefined) continue;\n const method = toHttpMethod(rawMethod);\n if (!method) {\n //? Unrecognised method in the generated map — log and skip rather than\n //? silently coercing, which would produce invalid probes.\n console.warn(`[test-runner] walkEndpoints: skipping api/${page}/${name}/${version} — unrecognised method '${rawMethod}' in apiMethodMap`);\n continue;\n }\n endpoints.push({\n page,\n name,\n version,\n method,\n fullPath: `api/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n\n//? Walk the generated `syncMethodMap` and produce a flat list of sync-route\n//? descriptors. Each descriptor's `fullPath` is `sync/<page>/<name>/<version>`.\n//? Sync routes always use POST over the HTTP-fallback transport regardless of\n//? the declared method, so the method is hardcoded to `POST` here.\nexport const walkSyncEndpoints = (syncMethodMap: SyncMethodMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(syncMethodMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, method] of Object.entries(versionMap)) {\n if (method === undefined) continue;\n endpoints.push({\n page,\n name,\n version,\n method: 'POST',\n fullPath: `sync/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n","//? Single-source fetch probe used by every check layer (contract, auth,\n//? csrf, rate-limit). Centralises the AbortController + timeout + tryCatch\n//? pattern so it isn't copied into each check file.\n\nimport { tryCatch } from '@luckystack/core';\nimport type { HttpMethod } from './types';\n\nexport interface ProbeRequestInput {\n url: string;\n method: HttpMethod;\n baseUrl: string;\n body?: unknown;\n headers?: Record<string, string>;\n requestTimeoutMs: number;\n}\n\nexport interface ProbeResponse {\n httpStatus: number;\n parsed: { status?: string; errorCode?: string } | null;\n}\n\n/**\n * Send a single timed fetch probe and parse the JSON envelope. Returns\n * `null` when the request itself fails (network error or abort).\n */\nexport const sendProbe = async (input: ProbeRequestInput): Promise<ProbeResponse | null> => {\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, input.requestTimeoutMs);\n\n const [fetchError, response] = await tryCatch(() => fetch(input.url, {\n method: input.method,\n headers: {\n 'Content-Type': 'application/json',\n //? Browsers attach Origin on state-changing requests; the server's origin\n //? policy fail-closes POST/PUT/DELETE without one.\n 'Origin': new URL(input.baseUrl).origin,\n ...input.headers,\n },\n body: input.method === 'GET' ? undefined : JSON.stringify(input.body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n\n if (fetchError || !response) return null;\n\n const [, parsed] = await tryCatch<{ status?: string; errorCode?: string } | null, undefined>(\n async () => await response.json() as { status?: string; errorCode?: string },\n );\n\n return { httpStatus: response.status, parsed: parsed ?? null };\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\nexport interface ContractCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * Optional input factory. Defaults to `{}`. When an endpoint requires a\n * specific shape to not crash, callers can override per-endpoint here.\n */\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /**\n * Optional headers (auth tokens, cookies). Applied to every request.\n */\n headers?: Record<string, string>;\n /**\n * Per-call request timeout in ms. Defaults to 5000. Bump for slow endpoints\n * (AI calls, large reports) where the framework default would false-fail\n * the contract check.\n */\n requestTimeoutMs?: number;\n}\n\n//? Contract layer: send any-shape input, accept any response where\n//? `status` is `success` or `error` (with an `errorCode`). Anything else is\n//? a contract violation — 5xx, thrown exceptions, or shapes outside this\n//? envelope.\n//?\n//? The goal here is NOT to prove every endpoint works end-to-end; it's to\n//? catch endpoints that crash the server or return garbage on well-formed\n//? empty input. Real behavior tests go in the per-endpoint test file.\nexport const runContractCheck = async (input: ContractCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n const result = await sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (!parsed) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'JSON parse failed',\n durationMs,\n };\n }\n\n if (parsed.status !== 'success' && parsed.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.status === 'error' && !parsed.errorCode) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n reason: 'Error response missing `errorCode`',\n durationMs,\n };\n }\n\n //? When `inputFor` produced a non-empty sample body but the route returned an\n //? error, the probe may have failed its own Zod validation rather than proving\n //? the contract. Counting that as a PASS is misleading — the happy-path was\n //? never exercised. Classify as `skipped` so the caller knows the body was\n //? rejected before the handler ran and can supply a better sample.\n const bodyIsNonEmpty = body !== null\n && typeof body === 'object'\n && !Array.isArray(body)\n && Object.keys(body).length > 0;\n if (input.inputFor && bodyIsNonEmpty && parsed.status === 'error') {\n return {\n endpoint,\n status: 'skipped',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `inputFor produced a non-empty sample but route returned error (${parsed.errorCode ?? 'unknown'}); provide a valid sample via inputFor to test the happy path`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: parsed.status,\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","//? Shared helpers for the sweep test layers (contract, auth-enforcement,\n//? rate-limit, fuzz). Centralizes the skip-matching, meta-map queries, and\n//? summary-count math that were previously copy-pasted across each layer file.\n\nimport type {\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n HttpMethod,\n RunContractSummary,\n} from './types';\n\n//? Single source of truth for the canonical layer names used in both the\n//? orchestrator (`runAllTests`) and the reporter (`logRunAllSummary`). Keeping\n//? them here prevents silent drift between the reporting labels and the summary\n//? property keys when layers are added or renamed.\nexport const LAYER_KEYS = {\n contract: 'contract',\n auth: 'auth-enforcement',\n rateLimit: 'rate-limit',\n csrf: 'csrf-enforcement',\n fuzz: 'fuzz',\n custom: 'custom',\n} as const;\n\n//? HTTP methods that carry a body and mutate server state. The CSRF middleware\n//? enforces a token only on these. Fuzz / rate-limit layers share this set to\n//? avoid firing junk bodies at mutating authenticated routes in cookie-mode.\nexport const STATE_CHANGING_METHODS = new Set<HttpMethod>(['POST', 'PUT', 'DELETE']);\n\n/**\n * Whether an endpoint is in the explicit skip list. Matched against\n * `<page>/<name>` (version-agnostic) and `<page>/<name>/<version>`\n * (version-specific).\n */\nexport const shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\n if (skip.length === 0) return false;\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\n const versionless = `${endpoint.page}/${endpoint.name}`;\n return skip.includes(versioned) || skip.includes(versionless);\n};\n\n/** Whether the endpoint's meta entry declares `auth.login: true`. */\nexport const requiresLogin = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n //? `apiMetaMap` is a consumer-generated RUNTIME artifact — the static type\n //? declares `auth` as required, but a stale/partial entry can be missing it.\n //? Read defensively so a malformed entry surfaces as `false` (no login) and\n //? gets handled by the layer's skip path, instead of throwing a TypeError\n //? outside the per-endpoint tryCatch and aborting the whole layer.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return meta?.auth?.login ?? false;\n};\n\n/** Whether the endpoint requires ANY authorization: `auth.login: true` OR a\n * declared non-empty `auth.additional[]` predicate list (role/tenant/ownership\n * guards). The auth sweep must probe BOTH — an additional[]-only route (login:\n * false) is still authz-protected and an unauth call must be rejected. */\nexport const hasAuthRequirement = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return (meta?.auth?.login ?? false) || (meta?.auth?.hasAdditional ?? false);\n};\n\n/** Whether the meta map carries an entry for this endpoint at all. */\nexport const hasMetaEntry = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean =>\n apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version] !== undefined;\n\n/**\n * The endpoint's declared numeric rate limit, or `null` when it has none\n * (`false`/`undefined`) or no meta entry exists.\n */\nexport const getRateLimit = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): number | null => {\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n if (!meta || meta.rateLimit === false || meta.rateLimit === undefined) return null;\n return meta.rateLimit;\n};\n\n/** Aggregate pass/fail/skip counts over a sweep layer's results. */\nexport const calculateSummary = (results: ContractCheckResult[]): RunContractSummary => ({\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n skipped: results.filter(r => r.status === 'skipped').length,\n results,\n});\n","import { walkEndpoints } from './walkEndpoints';\nimport { runContractCheck } from './contractCheck';\nimport { shouldSkip, calculateSummary } from './testLayerHelpers';\nimport type { ApiMethodMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunContractTestsInput {\n apiMethodMap: ApiMethodMap;\n baseUrl: string;\n /**\n * Endpoints to skip. Matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>` (version-specific). Useful for endpoints that\n * are known to need real input (e.g. a file upload).\n */\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runContractTests = async (input: RunContractTestsInput): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? Serial rather than parallel so we don't trip rate limiters or mask\n //? endpoint ordering bugs. Swap to a pool later if walk-time matters.\n const result = await runContractCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n headers: input.headers,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error codes the framework emits when an unauthenticated request hits\n//? a protected endpoint: `auth.required` for an `auth.login: true` route, and\n//? `auth.forbidden` for a route guarded ONLY by `auth.additional[]` predicates\n//? (login: false) — validateRequest forbids the anonymous caller. Either is a PASS;\n//? both mean the guard fired. See packages/api/src/handleHttpApiRequest.ts +\n//? packages/core/src/validateRequest.ts.\nconst EXPECTED_ERROR_CODES = new Set(['auth.required', 'auth.forbidden']);\n\nexport interface AuthEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n}\n\n//? Auth-enforcement layer: for each `auth.login: true` endpoint, call it with\n//? NO session headers/cookies. Expect the server to reject with `auth.required`.\n//?\n//? A passing endpoint here proves the login guard isn't silently bypassed.\n//? Endpoints that return `success` without a session are a critical finding.\nexport const runAuthEnforcementCheck = async (\n input: AuthEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n //? No session cookie, no auth header — deliberately. Origin IS sent so the\n //? server's origin policy doesn't 403 before the auth check can run.\n const result = await sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'success',\n reason: 'auth.login endpoint returned success without a session',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.errorCode === undefined || !EXPECTED_ERROR_CODES.has(parsed.errorCode)) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `expected an auth-rejection errorCode (${[...EXPECTED_ERROR_CODES].join(' | ')}) but got '${parsed.errorCode ?? '(missing)'}'`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runAuthEnforcementCheck } from './authEnforcementCheck';\nimport { shouldSkip, hasAuthRequirement, hasMetaEntry, calculateSummary } from './testLayerHelpers';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunAuthEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n baseUrl: string;\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runAuthEnforcementTests = async (\n input: RunAuthEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n //? Order matters: the auth-requirement check must run BEFORE shouldSkip so a\n //? protected endpoint in the explicit skip list is still recorded as\n //? `skipped` in the results (not silently dropped as a public route).\n //? Reversing the order would hide the skip from the summary. We gate on\n //? `hasAuthRequirement` (login OR additional[]-predicates) so role/tenant/\n //? ownership-guarded routes with `login: false` are also probed.\n if (!hasMetaEntry(input.apiMetaMap, endpoint)) {\n //? Route is in `apiMethodMap` but ABSENT from `apiMetaMap` (partial drift /\n //? hand-trimmed map / generator bug). Its auth requirement is unknowable, so\n //? this layer can't assert it. Record a `skipped` result (NOT a silent\n //? continue) so the gap is visible per-route in the summary instead of the\n //? route masquerading as a verified-public endpoint. (M8)\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'No apiMetaMap entry — auth requirement unverifiable',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (!hasAuthRequirement(input.apiMetaMap, endpoint)) {\n //? Truly-public endpoints (meta entry present, login:false, no additional[])\n //? can't be tested by this layer — skip silently, no noise. They're still\n //? covered by the contract layer.\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runAuthEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\nconst EXPECTED_ERROR_CODE = 'api.rateLimitExceeded';\n\n//? Error codes the server emits BEFORE the rate-limit pipeline runs (auth guard\n//? + server-level CSRF middleware). When a drain response carries one of these,\n//? the bucket was never incremented — the final probe will fail for the wrong\n//? reason and should be classified as `skipped` rather than a real failure.\n//? These MUST match the framework's actual pre-limiter codes (pipeline order:\n//? auth → rateLimit, CSRF middleware before the limiter):\n//? - `auth.required` — login:true route called without a session\n//? (handleHttpApiRequest.ts).\n//? - `auth.forbidden` — additional[]-predicate failed / login:false guard\n//? (handleHttpApiRequest.ts).\n//? - `auth.csrfMismatch` — cookie-mode state-changing route, CSRF gate\n//? (server/httpRoutes/csrfMiddleware.ts).\n//? Validation (`api.invalidInputType`) runs AFTER the limiter, so it is NOT a\n//? pre-limiter code and is deliberately excluded.\nconst PRE_LIMITER_ERROR_CODES = new Set([\n 'auth.required',\n 'auth.forbidden',\n 'auth.csrfMismatch',\n]);\n\nexport interface RateLimitCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n rateLimit: number;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 10000. */\n requestTimeoutMs?: number;\n}\n\n//? Rate-limit layer: fire `rateLimit + 1` requests back to back and assert the\n//? last one is rejected with `api.rateLimitExceeded`. All preceding requests\n//? are allowed to return either success or any other envelope — only the N+1\n//? matters for this layer.\n//?\n//? This layer mutates server-side state (limiter counters). Run it after the\n//? contract + auth layers, or against a dedicated test instance. The server's\n//? clearAllRateLimits() utility can reset state between runs.\nexport const runRateLimitCheck = async (input: RateLimitCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl, rateLimit } = input;\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\n const send = () => sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs,\n });\n\n //? Fire `rateLimit` allowed requests first (drain the bucket), then a final\n //? request that should be blocked. Serial so the server sees a clean order.\n //? Inspect EVERY drain response for pre-limiter rejections (CSRF/auth/\n //? validation): if the server rejects before the limiter runs at any point,\n //? the bucket was never incremented for that request, the final probe will\n //? fail for the wrong reason, and we classify the check as `skipped`.\n for (let i = 0; i < rateLimit; i += 1) {\n const drained = await send();\n if (!drained) continue;\n const errorCode = drained.parsed?.errorCode;\n if (errorCode && PRE_LIMITER_ERROR_CODES.has(errorCode)) {\n return {\n endpoint,\n status: 'skipped',\n durationMs: Date.now() - started,\n reason: `drain request ${i + 1} rejected pre-limiter (${errorCode}); bucket was never incremented — check CSRF/auth headers`,\n };\n }\n }\n\n const final = await send();\n const durationMs = Date.now() - started;\n\n if (!final) {\n return {\n endpoint,\n status: 'fail',\n reason: 'Final rate-limit probe request failed to return a response',\n durationMs,\n };\n }\n\n const { httpStatus, parsed } = final;\n\n if (parsed?.status === 'error' && parsed.errorCode === EXPECTED_ERROR_CODE) {\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n }\n\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus,\n errorCode: parsed?.errorCode,\n reason: `expected '${EXPECTED_ERROR_CODE}' on request ${rateLimit + 1} but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n};\n","import { getProjectConfig, tryCatch } from '@luckystack/core';\n\n//? Calls the server's /_test/reset endpoint. Used between rate-limit test\n//? endpoints to drain the shared IP-based limiter bucket so one endpoint's\n//? N+1 assertion doesn't consume the next endpoint's window.\n//?\n//? /_test/reset is gated by `NODE_ENV in { 'development', 'test' }` on the\n//? server side AND requires `TEST_RESET_TOKEN` to be set unconditionally\n//? (no token = 403). For staging/preview deploys that enable it for CI,\n//? set `TEST_RESET_TOKEN` on the server and pass the same value here.\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\n\nexport interface ResetServerStateInput {\n baseUrl: string;\n token?: string;\n requestTimeoutMs?: number;\n}\n\nexport const resetServerState = async ({ baseUrl, token, requestTimeoutMs }: ResetServerStateInput): Promise<boolean> => {\n const url = `${baseUrl.replace(/\\/$/, '')}${getProjectConfig().http.testResetEndpoint}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (token) headers['X-Test-Reset-Token'] = token;\n\n //? Bound the call so a server that accepts the connection but never responds\n //? returns `false` (the existing failure path) instead of wedging the sweep.\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);\n const [error, response] = await tryCatch(() => fetch(url, { method: 'POST', headers, signal: controller.signal }));\n clearTimeout(timeoutHandle);\n if (error || !response) return false;\n return response.ok;\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runRateLimitCheck } from './rateLimitCheck';\nimport { resetServerState } from './resetServerState';\nimport { shouldSkip, requiresLogin, getRateLimit, calculateSummary, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunRateLimitTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n baseUrl: string;\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /**\n * Max rateLimit value to test. Endpoints with a higher limit are skipped\n * to avoid firing thousands of requests in CI.\n */\n maxRateLimitToTest?: number;\n /**\n * When true, hit `/_test/reset` before each endpoint so limiter state is\n * clean. Requires the server to allow the endpoint: `NODE_ENV` in\n * { 'development', 'test' } AND `TEST_RESET_TOKEN` set on the server with a\n * matching `resetToken` here (an unset token is 403, never open). Without\n * resetting, the shared IP bucket leaks state between endpoints.\n */\n resetBetweenEndpoints?: boolean;\n /**\n * Token forwarded as `X-Test-Reset-Token` when resetting. Only relevant in\n * staging/preview deploys that enable the endpoint behind a token.\n */\n resetToken?: string;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runRateLimitTests = async (\n input: RunRateLimitTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const maxRateLimit = input.maxRateLimitToTest ?? 50;\n //? A login-required route returns `auth.required` BEFORE the rate limiter\n //? runs, so it can only be rate-limit-tested with a session. A Cookie header\n //? means an authenticated sweep. We deliberately do NOT auto-authenticate the\n //? sweep — that would execute real mutations (delete/update) with junk input.\n //? Set TEST_AUTH_TOKEN to opt in and cover login-gated routes.\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const rateLimit = getRateLimit(input.apiMetaMap, endpoint);\n if (rateLimit === null) continue;\n\n if (!isAuthenticatedSweep && requiresLogin(input.apiMetaMap, endpoint)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'login-required route — set TEST_AUTH_TOKEN to rate-limit-test it (unauthenticated calls hit auth.required before the limiter)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? In cookie-mode the rate-limit check fires the endpoint `rateLimit + 1`\n //? times with junk input. State-changing routes (POST/PUT/DELETE) execute\n //? real mutation handlers on each hit — skip them to avoid side-effects on\n //? the test database. Their rate limits are better covered by a custom test\n //? that supplies valid input.\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in cookie-mode rate-limit sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (rateLimit > maxRateLimit) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: `rateLimit ${rateLimit} exceeds maxRateLimitToTest=${maxRateLimit}`,\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (input.resetBetweenEndpoints) {\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken });\n }\n\n const result = await runRateLimitCheck({\n endpoint,\n baseUrl: input.baseUrl,\n rateLimit,\n inputFor: input.inputFor,\n headers: input.headers,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error code the framework's CSRF middleware emits when a\n//? state-changing, cookie-authenticated request arrives without a matching\n//? CSRF token. See packages/server/src/httpRoutes/csrfMiddleware.ts.\nconst DEFAULT_EXPECTED_ERROR_CODE = 'auth.csrfMismatch';\nconst DEFAULT_EXPECTED_HTTP_STATUS = 403;\n\nexport interface CsrfEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * A valid session Cookie header (`<name>=<token>`). The probe deliberately\n * carries it so the request passes the auth guard and reaches the CSRF check.\n */\n authCookie: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n}\n\n//? CSRF-enforcement layer: for each `auth.login: true` state-changing route,\n//? send an authenticated request (valid session Cookie) WITHOUT the CSRF\n//? header. Expect the framework's CSRF middleware to reject it. This is the\n//? inverse of the custom layer's CSRF token-passing — it proves the protection\n//? actually fires, so a consumer who misconfigures CSRF doesn't get a green run.\nexport const runCsrfEnforcementCheck = async (\n input: CsrfEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n //? CSRF is only enforced on state-changing methods (POST/PUT/DELETE). A GET\n //? probe would never reach the CSRF middleware regardless of headers, so it\n //? would always appear to pass — a false green. Guard here so the check is\n //? safe to call directly (the runner already filters, but direct callers may not).\n if (endpoint.method === 'GET') {\n return {\n endpoint,\n status: 'skipped',\n reason: `CSRF enforcement does not apply to ${endpoint.method} requests`,\n durationMs: 0,\n };\n }\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const expectedErrorCode = input.expectedErrorCode ?? DEFAULT_EXPECTED_ERROR_CODE;\n const expectedHttpStatus = input.expectedHttpStatus ?? DEFAULT_EXPECTED_HTTP_STATUS;\n\n //? Valid session Cookie + Origin, but NO CSRF header — that omission is the\n //? whole point of the probe.\n const result = await sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n headers: { Cookie: input.authCookie },\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return { endpoint, status: 'fail', reason: 'fetch returned no response', durationMs: Date.now() - started };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'success',\n reason: 'state-changing endpoint accepted an authenticated request with NO CSRF header — CSRF protection is not enforced',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error' || parsed.errorCode !== expectedErrorCode) {\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint, status: 'fail', httpStatus, responseStatus, errorCode: parsed?.errorCode,\n reason: `expected CSRF rejection '${expectedErrorCode}' but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n }\n\n if (expectedHttpStatus !== false && httpStatus !== expectedHttpStatus) {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode,\n reason: `expected HTTP ${expectedHttpStatus} on CSRF rejection but got ${httpStatus}`,\n durationMs,\n };\n }\n\n return { endpoint, status: 'pass', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode, durationMs };\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runCsrfEnforcementCheck } from './csrfEnforcementCheck';\nimport { shouldSkip, requiresLogin, hasMetaEntry, calculateSummary, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunCsrfEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n baseUrl: string;\n /** Valid session Cookie header (`<name>=<token>`) the probes carry so they reach the CSRF check. */\n authCookie: string;\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms forwarded to each check. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runCsrfEnforcementTests = async (\n input: RunCsrfEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n //? Only login-required, state-changing routes are CSRF-protected. A missing\n //? meta entry means we can't know the auth requirement — skip silently here\n //? (the auth layer already surfaces the stale-map gap).\n if (!hasMetaEntry(input.apiMetaMap, endpoint)) continue;\n if (!requiresLogin(input.apiMetaMap, endpoint)) continue;\n if (!STATE_CHANGING_METHODS.has(endpoint.method)) continue;\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runCsrfEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n authCookie: input.authCookie,\n inputFor: input.inputFor,\n requestTimeoutMs: input.requestTimeoutMs,\n expectedErrorCode: input.expectedErrorCode,\n expectedHttpStatus: input.expectedHttpStatus,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Lightweight fuzz layer — no Zod yet. Sends well-known junk shapes and\n//? asserts the server doesn't 5xx or return a non-envelope body. Real\n//? schema-driven fuzzing (TS type -> random valid input) is deferred until\n//? the generator emits Zod schemas alongside the types.\nconst JUNK_PAYLOADS: readonly unknown[] = [\n null,\n [],\n [1, 2, 3],\n 'string-instead-of-object',\n 1_234_567_890,\n true,\n { nested: { deeply: { nested: { value: 'x'.repeat(10_000) } } } },\n //? `{ __proto__: ... }` serializes to `{}` via JSON.stringify (prototype keys\n //? are not own-enumerable), so it would probe nothing. Use `constructor` instead\n //? — a named own-key that some parsers misinterpret but survives JSON round-trip.\n { constructor: { polluted: true } },\n { key: null, other: undefined, third: Number.NaN },\n];\n\nexport interface FuzzCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n}\n\nexport const runFuzzCheck = async (input: FuzzCheckInput): Promise<ContractCheckResult> => {\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = `${input.baseUrl.replace(/\\/$/, '')}/${input.endpoint.fullPath}`;\n\n for (const payload of JUNK_PAYLOADS) {\n const result = await sendProbe({\n url,\n method: input.endpoint.method,\n baseUrl: input.baseUrl,\n body: payload,\n headers: input.headers,\n requestTimeoutMs,\n });\n const durationMs = Date.now() - started;\n\n if (!result) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n reason: `fuzz probe crashed (no response) with payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n //? A 5xx that STILL returns a valid `{ status:'error', errorCode }` envelope\n //? is the framework gracefully catching an error (e.g. a route designed to\n //? throw), not a crash. The fuzz layer's concern is crashes / hangs /\n //? garbage — so we only fail on a non-envelope body below (which catches raw\n //? 5xx, HTML error pages, and truncated responses). A controlled 500\n //? envelope passes.\n if (!result.parsed || (result.parsed.status !== 'success' && result.parsed.status !== 'error')) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz payload produced non-envelope response: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n if (result.parsed.status === 'error' && typeof result.parsed.errorCode !== 'string') {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz error response missing errorCode for payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n }\n\n return {\n endpoint: input.endpoint,\n status: 'pass',\n durationMs: Date.now() - started,\n };\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runFuzzCheck } from './fuzzCheck';\nimport { shouldSkip, calculateSummary, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ApiMethodMap, ContractCheckResult, RunContractSummary } from './types';\n\nexport interface RunFuzzTestsInput {\n apiMethodMap: ApiMethodMap;\n baseUrl: string;\n skip?: string[];\n headers?: Record<string, string>;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runFuzzTests = async (input: RunFuzzTestsInput): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n //? In cookie-mode (authenticated sweep) junk bodies sent to state-changing\n //? routes (POST/PUT/DELETE) bypass the body-ignore short-circuit and reach real\n //? mutation handlers — risking unintended side-effects on the test database.\n //? Skip those endpoints here; the contract layer already validates their shape\n //? with well-formed input, and the CSRF layer probes their auth guard separately.\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in cookie-mode fuzz sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runFuzzCheck({\n endpoint,\n baseUrl: input.baseUrl,\n headers: input.headers,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst isProcessRunning = (pid: unknown): boolean => {\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return error instanceof Error && 'code' in error && error.code === 'EPERM';\n }\n};\n\nexport interface ResolveTestBaseUrlOptions {\n /** Project root containing node_modules/.luckystack/dev-server.json. */\n cwd?: string;\n /** Used when no explicit override or valid live advertisement exists. */\n fallbackUrl?: string;\n}\n\n//? Shared by the framework repo and generated consumers so a dev backend that\n//? auto-increments off its configured port does not turn connection failures\n//? against the stale port into false test failures.\nexport const resolveTestBaseUrl = (\n options: ResolveTestBaseUrlOptions = {},\n): string => {\n const explicit = process.env.TEST_BASE_URL;\n if (explicit) return explicit;\n\n try {\n const raw = fs.readFileSync(\n path.join(options.cwd ?? process.cwd(), 'node_modules', '.luckystack', 'dev-server.json'),\n 'utf8',\n );\n const info = JSON.parse(raw) as { port?: unknown; pid?: unknown };\n if (\n typeof info.port === 'number'\n && Number.isInteger(info.port)\n && info.port > 0\n && info.port <= 65_535\n && isProcessRunning(info.pid)\n ) {\n return `http://localhost:${String(info.port)}`;\n }\n } catch {\n //? No live dev advertisement — use the caller's config-derived fallback.\n }\n\n return options.fallbackUrl ?? 'http://localhost:80';\n};\n","//? @adr 0032 — test-process env/secret bootstrap through lazy consumer config.\n//? Test-process bootstrap for environment-backed integrations. The live server\n//? resolves secret-manager pointers in its own process, but Layer-5 tests also\n//? use Prisma/Redis directly inside the runner process. That process therefore\n//? needs the same env prefix BEFORE custom test modules are imported.\n\nimport { loadEnvFiles, tryCatch } from '@luckystack/core';\n\ntype MaybePromise<T> = T | Promise<T>;\n\ninterface SecretManagerConfigLike {\n url: string;\n token: string | { fromFile: string };\n [key: string]: unknown;\n}\n\nexport interface ResolveTestEnvironmentInput {\n /**\n * Loads the consumer's default-exported `config.ts` object after `.env` files\n * are available. Keeping this as a callback avoids hardcoding a consumer path\n * inside the package and keeps `@luckystack/secret-manager` optional.\n */\n loadProjectConfig?: () => MaybePromise<unknown>;\n}\n\ninterface RequiredProjectConfigLoader {\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n}\n\n/** @internal Runtime guard for untyped callers of the public orchestrators. */\nexport const assertProjectConfigLoader: (\n input: unknown,\n caller: 'runAllTests' | 'runCustomTests',\n) => asserts input is RequiredProjectConfigLoader = (input, caller) => {\n if (\n typeof input !== 'object'\n || input === null\n || !('loadProjectConfig' in input)\n || typeof input.loadProjectConfig !== 'function'\n ) {\n throw new TypeError(`[test-runner] ${caller} requires a lazy loadProjectConfig callback`);\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isToken = (value: unknown): value is SecretManagerConfigLike['token'] =>\n typeof value === 'string'\n || (isRecord(value) && typeof value.fromFile === 'string');\n\nconst readSecretManagerConfig = (projectConfig: unknown): SecretManagerConfigLike | null => {\n if (!isRecord(projectConfig)) return null;\n const candidate = projectConfig.secretManager;\n if (!isRecord(candidate)) return null;\n\n const url = candidate.url;\n if (url === undefined || url === '') return null;\n if (typeof url !== 'string') {\n throw new TypeError('[test-runner] config.secretManager.url must be a string.');\n }\n if (!isToken(candidate.token)) {\n throw new TypeError(\n '[test-runner] config.secretManager.token must be a string or { fromFile: string }.',\n );\n }\n\n return { ...candidate, url, token: candidate.token };\n};\n\n/**\n * Load the project's env files and resolve optional secret-manager pointers for\n * the TEST process. Call before importing integration-test modules that touch a\n * database, Redis, or another env-backed SDK.\n */\nexport const resolveTestEnvironment = async (\n input: ResolveTestEnvironmentInput = {},\n): Promise<void> => {\n loadEnvFiles();\n if (!input.loadProjectConfig) return;\n\n const projectConfig = await input.loadProjectConfig();\n const secretManagerConfig = readSecretManagerConfig(projectConfig);\n if (!secretManagerConfig) return;\n\n const [importError, secretManager] = await tryCatch(\n () => import('@luckystack/secret-manager'),\n );\n if (importError || !secretManager) {\n throw new Error(\n '[test-runner] config.secretManager.url is set, but @luckystack/secret-manager '\n + 'could not be loaded. Install the package or remove the secret-manager config; '\n + 'tests will not continue with unresolved env pointers.',\n { cause: importError ?? undefined },\n );\n }\n\n await secretManager.initSecretManager({\n ...secretManagerConfig,\n //? Match the normal server/ORM bootstrap: configured remote resolution is\n //? fail-fast. A raw DATABASE_URL_V<n> must never leak into Prisma.\n source: 'remote',\n });\n};\n","import type { z } from 'zod';\n\n//? Options threaded through the whole walk so nested string fields get the\n//? same treatment as top-level ones.\nexport interface SampleSchemaOptions {\n /**\n * Marker prefix applied to generated UNCONSTRAINED string values so any rows\n * a sweep creates are identifiable + collision-free (finding #98). Strings\n * that carry a format (email/url/uuid/datetime/…) or a check (min/max/\n * length/regex) are left as-is, since prefixing could break the route's own\n * validation and defeat the contract layer's \"valid input\" guarantee.\n */\n stringPrefix?: string;\n}\n\n//? Walk a Zod schema and produce a minimal valid value. Used by the\n//? contract/fuzz runners so \"valid input\" tests don't require hand-written\n//? fixtures per endpoint. Handles the shapes the generator emits\n//? (primitives, literals, unions, optionals, objects, records, arrays).\n//?\n//? Not a property-based generator — returns the SAME deterministic value\n//? every call. For randomized fuzz, swap to `fast-check` in a later session.\n\n//? zod 4 renamed the internal def accessor from `._def` to `.def`; recurse\n//? through whichever a child node exposes so the walker handles both versions.\nconst innerDef = (node: { _def?: unknown; def?: unknown } | undefined): unknown =>\n node ? (node.def ?? node._def) : undefined;\n\nconst sampleForDef = (def: unknown, options: SampleSchemaOptions | undefined): unknown => {\n //? Zod stores its runtime shape on `_def.type` (v3) or `_def.typeName`.\n //? Access is untyped because `def` can be any of dozens of internal shapes.\n const d = def as {\n typeName?: string;\n type?: string;\n innerType?: { _def?: unknown; def?: unknown };\n values?: unknown[] | Record<string, unknown>;\n entries?: Record<string, unknown>;\n value?: unknown;\n options?: { _def?: unknown; def?: unknown }[];\n shape?: (() => Record<string, { _def?: unknown; def?: unknown }>) | Record<string, { _def?: unknown; def?: unknown }>;\n valueType?: { _def: unknown };\n element?: { _def: unknown };\n items?: { _def?: unknown; def?: unknown }[];\n schema?: { _def?: unknown; def?: unknown };\n in?: { _def?: unknown; def?: unknown };\n //? String-constraint markers. zod 4 sets `format` + `check: 'string_format'`\n //? for email/url/uuid/datetime/cuid/… and a `checks` array for min/max/\n //? length/regex; zod 3 puts the lot on `checks`. A bare string has none.\n format?: string;\n check?: string;\n checks?: unknown[];\n };\n\n const name = d.typeName ?? d.type;\n\n switch (name) {\n case 'ZodString':\n case 'string': {\n //? Prefix only UNCONSTRAINED strings with the caller's test-data marker\n //? (finding #98). A format (email/url/uuid/datetime/…) or any check\n //? (min/max/length/regex) means a prefixed/lengthened value could fail the\n //? route's own Zod, turning the happy-path probe into a validation-error\n //? test — so those keep the plain `'test'`.\n const isConstrained = Boolean(d.format)\n || d.check === 'string_format'\n || (Array.isArray(d.checks) && d.checks.length > 0);\n if (options?.stringPrefix && !isConstrained) return `${options.stringPrefix}test`;\n return 'test';\n }\n case 'ZodNumber':\n case 'number': {\n return 0;\n }\n case 'ZodBoolean':\n case 'boolean': {\n return false;\n }\n case 'ZodNull':\n case 'null': {\n return null;\n }\n case 'ZodUndefined':\n case 'undefined': {\n return undefined;\n }\n case 'ZodAny':\n case 'any':\n case 'ZodUnknown':\n case 'unknown': {\n return null;\n }\n case 'ZodLiteral':\n case 'literal': {\n //? zod 3 stores the literal on `_def.value`; zod 4 stores `_def.values` (array).\n return d.value ?? (Array.isArray(d.values) ? d.values[0] : undefined);\n }\n case 'ZodOptional':\n case 'optional':\n case 'ZodNullable':\n case 'nullable': {\n //? Omitting optional fields is the most conservative valid input.\n return undefined;\n }\n case 'ZodEnum':\n case 'enum':\n case 'ZodNativeEnum':\n case 'nativeEnum': {\n //? Return the first declared value so an enum-typed required field gets a\n //? VALID member rather than `null` (which the route's own Zod rejects,\n //? silently turning the contract probe into a validation-error test).\n //? zod 3 stores members on `_def.values` (array); zod 4 on `_def.entries`\n //? (object) or `_def.values` (Set/array).\n if (Array.isArray(d.values)) return d.values[0];\n const entries = d.entries ?? (d.values && !Array.isArray(d.values) ? d.values : undefined);\n if (entries) {\n const first = Object.values(entries)[0];\n if (first !== undefined) return first;\n }\n return undefined;\n }\n case 'ZodDefault':\n case 'default':\n case 'ZodCatch':\n case 'catch': {\n //? Unwrap to the inner schema's sample (the field is still required at the\n //? type level; the default only applies when omitted).\n return sampleForDef(innerDef(d.innerType), options);\n }\n case 'ZodEffects':\n case 'effects':\n case 'ZodPipeline':\n case 'pipe':\n case 'ZodBranded':\n case 'branded':\n case 'ZodReadonly':\n case 'readonly': {\n //? `.refine`/`.transform`/`.pipe`/`.brand`/`.readonly` wrap an inner\n //? schema — recurse into it. A `.refine` may still reject the sample, but\n //? recursing yields a structurally-valid base far more often than `null`.\n return sampleForDef(innerDef(d.schema ?? d.in ?? d.innerType), options);\n }\n case 'ZodTuple':\n case 'tuple': {\n const items = d.items ?? [];\n return items.map(item => sampleForDef(innerDef(item), options));\n }\n case 'ZodUnion':\n case 'union':\n case 'ZodDiscriminatedUnion':\n case 'discriminatedUnion': {\n return d.options && d.options.length > 0 && d.options[0] ? sampleForDef(innerDef(d.options[0]), options) : undefined;\n }\n case 'ZodArray':\n case 'array': {\n return [];\n }\n case 'ZodObject':\n case 'object': {\n const out: Record<string, unknown> = {};\n //? zod 3 exposes `_def.shape` as a function; zod 4 as a plain object.\n const shape = typeof d.shape === 'function' ? d.shape() : (d.shape ?? {});\n for (const [key, valueSchema] of Object.entries(shape)) {\n const sample = sampleForDef(innerDef(valueSchema), options);\n if (sample !== undefined) out[key] = sample;\n }\n return out;\n }\n case 'ZodRecord':\n case 'record': {\n return {};\n }\n case 'ZodDate':\n case 'date': {\n return new Date().toISOString();\n }\n default: {\n //? Unknown node: OMIT (undefined) rather than emit `null`. A `null` for a\n //? required field of an unhandled type makes the route's own Zod reject\n //? the body, silently turning the \"happy path\" contract probe into a\n //? validation-error test. Omitting lets the object walker drop the key so\n //? the probe only fails if the field was genuinely required (a real,\n //? visible signal) instead of always.\n return undefined;\n }\n }\n};\n\nexport const sampleSchemaInput = (schema: z.ZodType, options?: SampleSchemaOptions): unknown => {\n //? zod 4 renamed the public def accessor from `._def` to `.def`; fall back to\n //? `._def` (zod 3) so the top-level entry handles both, matching the\n //? recursive `innerDef` helper.\n const node = schema as { def?: unknown; _def?: unknown };\n return sampleForDef(node.def ?? node._def, options);\n};\n","import { randomUUID } from 'node:crypto';\n\n//? Contract-test mutation safety (finding #98). The contract sweep posts\n//? generated sample payloads at every endpoint, which can create real rows.\n//? Tagging generated string values with a recognizable, run-unique marker\n//? makes any such row identifiable (so the reset bookends — and a human\n//? scanning the DB — can tell test data from real data) and collision-free\n//? (no chance a generated value matches an existing real record's field).\n\n//? Fixed leading token every generated test string starts with. Stable across\n//? runs so consumers / DB scans can match `lstest_*` to spot stray test data.\nexport const TEST_DATA_PREFIX = 'lstest_';\n\n//? Build a run-unique marker like `lstest_a1b2c3d4_`. The short uuid segment\n//? keeps two concurrent runs from generating colliding values while staying\n//? short enough not to blow past typical string length limits.\nexport const createTestDataMarker = (): string => `${TEST_DATA_PREFIX}${randomUUID().slice(0, 8)}_`;\n","import type { ContractCheckResult, RunContractSummary } from './types';\n\nconst statusIcon = (status: ContractCheckResult['status']): string => {\n switch (status) {\n case 'pass': { return '[PASS]';\n }\n case 'fail': { return '[FAIL]';\n }\n case 'skipped': { return '[SKIP]';\n }\n }\n};\n\nexport const logContractResult = (result: ContractCheckResult): void => {\n const { endpoint, durationMs, httpStatus, responseStatus, errorCode, reason } = result;\n const icon = statusIcon(result.status);\n const core = `${icon} ${endpoint.method} /${endpoint.fullPath} ${durationMs}ms`;\n\n if (result.status === 'pass') {\n const tail = responseStatus === 'error' ? ` (error: ${errorCode ?? 'unknown'})` : '';\n console.log(`${core} http=${httpStatus ?? '-'}${tail}`);\n return;\n }\n\n if (result.status === 'skipped') {\n console.log(`${core} ${reason ?? ''}`);\n return;\n }\n\n console.log(`${core} http=${httpStatus ?? '-'} reason=${reason ?? '(none)'}`);\n};\n\nexport const logContractSummary = (summary: RunContractSummary): void => {\n const { total, passed, failed, skipped } = summary;\n console.log('');\n console.log(`Contract tests: ${passed}/${total} passed, ${failed} failed, ${skipped} skipped`);\n\n if (failed > 0) {\n console.log('');\n console.log('Failures:');\n for (const result of summary.results) {\n if (result.status !== 'fail') continue;\n console.log(` - /${result.endpoint.fullPath}: ${result.reason ?? '(no reason)'}`);\n }\n }\n};\n","//? Per-route business-logic test layer. Discovers `*.tests.ts` files\n//? alongside `_api/` and `_sync/` route source, dynamic-imports each,\n//? builds a route-bound `TestContext`, runs the exported `customTests`\n//? cases. Spec: docs/ARCHITECTURE_TESTING.md.\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { getCsrfConfig, getPrismaClient, getProjectConfig, getSrcDir, tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport {\n openStreamWatcher,\n type StreamChunkFrame,\n type StreamWatcher,\n} from './streamWatcher';\n//? `ApiMethodMap` mirrors the generated `apiMethodMap` shape (page → name →\n//? version → method) and is a type-only import, so there's still no import-time\n//? coupling to the consumer's generated artifact.\nimport type { ApiMethodMap } from './types';\n\nconst API_TEST_FILE_PATTERN = /_v(\\d+)\\.tests\\.ts$/;\nconst SYNC_SERVER_TEST_FILE_PATTERN = /_server_v(\\d+)\\.tests\\.ts$/;\n\n//? Default per-request timeout for the Layer-5 call helpers. Mirrors the sweep\n//? layers so a hung (non-erroring) server can't wedge the whole custom-test run\n//? indefinitely — without it the case's own tryCatch never fires.\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000;\n\nexport interface CustomTestCase {\n name: string;\n run: (ctx: TestContext) => Promise<void>;\n /**\n * Mark this case as a KNOWN, accepted failure (e.g. a documented bug not\n * yet fixed, or a scenario the current implementation cannot satisfy). The\n * string is the reason shown in the report. A case marked `expectedToFail`\n * that throws is reported as `xfail` — counted separately, NOT a red\n * failure. If it unexpectedly PASSES it is reported as `xpass` so the stale\n * marker can be removed. Leave undefined for normal cases.\n */\n expectedToFail?: string;\n}\n\nexport interface CustomTestResult {\n routePath: string;\n caseName: string;\n //? `xfail` = marked expectedToFail and failed as expected (known issue).\n //? `xpass` = marked expectedToFail but passed (remove the marker).\n status: 'pass' | 'fail' | 'xfail' | 'xpass';\n durationMs: number;\n reason?: string;\n /** Documented reason when the case carried an `expectedToFail` marker. */\n expectedToFail?: string;\n}\n\nexport interface RunCustomTestsSummary {\n total: number;\n passed: number;\n failed: number;\n /** Cases marked `expectedToFail` that failed as expected (known issues). */\n xfailed: number;\n /** Cases marked `expectedToFail` that unexpectedly passed (remove the marker). */\n xpassed: number;\n results: CustomTestResult[];\n}\n\nexport interface RunCustomTestsInput {\n baseUrl: string;\n /** Optional cookie name for the session token. Defaults to the project's configured cookie. */\n sessionCookieName?: string;\n /** Filter to a subset of routes — substring match against `<page>/<name>/<version>`. */\n filter?: string;\n /**\n * Generated `apiMethodMap` so the harness sends each API route its declared\n * HTTP method (e.g. logout is DELETE). Falls back to POST when absent.\n */\n apiMethodMap?: ApiMethodMap;\n /**\n * Lazy consumer config for direct Layer-5 runs. Required so the public path\n * cannot silently skip a configured secret manager; `runAllTests` prepares\n * the environment separately before using the internal Layer-5 entrypoint.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n onResult?: (result: CustomTestResult) => void;\n}\n\ntype PreparedRunCustomTestsInput = Omit<RunCustomTestsInput, 'loadProjectConfig'>;\n\ninterface DiscoveredTestFile {\n /** Absolute path to the test file. */\n filePath: string;\n /** `api` or `sync` — which transport this route uses. */\n kind: 'api' | 'sync';\n /** Route descriptor parsed from the filename + directory location. */\n route: { page: string; name: string; version: string };\n}\n\n//? Walk `src/` recursively. A file matches when its directory contains an\n//? `_api/` or `_sync/` segment AND the filename matches the version suffix\n//? pattern. The route's `page` is everything between `src/` and the marker\n//? segment, joined with `/`. Nested page folders are supported (e.g.\n//? `src/admin/users/_api/getUser_v1.tests.ts` → page `admin/users`).\nexport const discoverCustomTestFiles = (srcDir: string = getSrcDir()): DiscoveredTestFile[] => {\n if (!fs.existsSync(srcDir)) return [];\n const found: DiscoveredTestFile[] = [];\n const stack: string[] = [srcDir];\n //? Track visited real-paths so a cyclic symlink under `src/` can't drive an\n //? infinite walk / unbounded stack growth.\n const visited = new Set<string>();\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined) break;\n //? Resolve to the canonical path and skip if already seen (symlink cycle).\n const [realError, realPath] = tryCatchSync(() => fs.realpathSync(current));\n const canonical = realError || realPath === null ? current : realPath;\n if (visited.has(canonical)) continue;\n visited.add(canonical);\n //? Guard the readdir: a permission error or a directory deleted mid-walk\n //? throws synchronously and would otherwise abort the whole custom layer.\n //? Skip the unreadable directory instead.\n const [readError, entries] = tryCatchSync(() => fs.readdirSync(current, { withFileTypes: true }));\n if (readError || entries === null) continue;\n for (const entry of entries) {\n const fullPath = path.join(current, entry.name);\n //? Skip symlinked entries entirely — following them risks a cycle and\n //? a discovered test file should live in the real source tree.\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n stack.push(fullPath);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.tests.ts')) continue;\n const parentDir = path.basename(current);\n if (parentDir !== '_api' && parentDir !== '_sync') continue;\n const pattern = parentDir === '_api' ? API_TEST_FILE_PATTERN : SYNC_SERVER_TEST_FILE_PATTERN;\n const versionMatch = pattern.exec(entry.name);\n if (!versionMatch) continue;\n const version = `v${versionMatch[1]}`;\n const namePart = entry.name.replace(parentDir === '_api' ? /_v\\d+\\.tests\\.ts$/ : /_server_v\\d+\\.tests\\.ts$/, '');\n const pageDir = path.dirname(current);\n //? Top-level `src/_api/` (no page folder) routes under the framework's\n //? `system` page — mirror that, otherwise an empty page produces the\n //? malformed URL `/api//logout/v1` and the call 404s.\n const relativePage = path.relative(srcDir, pageDir).replaceAll('\\\\', '/') || 'system';\n found.push({\n filePath: fullPath,\n kind: parentDir === '_api' ? 'api' : 'sync',\n route: { page: relativePage, name: namePart, version },\n });\n }\n }\n return found.toSorted((a, b) => a.filePath.localeCompare(b.filePath));\n};\n\n//? Test fixture shapes. The TestContext is built per route — each per-route\n//? test file's `customTests` cases receive a context already bound to the\n//? route the file lives next to, so authors don't repeat `page/name/version`.\nexport interface TestContext {\n /** Invoke the route under test. Page / name / version are baked in. */\n //? Two type parameters by design — `TInput` lets callers document the\n //? typed shape they're sending even though we don't validate against it\n //? here (the route's own runtime validation does). The ESLint rule\n //? otherwise flags it as \"used once\"; the documentation value is real.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callApi: <TInput = unknown, TOutput = unknown>(input: TInput) => Promise<TOutput>;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: { receiver?: string }) => Promise<TOutput>;\n /**\n * Open a second socket connection that joins `roomCode` as a pure\n * subscriber and exposes the incoming chunk stream for assertions.\n *\n * The route is auto-derived from the test file location, so a watcher\n * only surfaces chunks for the route this test belongs to. Multiple\n * watchers per test are allowed; all are auto-closed when the test\n * case finishes (whether it passes or throws).\n */\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n watchStream: <TChunk extends StreamChunkFrame = StreamChunkFrame>(roomCode: string) => Promise<StreamWatcher<TChunk>>;\n session: {\n login: (user?: { email?: string; id?: string; name?: string }) => Promise<{ token: string; userId: string }>;\n logout: () => Promise<void>;\n current: () => { token: string | null; userId: string | null };\n };\n prisma: ReturnType<typeof getPrismaClient>;\n expect: TestExpect;\n}\n\nexport interface TestExpect {\n eq: <T>(actual: T, expected: T, message?: string) => void;\n ok: (value: unknown, message?: string) => void;\n throws: (fn: () => Promise<unknown>, message?: string) => Promise<Error>;\n matches: (value: string, pattern: RegExp, message?: string) => void;\n}\n\nclass AssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AssertionError';\n }\n}\n\nconst buildExpect = (): TestExpect => ({\n eq: (actual, expected, message) => {\n const ok = Object.is(actual, expected)\n || (typeof actual === 'object' && typeof expected === 'object' && JSON.stringify(actual) === JSON.stringify(expected));\n if (!ok) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);\n }\n },\n ok: (value, message) => {\n if (!value) throw new AssertionError(message ?? `expected truthy value, got ${JSON.stringify(value)}`);\n },\n throws: async (fn, message) => {\n const [err] = await tryCatch(fn);\n if (!err) throw new AssertionError(message ?? 'expected fn to throw, but it did not');\n return err;\n },\n matches: (value, pattern, message) => {\n if (!pattern.test(value)) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}\"${value}\" does not match ${pattern}`);\n }\n },\n});\n\ninterface TestSessionState {\n token: string | null;\n userId: string | null;\n //? CSRF token minted by saveSession on login. Sent on authenticated\n //? state-changing requests so the server's CSRF middleware doesn't 403.\n csrfToken: string | null;\n //? Raw JSON body of the most recent callApi / callSync response. Surfaced in\n //? a failed case's reason so the report shows the server's actual errorCode\n //? (e.g. `api.rateLimitExceeded`) instead of only the assertion text.\n lastResponse: unknown;\n}\n\n//? Pull the server's errorCode (or non-success status) out of the last HTTP\n//? response so a failure's reason shows WHY the server rejected the call, not\n//? just the assertion text. Returns undefined for success / non-envelope bodies.\nconst extractErrorCode = (response: unknown): string | undefined => {\n if (!response || typeof response !== 'object') return undefined;\n const r = response as { errorCode?: unknown; status?: unknown };\n if (typeof r.errorCode === 'string' && r.errorCode.length > 0) return r.errorCode;\n if (typeof r.status === 'string' && r.status !== 'success') return r.status;\n return undefined;\n};\n\n//? Minimal session login: mints a session in Redis via the consumer's\n//? registered SessionAdapter (from `@luckystack/login`). Deliberately does\n//? NOT create the Prisma user record — User-table shape varies per\n//? project. If your test needs a real user row, create it via\n//? `ctx.prisma.user.create(...)` first, then pass `{ id, email }` to\n//? `session.login()`.\nconst buildSessionHelpers = (state: TestSessionState): TestContext['session'] => {\n return {\n login: async (user) => {\n const { saveSession, getSession } = await import('@luckystack/login');\n const userId = user?.id ?? `test-user-${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;\n const email = user?.email ?? `test-${Date.now()}-${crypto.randomBytes(8).toString('hex')}@example.com`;\n const name = user?.name ?? 'Test User';\n const token = `test-${Date.now()}-${crypto.randomBytes(16).toString('hex')}`;\n const sessionData = {\n id: userId,\n email,\n name,\n token,\n provider: 'credentials' as const,\n };\n await saveSession(token, sessionData, true);\n state.token = token;\n state.userId = userId;\n //? Read back the CSRF token saveSession minted so authenticated\n //? state-changing requests can pass the server's CSRF middleware.\n const persisted = await getSession(token);\n state.csrfToken = persisted?.csrfToken ?? null;\n return { token, userId };\n },\n logout: async () => {\n if (!state.token) return;\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(state.token);\n state.token = null;\n state.userId = null;\n state.csrfToken = null;\n },\n current: () => ({ token: state.token, userId: state.userId }),\n };\n};\n\n//? Parse a fetch Response as JSON behind tryCatch and store the raw body on the\n//? session state. A non-JSON body (HTML 500, empty 204, truncated) would\n//? otherwise throw a raw SyntaxError with `state.lastResponse` null, hiding the\n//? server's actual output. On parse failure we keep a snippet of the text body\n//? so a failed case's reason can show what the server actually returned.\nconst parseResponse = async (response: Response, state: TestSessionState): Promise<unknown> => {\n const [textError, text] = await tryCatch(() => response.text());\n if (textError || text === null) {\n state.lastResponse = null;\n throw new Error(`failed to read response body (HTTP ${response.status}): ${textError?.message ?? 'unknown'}`);\n }\n const [parseError, parsed] = tryCatchSync(() => JSON.parse(text) as unknown);\n if (parseError) {\n const snippet = text.length > 500 ? `${text.slice(0, 500)}…` : text;\n state.lastResponse = snippet;\n throw new Error(`response was not valid JSON (HTTP ${response.status}): ${snippet}`);\n }\n state.lastResponse = parsed;\n return parsed;\n};\n\nconst buildCallApi = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n //? The route's declared HTTP method (from the generated apiMethodMap). The\n //? server validates the method, so DELETE/PUT routes (e.g. logout) 405 if we\n //? always sent POST.\n method: string,\n): TestContext['callApi'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callApi'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput): Promise<TOutput> => {\n const url = `${baseUrl.replace(/\\/$/, '')}/api/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const init: RequestInit = { method, headers, signal: controller.signal };\n //? GET / HEAD requests cannot carry a body (fetch throws). Every other\n //? method sends the JSON payload.\n if (method !== 'GET' && method !== 'HEAD') init.body = JSON.stringify(input ?? {});\n const [fetchError, response] = await tryCatch(() => fetch(url, init));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callApi ${method} ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? `parseResponse` returns `unknown`; TOutput is the caller-supplied expectation.\n //? The runtime shape is unverifiable here — the route's own validator owns that\n //? contract. The cast is required because parseResponse must return `unknown`.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, no-restricted-syntax -- parseResponse returns unknown; TOutput is the caller's type contract, verified at the call site\n return await parseResponse(response, state) as TOutput;\n };\n};\n\nconst buildCallSync = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n): TestContext['callSync'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callSync'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput, opts?: { receiver?: string }): Promise<TOutput> => {\n const url = `${baseUrl.replace(/\\/$/, '')}/sync/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const body = { data: input ?? {}, receiver: opts?.receiver };\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const [fetchError, response] = await tryCatch(() => fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callSync ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? parseResponse returns unknown; TOutput is the caller's type contract, verified at the call site\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, no-restricted-syntax -- parseResponse returns unknown; cast to TOutput is the boundary\n return await parseResponse(response, state) as TOutput;\n };\n};\n\ninterface BuiltContext {\n ctx: TestContext;\n /** Close every watcher opened during the case. Called by `runCustomTests` after each case. */\n closeAllWatchers: () => Promise<void>;\n /** Per-case session state — read after the run to surface the last response's errorCode. */\n state: TestSessionState;\n}\n\nconst buildContext = (\n discovery: DiscoveredTestFile,\n input: PreparedRunCustomTestsInput,\n): BuiltContext => {\n //? Default to the PROJECT's configured session cookie name (the server reads\n //? the token from this cookie). Hardcoding a name silently breaks every\n //? authenticated custom test when the project uses a different name.\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n const routeFullName = `sync/${routePath}`;\n //? API routes are invoked with their declared HTTP method; sync routes always\n //? POST. Default to POST when the map lacks the route (e.g. sync, or a map\n //? wasn't threaded through).\n const apiMethod = input.apiMethodMap?.[discovery.route.page]?.[discovery.route.name]?.[discovery.route.version] ?? 'POST';\n const state: TestSessionState = { token: null, userId: null, csrfToken: null, lastResponse: null };\n\n //? Watchers are tracked per case so a missing explicit `close()` can't\n //? leak socket B between tests.\n const watchers: StreamWatcher[] = [];\n const watchStream = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n roomCode: string,\n ): Promise<StreamWatcher<TChunk>> => {\n const watcher = await openStreamWatcher<TChunk>({\n baseUrl: input.baseUrl,\n roomCode,\n token: state.token,\n routeFullName,\n });\n //? Track watchers under the base `StreamChunkFrame` shape; the\n //? returned reference keeps the caller-supplied generic.\n watchers.push(watcher);\n return watcher;\n };\n\n const closeAllWatchers = async (): Promise<void> => {\n while (watchers.length > 0) {\n const watcher = watchers.pop();\n if (!watcher) continue;\n const [closeError] = await tryCatch(() => watcher.close());\n if (closeError) {\n //? Swallow — closing in cleanup must never mask the original test\n //? failure. Surface via logger if a project wires one up.\n //? (Intentionally not using getLogger here to avoid an extra import.)\n }\n }\n };\n\n const ctx: TestContext = {\n callApi: buildCallApi(input.baseUrl, routePath, state, cookieName, apiMethod),\n callSync: buildCallSync(input.baseUrl, routePath, state, cookieName),\n watchStream,\n session: buildSessionHelpers(state),\n //? Lazy: only resolves when a case actually touches `ctx.prisma`, so an\n //? orm:'none' project (no registered client) can still run DB-free custom\n //? tests — the eager resolve used to abort the whole custom-test phase.\n get prisma() {\n return getPrismaClient();\n },\n expect: buildExpect(),\n };\n\n return { ctx, closeAllWatchers, state };\n};\n\n/** @internal Environment is already prepared by `runAllTests`. */\nexport const runCustomTestsAfterEnvironmentPrepared = async (\n input: PreparedRunCustomTestsInput,\n): Promise<RunCustomTestsSummary> => {\n const srcDir = getSrcDir();\n const files = discoverCustomTestFiles(srcDir);\n const results: CustomTestResult[] = [];\n\n for (const discovery of files) {\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n if (input.filter && !routePath.includes(input.filter)) continue;\n\n //? Canonical-path containment guard: resolve to the real path and verify\n //? it is still inside srcDir. Protects against symlink-escaped paths or\n //? an adversarially crafted filePath that slipped through the walker.\n const [realPathErr, realFilePath] = tryCatchSync(() => fs.realpathSync(discovery.filePath));\n const resolvedPath = realPathErr ? discovery.filePath : (realFilePath ?? discovery.filePath);\n const relToSrc = path.relative(srcDir, resolvedPath);\n if (relToSrc.startsWith('..') || path.isAbsolute(relToSrc)) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `test file path escapes srcDir and was rejected: ${discovery.filePath}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const [importError, mod] = await tryCatch(\n () => import(pathToFileURL(resolvedPath).href) as Promise<Record<string, unknown>>,\n );\n if (importError || !mod) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `failed to import ${discovery.filePath}: ${importError?.message ?? 'unknown'}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const exported = (mod as { customTests?: unknown }).customTests;\n //? DD-TR-various — empty-stub contract (documented decision):\n //? A test file that exports an empty array (or no `customTests` export) is\n //? silently skipped, NOT emitted as a `skipped` result. Rationale: AI-generated\n //? stub files start life empty; surfacing them as \"skipped\" every run adds noise\n //? before the author fills them in. The `CustomTestResult` status union therefore\n //? stays `'pass' | 'fail' | 'xfail' | 'xpass'` — no `'skipped'` variant.\n //? If you need to track unfilled stubs, scan `discoverCustomTestFiles()` and\n //? compare against the results array.\n if (!Array.isArray(exported) || exported.length === 0) {\n continue;\n }\n\n for (const rawCase of exported) {\n const c = rawCase as CustomTestCase;\n if (typeof c.name !== 'string' || typeof c.run !== 'function') continue;\n const expectedToFail = typeof c.expectedToFail === 'string' ? c.expectedToFail : undefined;\n const built = buildContext(discovery, input);\n const started = Date.now();\n const [runError] = await tryCatch(async () => c.run(built.ctx));\n //? Always close watchers regardless of pass/fail so the next case\n //? starts clean. tryCatch on the cleanup itself prevents a teardown\n //? throw from masking the original failure.\n await tryCatch(() => built.closeAllWatchers());\n //? Best-effort session cleanup: if `session.login()` minted a Redis\n //? session but the case didn't call `session.logout()`, the token\n //? accumulates until its TTL expires. Delete it now to avoid a slow\n //? session-key leak across a full sweep run.\n const residualToken = built.state.token;\n if (residualToken) {\n await tryCatch(async () => {\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(residualToken);\n });\n }\n const durationMs = Date.now() - started;\n\n let r: CustomTestResult;\n if (runError) {\n //? Augment the assertion message with the server's actual errorCode so\n //? the report explains the rejection, not just the failed expectation.\n const errorCode = extractErrorCode(built.state.lastResponse);\n const reason = errorCode ? `${runError.message} (server: ${errorCode})` : runError.message;\n r = expectedToFail\n ? { routePath, caseName: c.name, status: 'xfail', durationMs, reason, expectedToFail }\n : { routePath, caseName: c.name, status: 'fail', durationMs, reason };\n } else {\n r = expectedToFail\n ? {\n routePath, caseName: c.name, status: 'xpass', durationMs,\n reason: `marked expectedToFail (\"${expectedToFail}\") but passed — remove the marker`,\n expectedToFail,\n }\n : { routePath, caseName: c.name, status: 'pass', durationMs };\n }\n results.push(r);\n input.onResult?.(r);\n }\n }\n\n return {\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n xfailed: results.filter(r => r.status === 'xfail').length,\n xpassed: results.filter(r => r.status === 'xpass').length,\n results,\n };\n};\n\n//? Public direct Layer-5 path: custom modules are imported only after env files\n//? and optional secret pointers resolve. The process-wide orchestrator calls the\n//? internal prepared function above, so it neither reloads pointers nor exposes\n//? a public `skipBootstrap` flag that callers could misuse accidentally.\nexport const runCustomTests = async (input: RunCustomTestsInput): Promise<RunCustomTestsSummary> => {\n assertProjectConfigLoader(input, 'runCustomTests');\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n return runCustomTestsAfterEnvironmentPrepared(input);\n};\n","//? Second-socket harness for Layer 5 custom tests. Opens a separate\n//? socket.io-client connection (socket B), joins the receiver room as a\n//? pure subscriber, and exposes the broadcast/streamTo chunk stream for\n//? assertions. Without this, `ctx.callSync` only surfaces the final\n//? envelope — chunks (LLM tokens, progress ticks) emitted via\n//? `broadcastStream` / `streamTo` flow on the `sync` event channel to\n//? room members other than the request originator's socket.\n//?\n//? Spec: see `docs/contract-tests.md` §watchStream and CLAUDE Rule 21\n//? (no `as unknown as X` / `as any` — typed generics throughout).\n//?\n//? Wire shape, sourced from `@luckystack/sync` `_shared/streamEmitters.ts`:\n//? io.to(receiver).emit('sync', { ...payload, cb, fullName, status: 'stream' })\n//? where `fullName` = `sync/<page>/<name>/<version>`. We filter incoming\n//? frames by that `fullName` so a watcher only sees chunks for the route\n//? its test file lives next to.\n\nimport { io as createSocketIoClient, Socket } from 'socket.io-client';\nimport {\n buildJoinRoomResponseEventName,\n socketEventNames,\n tryCatch,\n getProjectConfig,\n} from '@luckystack/core';\n\nexport interface StreamChunkFrame {\n /** Per-request correlation id stamped by the server. */\n cb?: string;\n /** Full route name (`sync/<page>/<name>/<version>`). */\n fullName?: string;\n /** Always `'stream'` for chunk frames. */\n status?: 'stream';\n /** Payload fields spread on top of the envelope. */\n [key: string]: unknown;\n}\n\nexport interface StreamWatcher<TChunk extends StreamChunkFrame = StreamChunkFrame> {\n /** Chunks received so far. Array reference is stable; entries are pushed in arrival order. */\n readonly chunks: TChunk[];\n /** Wait until `predicate(chunks)` returns true, or until timeout (default 5000ms). */\n stopAt(predicate: (chunks: TChunk[]) => boolean, timeoutMs?: number): Promise<void>;\n /** Wait until N total chunks received, or until timeout. */\n waitForCount(n: number, timeoutMs?: number): Promise<void>;\n /** Close socket B. Tests SHOULD call this in cleanup; ctx auto-closes all watchers when the test ends. */\n close(): Promise<void>;\n}\n\nexport interface OpenStreamWatcherInput {\n /** Server URL (same as `ctx`'s `baseUrl`). */\n baseUrl: string;\n /** Room to join as a pure subscriber. */\n roomCode: string;\n /** Active session token (used for `joinRoom` auth). May be null for routes that don't require auth. */\n token: string | null;\n /**\n * Resolved route name `sync/<page>/<name>/<version>` — chunk frames are\n * filtered by their `fullName` property so a watcher only surfaces chunks\n * for the route its test is bound to.\n */\n routeFullName: string;\n /** Default 5000ms — overridable per `stopAt` / `waitForCount` call. */\n defaultTimeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst JOIN_RESPONSE_TIMEOUT_MS = 3000;\nconst CONNECT_TIMEOUT_MS = 3000;\n\ninterface JoinRoomResponse {\n status?: string;\n errorCode?: string;\n}\n\nconst isJoinRoomResponse = (value: unknown): value is JoinRoomResponse =>\n typeof value === 'object' && value !== null;\n\nconst isStreamChunkFrame = (value: unknown): value is StreamChunkFrame =>\n typeof value === 'object' && value !== null;\n\n//? Monotonic counter — each watcher uses a fresh responseIndex so the\n//? `buildJoinRoomResponseEventName(...)` listener doesn't collide with a\n//? sibling watcher on the same process.\nlet responseIndexCounter = 0;\nconst nextResponseIndex = (): number => {\n responseIndexCounter += 1;\n return responseIndexCounter;\n};\n\nconst waitForConnect = (socket: Socket): Promise<void> => {\n return new Promise<void>((resolve, reject) => {\n if (socket.connected) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`watchStream: socket failed to connect within ${CONNECT_TIMEOUT_MS}ms`));\n }, CONNECT_TIMEOUT_MS);\n const onConnect = (): void => {\n cleanup();\n resolve();\n };\n const onConnectError = (err: Error): void => {\n cleanup();\n reject(new Error(`watchStream: connect_error — ${err.message}`));\n };\n const cleanup = (): void => {\n clearTimeout(timer);\n socket.off('connect', onConnect);\n socket.off('connect_error', onConnectError);\n };\n socket.on('connect', onConnect);\n socket.on('connect_error', onConnectError);\n });\n};\n\nconst waitForJoinAck = (socket: Socket, responseIndex: number): Promise<void> => {\n const eventName = buildJoinRoomResponseEventName(responseIndex);\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n socket.off(eventName, onAck);\n reject(new Error(`watchStream: joinRoom ack timeout after ${JOIN_RESPONSE_TIMEOUT_MS}ms`));\n }, JOIN_RESPONSE_TIMEOUT_MS);\n const onAck = (payload: unknown): void => {\n clearTimeout(timer);\n socket.off(eventName, onAck);\n if (isJoinRoomResponse(payload) && payload.status === 'error') {\n reject(new Error(`watchStream: joinRoom rejected — ${payload.errorCode ?? 'unknown'}`));\n return;\n }\n resolve();\n };\n socket.on(eventName, onAck);\n });\n};\n\nexport const openStreamWatcher = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n input: OpenStreamWatcherInput,\n): Promise<StreamWatcher<TChunk>> => {\n const defaultTimeoutMs = input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;\n const chunks: TChunk[] = [];\n const waiters: { predicate: (chunks: TChunk[]) => boolean; resolve: () => void }[] = [];\n\n //? Authenticate the observer socket in BOTH token modes. `auth.token` is the\n //? sessionBasedToken (sessionStorage) path; in the DEFAULT cookie mode the\n //? server reads the token ONLY from the session cookie (extractTokenFromSocket\n //? ignores handshake.auth.token unless `acceptBearerInCookieMode`), so without\n //? the Cookie header the observer socket connects anonymously and every\n //? `joinRoom` is rejected with `auth.required`. In Node, socket.io-client honours\n //? `extraHeaders` on the websocket handshake, so we set the cookie there.\n const sessionCookieName = getProjectConfig().http.sessionCookieName;\n const socket: Socket = createSocketIoClient(input.baseUrl, {\n transports: ['websocket'],\n forceNew: true,\n reconnection: false,\n auth: input.token ? { token: input.token } : undefined,\n extraHeaders: input.token ? { Cookie: `${sessionCookieName}=${input.token}` } : undefined,\n });\n\n const handleChunk = (raw: unknown): void => {\n if (!isStreamChunkFrame(raw)) return;\n if (raw.status !== 'stream') return;\n if (raw.fullName !== input.routeFullName) return;\n //? Server emits chunk frames typed as `StreamChunkFrame` — caller's\n //? `TChunk` narrows the payload shape (e.g. `{ chunk: string }` for\n //? `streamBroadcast`). The runtime guards above confirm the wire shape;\n //? narrowing the static type is the test author's responsibility.\n const chunk = raw as TChunk;\n chunks.push(chunk);\n //? Drain waiters whose predicates are now satisfied. Iterate in reverse\n //? so we can splice without breaking indices.\n for (let i = waiters.length - 1; i >= 0; i--) {\n const waiter = waiters[i];\n if (!waiter) continue;\n if (waiter.predicate(chunks)) {\n waiters.splice(i, 1);\n waiter.resolve();\n }\n }\n };\n\n socket.on(socketEventNames.sync, handleChunk);\n\n //? On a connect timeout / connect_error the socket (and its `sync` listener)\n //? must be torn down — otherwise every failed attempt leaks one socket that\n //? is never auto-closed (the caller only tracks the watcher after this\n //? resolves). Mirror the join-ack cleanup path below.\n const [connectError] = await tryCatch(() => waitForConnect(socket));\n if (connectError) {\n socket.off(socketEventNames.sync, handleChunk);\n socket.disconnect();\n throw connectError;\n }\n\n const responseIndex = nextResponseIndex();\n socket.emit(socketEventNames.joinRoom, {\n group: input.roomCode,\n responseIndex,\n });\n const [joinError] = await tryCatch(() => waitForJoinAck(socket, responseIndex));\n if (joinError) {\n socket.off(socketEventNames.sync, handleChunk);\n socket.disconnect();\n throw joinError;\n }\n\n let closed = false;\n const close = (): Promise<void> => {\n if (closed) return Promise.resolve();\n closed = true;\n socket.off(socketEventNames.sync, handleChunk);\n //? Drain any outstanding waiters so callers don't hang past test end.\n for (const waiter of waiters) waiter.resolve();\n waiters.length = 0;\n socket.disconnect();\n return Promise.resolve();\n };\n\n const stopAt = (\n predicate: (chunks: TChunk[]) => boolean,\n timeoutMs: number = defaultTimeoutMs,\n ): Promise<void> => {\n if (predicate(chunks)) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const waiter = {\n predicate,\n resolve: () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n },\n };\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n const idx = waiters.indexOf(waiter);\n if (idx !== -1) waiters.splice(idx, 1);\n reject(new Error(\n `watchStream.stopAt: predicate not satisfied within ${timeoutMs}ms (received ${chunks.length} chunks)`,\n ));\n }, timeoutMs);\n waiters.push(waiter);\n });\n };\n\n const waitForCount = (n: number, timeoutMs: number = defaultTimeoutMs): Promise<void> => {\n return stopAt((current) => current.length >= n, timeoutMs);\n };\n\n return {\n chunks,\n stopAt,\n waitForCount,\n close,\n };\n};\n","//? Orchestrator — runs all five test layers (contract, auth-enforcement,\n//? rate-limit, fuzz, custom) against a running server and returns a single\n//? combined summary. Consumers call this from their own thin script that\n//? imports the generated `apiMethodMap` + `apiInputSchemas`.\n\nimport { clearAllRateLimits, getCsrfConfig, getProjectConfig } from '@luckystack/core';\n\nimport { sampleSchemaInput } from './schemaSampleInput';\nimport { createTestDataMarker } from './testDataMarker';\nimport { resetServerState } from './resetServerState';\nimport { runContractTests } from './runContractTests';\nimport { runAuthEnforcementTests } from './runAuthEnforcementTests';\nimport { runRateLimitTests } from './runRateLimitTests';\nimport { runCsrfEnforcementTests } from './runCsrfEnforcementTests';\nimport { runFuzzTests } from './runFuzzTests';\nimport { runCustomTestsAfterEnvironmentPrepared } from './customTests';\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport { calculateSummary, LAYER_KEYS } from './testLayerHelpers';\nimport type { CustomTestResult, RunCustomTestsSummary } from './customTests';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\nimport type { ZodType } from 'zod';\n\ntype ApiInputSchemas = Partial<Record<string, Partial<Record<string, Partial<Record<string, ZodType | undefined>>>>>>;\n\nexport interface RunAllTestsInput {\n apiMethodMap: ApiMethodMap;\n /**\n * Lazy loader for the consumer's default `config.ts` export. Required so\n * runAllTests cannot silently skip a configured secret manager; resolution\n * happens in THIS process before any layer or env-backed client is touched.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n apiMetaMap: ApiMetaMap;\n apiInputSchemas: ApiInputSchemas;\n baseUrl: string;\n /** Cookie name for the session token. Defaults to the project's configured session cookie name (`projectConfig.http.sessionCookieName`). */\n sessionCookieName?: string;\n /** Auth token applied as a `Cookie` header to sweep-layer requests that need a session. */\n authToken?: string;\n /** Endpoints to skip from the sweep layers, e.g. routes that need a file upload. */\n skip?: string[];\n /** Substring filter applied to `<page>/<name>/<version>`. */\n filter?: string;\n /** Disable individual layers (faster local iteration). */\n noSweep?: boolean;\n noFuzz?: boolean;\n noRateLimit?: boolean;\n /**\n * Disable the CSRF-enforcement layer. Defaults to ON (the layer runs) — but it\n * can only probe past the auth guard with a session, so it is a no-op unless an\n * `authToken` is supplied.\n */\n noCsrf?: boolean;\n noCustom?: boolean;\n /**\n * Token sent as `X-Test-Reset-Token` when the rate-limit layer resets the\n * shared bucket between endpoints. The server's `/_test/reset` requires\n * `TEST_RESET_TOKEN` to be set and matching — an unset/wrong token is 403, so\n * `resetBetweenEndpoints` would silently no-op. Defaults to\n * `process.env.TEST_RESET_TOKEN` so a CI run with the env set works with no\n * extra wiring; pass explicitly to override.\n */\n resetToken?: string;\n /**\n * Contract-test mutation safety (finding #98). When a reset token is\n * available, the sweep POSTs `/_test/reset` ONCE before the first layer\n * (clean slate — no real row the generated payloads could clobber) and ONCE\n * after the last (drop whatever the sweep created, so no test data persists).\n * Defaults to ON; set `false` to opt out. With no token the bookends are\n * silently skipped with a logged note (they can't run — `/_test/reset` 403s).\n */\n resetBookends?: boolean;\n}\n\nexport interface RunAllTestsSummary {\n contract?: RunContractSummary;\n auth?: RunContractSummary;\n rateLimit?: RunContractSummary;\n csrf?: RunContractSummary;\n fuzz?: RunContractSummary;\n custom?: RunCustomTestsSummary;\n totalPassed: number;\n totalFailed: number;\n}\n\nconst inputForEndpoint = (apiInputSchemas: ApiInputSchemas, stringPrefix: string) =>\n (endpoint: EndpointDescriptor): unknown => {\n const schema = apiInputSchemas[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n //? Tag generated strings with the run-unique test-data marker so any rows\n //? the sweep creates are identifiable + collision-free (finding #98).\n return schema ? sampleSchemaInput(schema, { stringPrefix }) : {};\n };\n\n//? Decide whether the contract-sweep reset bookends should run. Returns the\n//? token to use, or null to skip. When the bookends are requested (the default)\n//? but no token is available, warn once — `/_test/reset` requires the token and\n//? would 403, so the bookends silently can't run; the sweep proceeds unguarded.\nconst resolveResetBookendToken = (input: RunAllTestsInput): string | null => {\n if (input.resetBookends === false) return null;\n const token = input.resetToken ?? process.env.TEST_RESET_TOKEN;\n if (!token) {\n console.warn(\n '[test-runner] runAllTests: reset bookends skipped — no reset token '\n + '(set TEST_RESET_TOKEN or pass resetToken/resetBookends). /_test/reset '\n + 'requires it, so the clean-slate + cleanup resets around the sweep are skipped.',\n );\n return null;\n }\n return token;\n};\n\nconst matchesFilter = (endpoint: EndpointDescriptor, filter: string | undefined): boolean => {\n if (!filter) return true;\n return `${endpoint.page}/${endpoint.name}/${endpoint.version}`.includes(filter);\n};\n\nconst filterResults = (results: ContractCheckResult[], filter: string | undefined): ContractCheckResult[] =>\n filter ? results.filter(r => matchesFilter(r.endpoint, filter)) : results;\n\nconst cloneSummary = (summary: RunContractSummary, filter: string | undefined): RunContractSummary => {\n if (!filter) return summary;\n return calculateSummary(filterResults(summary.results, filter));\n};\n\n//? Build the Cookie (and CSRF) headers sweep layers send when an `authToken`\n//? is supplied. In cookie-mode the server's CSRF middleware rejects POST/PUT/DELETE\n//? without a matching CSRF token, so we look up the session record and attach the\n//? token the same way customTests.ts does: call getSession(authToken) and read\n//? csrfToken off the persisted record. Token-mode sweeps (no Cookie header) and\n//? runs without authToken are unaffected — the CSRF header is never added.\nconst buildAuthHeaders = async (input: RunAllTestsInput): Promise<Record<string, string>> => {\n const headers: Record<string, string> = {};\n if (!input.authToken) return headers;\n\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n headers.Cookie = `${cookieName}=${input.authToken}`;\n\n //? Resolve the CSRF token lazily — import @luckystack/login at call time so\n //? projects that run the test-runner without login installed don't crash on\n //? import. Mirror the same dynamic import pattern used in customTests.ts.\n try {\n const { getSession } = await import('@luckystack/login');\n const session = await getSession(input.authToken);\n if (session?.csrfToken) {\n headers[getCsrfConfig().headerName] = session.csrfToken;\n }\n } catch (error) {\n //? @luckystack/login not installed or session lookup failed.\n //? Warn so the operator knows the sweep is running without a CSRF header\n //? (degraded mode) — the sweep continues, but CSRF-protected state-changing\n //? endpoints will be unreachable in cookie-mode.\n console.warn(\n '[test-runner] buildAuthHeaders: could not resolve CSRF token —',\n error instanceof Error ? error.message : String(error),\n '— sweep will run without the CSRF header (degraded mode).',\n );\n }\n\n return headers;\n};\n\n//? Run the four auto-sweep layers (contract → auth → rate-limit → fuzz) in\n//? order, honoring the per-layer disable flags, and write each filtered\n//? summary onto `summary`. Mutates `summary` in place — identical to the\n//? prior inline block. The auth and rate-limit / fuzz nesting + ordering is\n//? preserved exactly (auth always runs in the sweep; rate-limit + fuzz are\n//? individually gated).\nconst runSweepLayers = async (\n input: RunAllTestsInput,\n summary: RunAllTestsSummary,\n headers: Record<string, string>,\n inputFor: (endpoint: EndpointDescriptor) => unknown,\n): Promise<void> => {\n //? Reset bookend — BEFORE the sweep. With a clean DB no real record exists for\n //? the generated sample payloads to accidentally edit/delete (finding #98).\n const resetBookendToken = resolveResetBookendToken(input);\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken });\n }\n\n const contract = await runContractTests({\n apiMethodMap: input.apiMethodMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n headers,\n inputFor,\n });\n summary.contract = cloneSummary(contract, input.filter);\n\n const auth = await runAuthEnforcementTests({\n apiMethodMap: input.apiMethodMap,\n apiMetaMap: input.apiMetaMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n inputFor,\n });\n summary.auth = cloneSummary(auth, input.filter);\n\n if (!input.noRateLimit) {\n const rateLimit = await runRateLimitTests({\n apiMethodMap: input.apiMethodMap,\n apiMetaMap: input.apiMetaMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n headers,\n inputFor,\n //? Reset the shared per-IP rate-limit bucket between endpoints so a\n //? neighbour saturating the window doesn't cause false pass/fail on the\n //? next endpoint. Requires the server to expose /_test/reset (NODE_ENV in\n //? { 'development', 'test' } AND TEST_RESET_TOKEN set + matching header).\n resetBetweenEndpoints: true,\n //? Thread the reset token (explicit input, else the TEST_RESET_TOKEN env the\n //? server validates against) — without it `/_test/reset` returns 403 and the\n //? between-endpoint reset silently never happens.\n resetToken: input.resetToken ?? process.env.TEST_RESET_TOKEN,\n });\n summary.rateLimit = cloneSummary(rateLimit, input.filter);\n }\n\n //? CSRF-enforcement layer: probes that the framework's CSRF middleware rejects\n //? state-changing authenticated requests carrying NO CSRF header. It can only\n //? reach the CSRF check past the auth guard with a valid session, so it runs\n //? only when an `authToken` is supplied (and the layer isn't disabled). Without\n //? a token there is nothing to probe — the layer is silently skipped, leaving\n //? behavior unchanged for tokenless sweeps.\n if (!input.noCsrf && headers.Cookie) {\n const csrf = await runCsrfEnforcementTests({\n apiMethodMap: input.apiMethodMap,\n apiMetaMap: input.apiMetaMap,\n baseUrl: input.baseUrl,\n authCookie: headers.Cookie,\n skip: input.skip,\n inputFor,\n });\n summary.csrf = cloneSummary(csrf, input.filter);\n }\n\n if (!input.noFuzz) {\n const fuzz = await runFuzzTests({\n apiMethodMap: input.apiMethodMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n headers,\n });\n summary.fuzz = cloneSummary(fuzz, input.filter);\n }\n\n //? Reset bookend — AFTER the sweep. Drops every row the sweep's generated\n //? payloads created so no `lstest_*` test data persists in the DB (finding #98).\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken });\n }\n};\n\n//? Run the custom (Layer 5) sweep, after clearing the per-route rate-limit\n//? buckets the sweep layers drained. Mutates `summary` in place — identical to\n//? the prior inline block.\nconst runCustomLayer = async (input: RunAllTestsInput, summary: RunAllTestsSummary): Promise<void> => {\n //? The rate-limit layer drains per-route buckets; clear them before the\n //? custom layer so business-logic tests on low-limit routes (e.g.\n //? confirmReset, sendReset) aren't spuriously rejected with\n //? api.rateLimitExceeded. Runs in-process against the same Redis the server\n //? uses. Non-fatal if the active strategy has no clear().\n try {\n await clearAllRateLimits();\n } catch {\n //? swallow — a missing clear() is a test-quality degrade, not a crash.\n }\n const custom = await runCustomTestsAfterEnvironmentPrepared({\n baseUrl: input.baseUrl,\n sessionCookieName: input.sessionCookieName,\n filter: input.filter,\n //? Pass the generated method map so the harness invokes each API route\n //? with its declared HTTP method (e.g. logout is DELETE, not POST).\n apiMethodMap: input.apiMethodMap,\n });\n summary.custom = custom;\n};\n\n//? The ordered list of sweep-layer keys that map to `RunContractSummary`\n//? slots on `RunAllTestsSummary`. Drives `computeTotals` and the reporter so\n//? adding a new sweep layer only requires adding it here + to the summary type.\nconst SWEEP_LAYER_ORDER = [\n LAYER_KEYS.contract,\n LAYER_KEYS.auth,\n LAYER_KEYS.rateLimit,\n LAYER_KEYS.csrf,\n LAYER_KEYS.fuzz,\n] as const;\n\ntype SweepLayerKey = typeof SWEEP_LAYER_ORDER[number];\n\n//? Map from each canonical layer key to the matching summary property name on\n//? `RunAllTestsSummary`. Property names diverged from LAYER_KEYS values early\n//? (LAYER_KEYS uses display strings like `'auth-enforcement'`); keeping this\n//? map here means neither the reporter nor computeTotals need to hand-code the\n//? mapping.\nconst SWEEP_SUMMARY_PROP: Record<SweepLayerKey, keyof Pick<RunAllTestsSummary, 'contract' | 'auth' | 'rateLimit' | 'csrf' | 'fuzz'>> = {\n [LAYER_KEYS.contract]: 'contract',\n [LAYER_KEYS.auth]: 'auth',\n [LAYER_KEYS.rateLimit]: 'rateLimit',\n [LAYER_KEYS.csrf]: 'csrf',\n [LAYER_KEYS.fuzz]: 'fuzz',\n};\n\n//? Iterate every sweep layer + the custom layer to sum passed/failed.\n//? DD-TR-various — xpass exit-code policy (documented decision):\n//? `xpass` (a case marked `expectedToFail` that unexpectedly passes) is NOT\n//? counted in `totalFailed`. Rationale: an xpass is a positive signal — the\n//? known issue is gone and the marker just needs removing. Treating it as a\n//? failure would make CI red when a bug is fixed, which is backwards.\n//? It IS shown prominently in the summary line and the \"Unexpectedly passed\"\n//? section so the stale marker can't rot silently behind a green run.\n//? To opt into strict mode (xpass = CI failure), inspect `summary.custom?.xpassed`\n//? in your own script and `process.exit(1)` when it is > 0.\nconst computeTotals = (summary: RunAllTestsSummary): void => {\n let passed = 0;\n let failed = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n passed += s?.passed ?? 0;\n failed += s?.failed ?? 0;\n }\n summary.totalPassed = passed + (summary.custom?.passed ?? 0);\n summary.totalFailed = failed + (summary.custom?.failed ?? 0);\n};\n\nexport const runAllTests = async (input: RunAllTestsInput): Promise<RunAllTestsSummary> => {\n assertProjectConfigLoader(input, 'runAllTests');\n //? The live server resolves its own env, but Layer-5 tests may use ctx.prisma\n //? or Redis directly in THIS process. Resolve before importing custom tests or\n //? constructing those clients, otherwise DATABASE_URL_V<n> reaches Prisma raw.\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n\n const summary: RunAllTestsSummary = { totalPassed: 0, totalFailed: 0 };\n\n //? Warn when a non-empty apiMethodMap is paired with an empty apiMetaMap.\n //? The auth-enforcement and rate-limit layers both use the meta map to decide\n //? which routes require login and which have declared rate limits. An absent\n //? or stale map silently skips every auth/rate-limit check — the run looks\n //? green but provides no real coverage.\n if (!input.noSweep) {\n const hasRoutes = Object.keys(input.apiMethodMap).length > 0;\n const hasMeta = Object.keys(input.apiMetaMap).length > 0;\n if (hasRoutes && !hasMeta) {\n console.warn(\n '[test-runner] runAllTests: apiMetaMap is empty but apiMethodMap has routes. '\n + 'The auth-enforcement and rate-limit layers will skip every route. '\n + 'Pass the generated apiMetaMap to enable those checks.',\n );\n }\n }\n\n const headers = await buildAuthHeaders(input);\n //? One marker per run so every generated string this sweep posts is tagged\n //? with the same recognizable `lstest_<uuid>_` prefix (finding #98).\n const inputFor = inputForEndpoint(input.apiInputSchemas, createTestDataMarker());\n\n if (!input.noSweep) {\n await runSweepLayers(input, summary, headers, inputFor);\n }\n\n if (!input.noCustom) {\n await runCustomLayer(input, summary);\n }\n\n computeTotals(summary);\n\n return summary;\n};\n\n//? ── Colored, list-based reporting ───────────────────────────────────────\n//? Honors NO_COLOR / FORCE_COLOR (https://no-color.org) and falls back to\n//? plain text when stdout is not a TTY (piped to a file / CI log).\nconst useColor = ((): boolean => {\n if (process.env.NO_COLOR) return false;\n if (process.env.FORCE_COLOR) return true;\n return process.stdout.isTTY;\n})();\nconst paint = (code: string, text: string): string => (useColor ? `\\u001B[${code}m${text}\\u001B[0m` : text);\nconst green = (t: string): string => paint('32', t);\nconst red = (t: string): string => paint('31', t);\nconst yellow = (t: string): string => paint('33', t);\nconst dim = (t: string): string => paint('2', t);\nconst bold = (t: string): string => paint('1', t);\n\ninterface ReportRow {\n label: string;\n reason?: string;\n}\n\n//? DD-TR-various — leaky summary union tightened:\n//? `LayerSummary` is the minimal shape the reporter cares about — the fields\n//? every layer summary has in common. The extra-field helpers discriminate via\n//? `in` to stay type-safe without widening callers to `unknown`.\ninterface LayerSummary {\n total: number;\n passed: number;\n failed: number;\n}\n\nconst xfailOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('xfailed' in s)) return 0;\n const v = (s as Record<string, unknown>).xfailed;\n return typeof v === 'number' ? v : 0;\n};\nconst skippedOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('skipped' in s)) return 0;\n const v = (s as Record<string, unknown>).skipped;\n return typeof v === 'number' ? v : 0;\n};\n\n//? Per-layer headline: green \"X/Y passed\" when clean, red \"Z failed\" when not,\n//? plus dim xfail / skipped counts. This is the \"14/20 in groen, 6/20 in rood\"\n//? the report leads with.\nconst formatLayerLine = (label: string, s: LayerSummary | undefined): string => {\n if (!s) return ` ${dim('–')} ${label.padEnd(18)} ${dim('(layer skipped)')}`;\n const clean = s.failed === 0;\n const mark = clean ? green('✓') : red('✗');\n const head = clean\n ? green(`${s.passed}/${s.total} passed`)\n : `${green(`${s.passed}/${s.total} passed`)} ${red(`${s.failed}/${s.total} failed`)}`;\n const extras: string[] = [];\n const xf = xfailOf(s);\n const sk = skippedOf(s);\n if (xf) extras.push(yellow(`${xf} expected-fail`));\n if (sk) extras.push(dim(`${sk} skipped`));\n const extra = extras.length > 0 ? ` ${dim('·')} ${extras.join(dim(', '))}` : '';\n return ` ${mark} ${label.padEnd(18)} ${head}${extra}`;\n};\n\nconst sweepFailRows = (s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'fail')\n .map((r) => ({\n label: `${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.errorCode ? `${r.reason ?? 'failed'} [${r.errorCode}]` : r.reason,\n }));\n\nconst sweepSkipRows = (layer: string, s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'skipped')\n .map((r) => ({\n label: `${layer}: ${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.reason,\n }));\n\nconst customRows = (s: RunCustomTestsSummary | undefined, status: CustomTestResult['status']): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === status)\n .map((r) => ({ label: `${r.routePath} :: ${r.caseName}`, reason: r.reason }));\n\nconst printSection = (title: (t: string) => string, heading: string, rows: ReportRow[], bullet: string): void => {\n if (rows.length === 0) return;\n console.log('');\n console.log(title(bold(`${heading} (${rows.length})`)));\n for (const row of rows) {\n console.log(` ${title(bullet)} ${row.label}`);\n if (row.reason) console.log(` ${dim(row.reason)}`);\n }\n};\n\nexport const logRunAllSummary = (summary: RunAllTestsSummary): void => {\n console.log('');\n\n //? Print a headline line for every sweep layer (ordered) then the custom layer.\n for (const key of SWEEP_LAYER_ORDER) {\n console.log(formatLayerLine(key, summary[SWEEP_SUMMARY_PROP[key]]));\n }\n console.log(formatLayerLine(LAYER_KEYS.custom, summary.custom));\n\n //? Real failures across every layer — these MUST be fixed (a green test that\n //? went red, or a wrong test). Sweep failures + un-marked custom failures.\n const failed: ReportRow[] = [\n ...SWEEP_LAYER_ORDER.flatMap((key) => sweepFailRows(summary[SWEEP_SUMMARY_PROP[key]])),\n ...customRows(summary.custom, 'fail'),\n ];\n printSection(red, 'Failed — must be fixed (real bugs / wrong tests)', failed, '✗');\n\n //? Known, accepted failures — red is OK here; they are tracked, not regressions.\n printSection(yellow, 'Expected failures — known issues, allowed to fail', customRows(summary.custom, 'xfail'), '⚠');\n\n //? Stale markers — these passed but are still flagged expectedToFail.\n printSection(yellow, 'Unexpectedly passed — remove the expectedToFail marker', customRows(summary.custom, 'xpass'), '?');\n\n //? Skipped with reason — not run in this mode (login-gated, high rate-limit, …).\n const skipped: ReportRow[] = SWEEP_LAYER_ORDER.flatMap((key) =>\n sweepSkipRows(key, summary[SWEEP_SUMMARY_PROP[key]]),\n );\n printSection(dim, 'Skipped — not run (with reason)', skipped, '–');\n\n //? Sum xfail and skipped across all layers (sweep + custom) in one pass.\n let totalXfail = 0;\n let totalSkipped = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n totalXfail += xfailOf(s);\n totalSkipped += skippedOf(s);\n }\n totalXfail += xfailOf(summary.custom);\n totalSkipped += skippedOf(summary.custom);\n\n //? `xpass` (a case still marked expectedToFail that now passes) is NOT a\n //? failure, so it doesn't move the exit code — but it MUST be visible in the\n //? final line, otherwise a stale marker rots silently behind a green run.\n const totalXpass = summary.custom?.xpassed ?? 0;\n\n console.log('');\n const parts = [\n green(`${summary.totalPassed} passed`),\n summary.totalFailed > 0 ? red(`${summary.totalFailed} failed`) : dim('0 failed'),\n totalXfail > 0 ? yellow(`${totalXfail} expected-fail`) : dim('0 expected-fail'),\n totalXpass > 0 ? yellow(`${totalXpass} unexpectedly-passed`) : dim('0 unexpectedly-passed'),\n totalSkipped > 0 ? yellow(`${totalSkipped} skipped`) : dim('0 skipped'),\n ];\n console.log(`Summary: ${parts.join(dim(' · '))}`);\n console.log(dim(' legend: ✗ red = must fix · ⚠ yellow = known/allowed · – skipped (not run)'));\n};\n","//? Extension registry for the test runner. Consumers plug in custom test\n//? layers, realistic-payload fixtures, and result reporters without\n//? forking the framework.\n//?\n//? Three slots:\n//?\n//? 1. Test layers — additional checks per endpoint (CORS enforcement,\n//? business rules, custom auth schemes, multi-tenant isolation).\n//? 2. Fixtures — realistic input payloads (valid + invalid) per typeKey\n//? so the fuzz layer can use them before falling back to schema-random.\n//? 3. Reporters — per-result and per-summary callbacks + optional\n//? webhook URL the runner POSTs the batch summary to.\n//?\n//? All slots are append-only at registration time; clearing requires\n//? `resetTestExtensionsForTests()` (kept out of the production surface\n//? to prevent accidental wipes from a misbehaving plugin).\n\nexport interface TestLayerInput {\n /** Resolved route name (`api/billing/getInvoice/v1`). */\n endpoint: string;\n /** Method when applicable, undefined for sync endpoints. */\n method?: string;\n /** Bearer token / session token to use for auth-aware checks. */\n authToken?: string;\n}\n\nexport interface TestLayerResult {\n passed: boolean;\n /** Short reason key — i18n-style errorCode when failed. */\n message?: string;\n /** Optional extras for the reporter (latency, response sample, ...). */\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestLayer {\n name: string;\n run: (input: TestLayerInput) => Promise<TestLayerResult> | TestLayerResult;\n}\n\nexport interface TestFixture<TPayload = unknown> {\n valid: TPayload[];\n invalid: TPayload[];\n}\n\nexport interface TestResult {\n layer: string;\n endpoint: string;\n passed: boolean;\n message?: string;\n durationMs?: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestSummary {\n totalLayers: number;\n totalEndpoints: number;\n passed: number;\n failed: number;\n results: TestResult[];\n}\n\nexport interface TestReporter {\n /** Called once per individual TestResult as the runner progresses. */\n onResult?: (result: TestResult) => void | Promise<void>;\n /** Called once at the end with the aggregated summary. */\n onSummary?: (summary: TestSummary) => void | Promise<void>;\n /** Optional webhook the runner POSTs the summary to. */\n webhookUrl?: string;\n webhookAuth?: { type: 'bearer'; token: string };\n}\n\nconst layers = new Map<string, TestLayer>();\nconst fixtures = new Map<string, TestFixture>();\nlet reporter: TestReporter | null = null;\n\n/**\n * Register a custom test layer. Re-registering the same name replaces\n * the previous entry. Use for CORS enforcement, business-rule checks,\n * custom auth schemes, multi-tenant isolation, GDPR data-flow checks.\n */\nexport const registerTestLayer = (layer: TestLayer): void => {\n layers.set(layer.name, layer);\n};\n\nexport const listTestLayers = (): TestLayer[] => [...layers.values()];\n\n/**\n * Register a realistic payload fixture for a specific type key. The fuzz\n * layer prefers these over schema-random generated inputs when a fixture\n * exists for the endpoint's input type.\n *\n * `valid` payloads are used to verify happy-path behavior; `invalid`\n * payloads probe the validator's error responses.\n */\nexport const registerTestFixture = <TPayload = unknown>(typeKey: string, fixture: TestFixture<TPayload>): void => {\n fixtures.set(typeKey, fixture);\n};\n\nexport const getTestFixture = (typeKey: string): TestFixture | undefined => fixtures.get(typeKey);\n\n/**\n * Register a test reporter. Set `null` to unregister. The runner calls\n * `onResult` per individual result as the run progresses and `onSummary`\n * once at the end. When `webhookUrl` is set, the runner additionally\n * POSTs the JSON-serialised summary to that URL.\n */\nexport const registerTestReporter = (newReporter: TestReporter | null): void => {\n reporter = newReporter;\n};\n\nexport const getTestReporter = (): TestReporter | null => reporter;\n\nexport const resetTestExtensionsForTests = (): void => {\n layers.clear();\n fixtures.clear();\n reporter = null;\n};\n","//? Runs every layer registered via `registerTestLayer` against every endpoint\n//? in the given `apiMethodMap`, fans each result to the registered reporter\n//? (`onResult` / `onSummary`), and — when the reporter declares a `webhookUrl`\n//? — POSTs the JSON-serialised summary to it.\n//?\n//? Built-in sweep layers (contract/auth/rate-limit/fuzz) are NOT run here and\n//? `runAllTests` does NOT call this function automatically. The extension registry\n//? is a coordination surface for consumer test harnesses: read the layers with\n//? `listTestLayers()`, then call `runRegisteredLayers({ apiMethodMap, authToken })`\n//? from your own aggregator after the built-in sweeps finish. See\n//? docs/extension-hooks.md for the full lifecycle and examples.\n\nimport { tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport { walkEndpoints } from './walkEndpoints';\nimport { listTestLayers, getTestReporter } from './extensionRegistry';\nimport { shouldSkip } from './testLayerHelpers';\nimport type { TestResult, TestSummary } from './extensionRegistry';\nimport type { ApiMethodMap } from './types';\n\nexport interface RunRegisteredLayersInput {\n apiMethodMap: ApiMethodMap;\n /** Session/bearer token forwarded to each layer's `run({ authToken })`. */\n authToken?: string;\n /**\n * Endpoints to skip — matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>`, same convention as the built-in sweeps.\n */\n skip?: string[];\n}\n\n//? Whether a webhook URL targets a loopback host (localhost / 127.0.0.0/8 / ::1).\n//? Plaintext http to a non-loopback target leaks the route inventory (+ bearer)\n//? over the wire, so we warn — but never block, since the URL is consumer-self-\n//? registered.\nconst isLoopbackHost = (host: string): boolean =>\n host === 'localhost'\n || host === '::1'\n || host === '[::1]'\n || /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host);\n\n//? POST the summary to the reporter's webhook, if one is configured. Best-effort\n//? — a webhook failure must never fail the test run.\nconst postWebhook = async (summary: TestSummary): Promise<void> => {\n const reporter = getTestReporter();\n const webhookUrl = reporter?.webhookUrl;\n if (!reporter || !webhookUrl) return;\n //? Warn (don't block) when forwarding the summary — which carries endpoint\n //? paths, error codes and reporter metadata verbatim — plaintext to a\n //? non-loopback host. Mirrors the plaintext-target caution the other network\n //? helpers carry.\n const [, parsedUrl] = tryCatchSync(() => new URL(webhookUrl));\n if (parsedUrl?.protocol === 'http:' && !isLoopbackHost(parsedUrl.hostname)) {\n console.warn(\n `[test-runner] reporter.webhookUrl is plaintext http to a non-loopback host (${parsedUrl.hostname}); `\n + 'the summary (endpoint paths, error codes, metadata) and any bearer token are sent unencrypted.',\n );\n }\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (reporter.webhookAuth?.type === 'bearer') headers.Authorization = `Bearer ${reporter.webhookAuth.token}`;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, 10_000);\n await tryCatch(() => fetch(webhookUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(summary),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n};\n\nexport const runRegisteredLayers = async (input: RunRegisteredLayersInput): Promise<TestSummary> => {\n const layers = listTestLayers();\n const endpoints = walkEndpoints(input.apiMethodMap);\n const reporter = getTestReporter();\n const skip = input.skip ?? [];\n const results: TestResult[] = [];\n\n for (const layer of layers) {\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) continue;\n const started = Date.now();\n const [runError, layerResult] = await tryCatch(async () =>\n layer.run({ endpoint: endpoint.fullPath, method: endpoint.method, authToken: input.authToken }));\n const durationMs = Date.now() - started;\n\n const result: TestResult = runError || !layerResult\n ? {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: false,\n message: runError?.message ?? 'layer returned no result',\n durationMs,\n }\n : {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: layerResult.passed,\n message: layerResult.message,\n durationMs,\n metadata: layerResult.metadata,\n };\n\n results.push(result);\n await tryCatch(async () => reporter?.onResult?.(result));\n }\n }\n\n const summary: TestSummary = {\n totalLayers: layers.length,\n totalEndpoints: endpoints.length,\n passed: results.filter(r => r.passed).length,\n failed: results.filter(r => !r.passed).length,\n results,\n };\n\n await tryCatch(async () => reporter?.onSummary?.(summary));\n await postWebhook(summary);\n\n return summary;\n};\n"],"mappings":"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","names":["DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","fs","path","getProjectConfig","tryCatch","tryCatch","getProjectConfig","fs","path","tryCatch","getProjectConfig","getCsrfConfig","getProjectConfig","getProjectConfig","getCsrfConfig","tryCatch","tryCatchSync","reporter","tryCatchSync","tryCatch","layers"]}
1
+ {"version":3,"sources":["../src/walkEndpoints.ts","../src/probeRequest.ts","../src/contractCheck.ts","../src/testLayerHelpers.ts","../src/runContractTests.ts","../src/authEnforcementCheck.ts","../src/runAuthEnforcementTests.ts","../src/rateLimitCheck.ts","../src/resetServerState.ts","../src/runRateLimitTests.ts","../src/csrfEnforcementCheck.ts","../src/runCsrfEnforcementTests.ts","../src/fuzzCheck.ts","../src/runFuzzTests.ts","../src/resolveTestBaseUrl.ts","../src/resolveTestEnvironment.ts","../src/schemaSampleInput.ts","../src/testDataMarker.ts","../src/reporter.ts","../src/customTests.ts","../src/streamWatcher.ts","../src/runAllTests.ts","../src/extensionRegistry.ts","../src/runRegisteredLayers.ts"],"sourcesContent":["import type { EndpointDescriptor, HttpMethod, SyncMethodMap } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\n\n//? Must stay in sync with the `HttpMethod` union in types.ts. Only values that\n//? appear in both sets are valid probes — anything else is an unrecognised method.\nconst VALID_HTTP_METHODS = new Set<HttpMethod>(['GET', 'POST', 'PUT', 'DELETE']);\n\nconst toHttpMethod = (raw: string): HttpMethod | null => {\n //? Guard against a stale or hand-edited generated map carrying an unrecognised\n //? method string. An unknown value would propagate silently as a typed HttpMethod\n //? and break every probe that uses it. Return null so the caller can skip it.\n const upper = raw.toUpperCase();\n for (const m of VALID_HTTP_METHODS) {\n if (m === upper) return m;\n }\n return null;\n};\n\nexport const walkEndpoints = (apiMethodMap: ApiMethodMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(apiMethodMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, rawMethod] of Object.entries(versionMap)) {\n if (rawMethod === undefined) continue;\n const method = toHttpMethod(rawMethod);\n if (!method) {\n //? Unrecognised method in the generated map — log and skip rather than\n //? silently coercing, which would produce invalid probes.\n console.warn(`[test-runner] walkEndpoints: skipping api/${page}/${name}/${version} — unrecognised method '${rawMethod}' in apiMethodMap`);\n continue;\n }\n endpoints.push({\n page,\n name,\n version,\n method,\n fullPath: `api/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n\n//? Walk the generated `syncMethodMap` and produce a flat list of sync-route\n//? descriptors. Each descriptor's `fullPath` is `sync/<page>/<name>/<version>`.\n//? Sync routes always use POST over the HTTP-fallback transport regardless of\n//? the declared method, so the method is hardcoded to `POST` here.\nexport const walkSyncEndpoints = (syncMethodMap: SyncMethodMap): EndpointDescriptor[] => {\n const endpoints: EndpointDescriptor[] = [];\n for (const [page, nameMap] of Object.entries(syncMethodMap)) {\n if (!nameMap) continue;\n for (const [name, versionMap] of Object.entries(nameMap)) {\n if (!versionMap) continue;\n for (const [version, method] of Object.entries(versionMap)) {\n if (method === undefined) continue;\n endpoints.push({\n page,\n name,\n version,\n method: 'POST',\n fullPath: `sync/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n","//? Single-source fetch probe used by every check layer (contract, auth,\n//? csrf, rate-limit). Centralises the AbortController + timeout + tryCatch\n//? pattern so it isn't copied into each check file.\n\nimport { tryCatch } from '@luckystack/core';\nimport type { HttpMethod } from './types';\n\nexport interface ProbeRequestInput {\n url: string;\n method: HttpMethod;\n baseUrl: string;\n body?: unknown;\n headers?: Record<string, string>;\n requestTimeoutMs: number;\n}\n\nexport interface ProbeResponse {\n httpStatus: number;\n parsed: { status?: string; errorCode?: string } | null;\n}\n\n/**\n * Send a single timed fetch probe and parse the JSON envelope. Returns\n * `null` when the request itself fails (network error or abort).\n */\nexport const sendProbe = async (input: ProbeRequestInput): Promise<ProbeResponse | null> => {\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, input.requestTimeoutMs);\n\n const [fetchError, response] = await tryCatch(() => fetch(input.url, {\n method: input.method,\n headers: {\n 'Content-Type': 'application/json',\n //? Browsers attach Origin on state-changing requests; the server's origin\n //? policy fail-closes POST/PUT/DELETE without one.\n 'Origin': new URL(input.baseUrl).origin,\n ...input.headers,\n },\n body: input.method === 'GET' ? undefined : JSON.stringify(input.body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n\n if (fetchError || !response) return null;\n\n const [, parsed] = await tryCatch<{ status?: string; errorCode?: string } | null, undefined>(\n async () => await response.json() as { status?: string; errorCode?: string },\n );\n\n return { httpStatus: response.status, parsed: parsed ?? null };\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\nexport interface ContractCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * Optional input factory. Defaults to `{}`. When an endpoint requires a\n * specific shape to not crash, callers can override per-endpoint here.\n */\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /**\n * Optional headers (auth tokens, cookies). Applied to every request.\n */\n headers?: Record<string, string>;\n /**\n * Per-call request timeout in ms. Defaults to 5000. Bump for slow endpoints\n * (AI calls, large reports) where the framework default would false-fail\n * the contract check.\n */\n requestTimeoutMs?: number;\n}\n\n//? Contract layer: send any-shape input, accept any response where\n//? `status` is `success` or `error` (with an `errorCode`). Anything else is\n//? a contract violation — 5xx, thrown exceptions, or shapes outside this\n//? envelope.\n//?\n//? The goal here is NOT to prove every endpoint works end-to-end; it's to\n//? catch endpoints that crash the server or return garbage on well-formed\n//? empty input. Real behavior tests go in the per-endpoint test file.\nexport const runContractCheck = async (input: ContractCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n const result = await sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (!parsed) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'JSON parse failed',\n durationMs,\n };\n }\n\n if (parsed.status !== 'success' && parsed.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.status === 'error' && !parsed.errorCode) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n reason: 'Error response missing `errorCode`',\n durationMs,\n };\n }\n\n //? When `inputFor` produced a non-empty sample body but the route returned an\n //? error, the probe may have failed its own Zod validation rather than proving\n //? the contract. Counting that as a PASS is misleading — the happy-path was\n //? never exercised. Classify as `skipped` so the caller knows the body was\n //? rejected before the handler ran and can supply a better sample.\n const bodyIsNonEmpty = body !== null\n && typeof body === 'object'\n && !Array.isArray(body)\n && Object.keys(body).length > 0;\n if (input.inputFor && bodyIsNonEmpty && parsed.status === 'error') {\n return {\n endpoint,\n status: 'skipped',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `inputFor produced a non-empty sample but route returned error (${parsed.errorCode ?? 'unknown'}); provide a valid sample via inputFor to test the happy path`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: parsed.status,\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","//? Shared helpers for the sweep test layers (contract, auth-enforcement,\n//? rate-limit, fuzz). Centralizes the skip-matching, meta-map queries, and\n//? summary-count math that were previously copy-pasted across each layer file.\n\nimport type {\n ApiMetaMap,\n ContractCheckResult,\n EndpointDescriptor,\n HttpMethod,\n RunContractSummary,\n} from './types';\n\n//? Single source of truth for the canonical layer names used in both the\n//? orchestrator (`runAllTests`) and the reporter (`logRunAllSummary`). Keeping\n//? them here prevents silent drift between the reporting labels and the summary\n//? property keys when layers are added or renamed.\nexport const LAYER_KEYS = {\n contract: 'contract',\n auth: 'auth-enforcement',\n rateLimit: 'rate-limit',\n csrf: 'csrf-enforcement',\n fuzz: 'fuzz',\n custom: 'custom',\n} as const;\n\n//? HTTP methods that carry a body and mutate server state. The CSRF middleware\n//? enforces a token only on these. Fuzz / rate-limit layers share this set to\n//? avoid firing junk bodies at mutating authenticated routes in cookie-mode.\nexport const STATE_CHANGING_METHODS = new Set<HttpMethod>(['POST', 'PUT', 'DELETE']);\n\n/**\n * Whether an endpoint is in the explicit skip list. Matched against\n * `<page>/<name>` (version-agnostic) and `<page>/<name>/<version>`\n * (version-specific).\n */\nexport const shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\n if (skip.length === 0) return false;\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\n const versionless = `${endpoint.page}/${endpoint.name}`;\n return skip.includes(versioned) || skip.includes(versionless);\n};\n\n/** Whether the endpoint's meta entry declares `auth.login: true`. */\nexport const requiresLogin = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n //? `apiMetaMap` is a consumer-generated RUNTIME artifact — the static type\n //? declares `auth` as required, but a stale/partial entry can be missing it.\n //? Read defensively so a malformed entry surfaces as `false` (no login) and\n //? gets handled by the layer's skip path, instead of throwing a TypeError\n //? outside the per-endpoint tryCatch and aborting the whole layer.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return meta?.auth?.login ?? false;\n};\n\n/** Whether the endpoint requires ANY authorization: `auth.login: true` OR a\n * declared non-empty `auth.additional[]` predicate list (role/tenant/ownership\n * guards). The auth sweep must probe BOTH — an additional[]-only route (login:\n * false) is still authz-protected and an unauth call must be rejected. */\nexport const hasAuthRequirement = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime-defensive read of a generated artifact\n return (meta?.auth?.login ?? false) || (meta?.auth?.hasAdditional ?? false);\n};\n\n/** Whether the meta map carries an entry for this endpoint at all. */\nexport const hasMetaEntry = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean =>\n apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version] !== undefined;\n\n/**\n * The endpoint's declared numeric rate limit, or `null` when it has none\n * (`false`/`undefined`) or no meta entry exists.\n */\nexport const getRateLimit = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): number | null => {\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n if (!meta || meta.rateLimit === false || meta.rateLimit === undefined) return null;\n return meta.rateLimit;\n};\n\n/** Aggregate pass/fail/skip counts over a sweep layer's results. */\nexport const calculateSummary = (results: ContractCheckResult[]): RunContractSummary => ({\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n skipped: results.filter(r => r.status === 'skipped').length,\n results,\n});\n","import { walkEndpoints } from './walkEndpoints';\nimport { runContractCheck } from './contractCheck';\nimport { shouldSkip, calculateSummary } from './testLayerHelpers';\nimport type { ApiMethodMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunContractTestsInput {\n apiMethodMap: ApiMethodMap;\n baseUrl: string;\n /**\n * Endpoints to skip. Matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>` (version-specific). Useful for endpoints that\n * are known to need real input (e.g. a file upload).\n */\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runContractTests = async (input: RunContractTestsInput): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? Serial rather than parallel so we don't trip rate limiters or mask\n //? endpoint ordering bugs. Swap to a pool later if walk-time matters.\n const result = await runContractCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n headers: input.headers,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error codes the framework emits when an unauthenticated request hits\n//? a protected endpoint: `auth.required` for an `auth.login: true` route, and\n//? `auth.forbidden` for a route guarded ONLY by `auth.additional[]` predicates\n//? (login: false) — validateRequest forbids the anonymous caller. Either is a PASS;\n//? both mean the guard fired. See packages/api/src/handleHttpApiRequest.ts +\n//? packages/core/src/validateRequest.ts.\nconst EXPECTED_ERROR_CODES = new Set(['auth.required', 'auth.forbidden']);\n\nexport interface AuthEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n}\n\n//? Auth-enforcement layer: for each `auth.login: true` endpoint, call it with\n//? NO session headers/cookies. Expect the server to reject with `auth.required`.\n//?\n//? A passing endpoint here proves the login guard isn't silently bypassed.\n//? Endpoints that return `success` without a session are a critical finding.\nexport const runAuthEnforcementCheck = async (\n input: AuthEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n\n //? No session cookie, no auth header — deliberately. Origin IS sent so the\n //? server's origin policy doesn't 403 before the auth check can run.\n const result = await sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return {\n endpoint,\n status: 'fail',\n reason: 'fetch returned no response',\n durationMs: Date.now() - started,\n };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'success',\n reason: 'auth.login endpoint returned success without a session',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error') {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'unknown',\n reason: 'Response missing standard `status` envelope',\n durationMs,\n };\n }\n\n if (parsed.errorCode === undefined || !EXPECTED_ERROR_CODES.has(parsed.errorCode)) {\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n reason: `expected an auth-rejection errorCode (${[...EXPECTED_ERROR_CODES].join(' | ')}) but got '${parsed.errorCode ?? '(missing)'}'`,\n durationMs,\n };\n }\n\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runAuthEnforcementCheck } from './authEnforcementCheck';\nimport { shouldSkip, hasAuthRequirement, hasMetaEntry, calculateSummary } from './testLayerHelpers';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunAuthEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n baseUrl: string;\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runAuthEnforcementTests = async (\n input: RunAuthEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n //? Order matters: the auth-requirement check must run BEFORE shouldSkip so a\n //? protected endpoint in the explicit skip list is still recorded as\n //? `skipped` in the results (not silently dropped as a public route).\n //? Reversing the order would hide the skip from the summary. We gate on\n //? `hasAuthRequirement` (login OR additional[]-predicates) so role/tenant/\n //? ownership-guarded routes with `login: false` are also probed.\n if (!hasMetaEntry(input.apiMetaMap, endpoint)) {\n //? Route is in `apiMethodMap` but ABSENT from `apiMetaMap` (partial drift /\n //? hand-trimmed map / generator bug). Its auth requirement is unknowable, so\n //? this layer can't assert it. Record a `skipped` result (NOT a silent\n //? continue) so the gap is visible per-route in the summary instead of the\n //? route masquerading as a verified-public endpoint. (M8)\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'No apiMetaMap entry — auth requirement unverifiable',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (!hasAuthRequirement(input.apiMetaMap, endpoint)) {\n //? Truly-public endpoints (meta entry present, login:false, no additional[])\n //? can't be tested by this layer — skip silently, no noise. They're still\n //? covered by the contract layer.\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runAuthEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n inputFor: input.inputFor,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\nconst EXPECTED_ERROR_CODE = 'api.rateLimitExceeded';\n\n//? Error codes the server emits BEFORE the rate-limit pipeline runs (auth guard\n//? + server-level CSRF middleware). When a drain response carries one of these,\n//? the bucket was never incremented — the final probe will fail for the wrong\n//? reason and should be classified as `skipped` rather than a real failure.\n//? These MUST match the framework's actual pre-limiter codes (pipeline order:\n//? auth → rateLimit, CSRF middleware before the limiter):\n//? - `auth.required` — login:true route called without a session\n//? (handleHttpApiRequest.ts).\n//? - `auth.forbidden` — additional[]-predicate failed / login:false guard\n//? (handleHttpApiRequest.ts).\n//? - `auth.csrfMismatch` — cookie-mode state-changing route, CSRF gate\n//? (server/httpRoutes/csrfMiddleware.ts).\n//? Validation (`api.invalidInputType`) runs AFTER the limiter, so it is NOT a\n//? pre-limiter code and is deliberately excluded.\nconst PRE_LIMITER_ERROR_CODES = new Set([\n 'auth.required',\n 'auth.forbidden',\n 'auth.csrfMismatch',\n]);\n\nexport interface RateLimitCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n rateLimit: number;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 10000. */\n requestTimeoutMs?: number;\n}\n\n//? Rate-limit layer: fire `rateLimit + 1` requests back to back and assert the\n//? last one is rejected with `api.rateLimitExceeded`. All preceding requests\n//? are allowed to return either success or any other envelope — only the N+1\n//? matters for this layer.\n//?\n//? This layer mutates server-side state (limiter counters). Run it after the\n//? contract + auth layers, or against a dedicated test instance. The server's\n//? clearAllRateLimits() utility can reset state between runs.\nexport const runRateLimitCheck = async (input: RateLimitCheckInput): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl, rateLimit } = input;\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\n const send = () => sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n headers: input.headers,\n requestTimeoutMs,\n });\n\n //? Fire `rateLimit` allowed requests first (drain the bucket), then a final\n //? request that should be blocked. Serial so the server sees a clean order.\n //? Inspect EVERY drain response for pre-limiter rejections (CSRF/auth/\n //? validation): if the server rejects before the limiter runs at any point,\n //? the bucket was never incremented for that request, the final probe will\n //? fail for the wrong reason, and we classify the check as `skipped`.\n for (let i = 0; i < rateLimit; i += 1) {\n const drained = await send();\n if (!drained) continue;\n const errorCode = drained.parsed?.errorCode;\n if (errorCode && PRE_LIMITER_ERROR_CODES.has(errorCode)) {\n return {\n endpoint,\n status: 'skipped',\n durationMs: Date.now() - started,\n reason: `drain request ${i + 1} rejected pre-limiter (${errorCode}); bucket was never incremented — check CSRF/auth headers`,\n };\n }\n }\n\n const final = await send();\n const durationMs = Date.now() - started;\n\n if (!final) {\n return {\n endpoint,\n status: 'fail',\n reason: 'Final rate-limit probe request failed to return a response',\n durationMs,\n };\n }\n\n const { httpStatus, parsed } = final;\n\n if (parsed?.status === 'error' && parsed.errorCode === EXPECTED_ERROR_CODE) {\n return {\n endpoint,\n status: 'pass',\n httpStatus,\n responseStatus: 'error',\n errorCode: parsed.errorCode,\n durationMs,\n };\n }\n\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint,\n status: 'fail',\n httpStatus,\n responseStatus,\n errorCode: parsed?.errorCode,\n reason: `expected '${EXPECTED_ERROR_CODE}' on request ${rateLimit + 1} but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n};\n","import { getProjectConfig, tryCatch } from '@luckystack/core';\n\n//? Calls the server's /_test/reset endpoint. Used between rate-limit test\n//? endpoints to drain the shared IP-based limiter bucket so one endpoint's\n//? N+1 assertion doesn't consume the next endpoint's window.\n//?\n//? /_test/reset is gated by `NODE_ENV in { 'development', 'test' }` on the\n//? server side AND requires `TEST_RESET_TOKEN` to be set unconditionally\n//? (no token = 403). For staging/preview deploys that enable it for CI,\n//? set `TEST_RESET_TOKEN` on the server and pass the same value here.\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\n\nexport interface ResetServerStateInput {\n baseUrl: string;\n token?: string;\n requestTimeoutMs?: number;\n}\n\nexport const resetServerState = async ({ baseUrl, token, requestTimeoutMs }: ResetServerStateInput): Promise<boolean> => {\n const url = `${baseUrl.replace(/\\/$/, '')}${getProjectConfig().http.testResetEndpoint}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (token) headers['X-Test-Reset-Token'] = token;\n\n //? Bound the call so a server that accepts the connection but never responds\n //? returns `false` (the existing failure path) instead of wedging the sweep.\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);\n const [error, response] = await tryCatch(() => fetch(url, { method: 'POST', headers, signal: controller.signal }));\n clearTimeout(timeoutHandle);\n if (error || !response) return false;\n return response.ok;\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runRateLimitCheck } from './rateLimitCheck';\nimport { resetServerState } from './resetServerState';\nimport { shouldSkip, requiresLogin, getRateLimit, calculateSummary, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunRateLimitTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n baseUrl: string;\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n headers?: Record<string, string>;\n /**\n * Max rateLimit value to test. Endpoints with a higher limit are skipped\n * to avoid firing thousands of requests in CI.\n */\n maxRateLimitToTest?: number;\n /**\n * When true, hit `/_test/reset` before each endpoint so limiter state is\n * clean. Requires the server to allow the endpoint: `NODE_ENV` in\n * { 'development', 'test' } AND `TEST_RESET_TOKEN` set on the server with a\n * matching `resetToken` here (an unset token is 403, never open). Without\n * resetting, the shared IP bucket leaks state between endpoints.\n */\n resetBetweenEndpoints?: boolean;\n /**\n * Token forwarded as `X-Test-Reset-Token` when resetting. Only relevant in\n * staging/preview deploys that enable the endpoint behind a token.\n */\n resetToken?: string;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runRateLimitTests = async (\n input: RunRateLimitTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const maxRateLimit = input.maxRateLimitToTest ?? 50;\n //? A login-required route returns `auth.required` BEFORE the rate limiter\n //? runs, so it can only be rate-limit-tested with a session. A Cookie header\n //? means an authenticated sweep. We deliberately do NOT auto-authenticate the\n //? sweep — that would execute real mutations (delete/update) with junk input.\n //? Set TEST_AUTH_TOKEN to opt in and cover login-gated routes.\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n const rateLimit = getRateLimit(input.apiMetaMap, endpoint);\n if (rateLimit === null) continue;\n\n if (!isAuthenticatedSweep && requiresLogin(input.apiMetaMap, endpoint)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'login-required route — set TEST_AUTH_TOKEN to rate-limit-test it (unauthenticated calls hit auth.required before the limiter)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n //? In cookie-mode the rate-limit check fires the endpoint `rateLimit + 1`\n //? times with junk input. State-changing routes (POST/PUT/DELETE) execute\n //? real mutation handlers on each hit — skip them to avoid side-effects on\n //? the test database. Their rate limits are better covered by a custom test\n //? that supplies valid input.\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in cookie-mode rate-limit sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (rateLimit > maxRateLimit) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: `rateLimit ${rateLimit} exceeds maxRateLimitToTest=${maxRateLimit}`,\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (input.resetBetweenEndpoints) {\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken });\n }\n\n const result = await runRateLimitCheck({\n endpoint,\n baseUrl: input.baseUrl,\n rateLimit,\n inputFor: input.inputFor,\n headers: input.headers,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Canonical error code the framework's CSRF middleware emits when a\n//? state-changing, cookie-authenticated request arrives without a matching\n//? CSRF token. See packages/server/src/httpRoutes/csrfMiddleware.ts.\nconst DEFAULT_EXPECTED_ERROR_CODE = 'auth.csrfMismatch';\nconst DEFAULT_EXPECTED_HTTP_STATUS = 403;\n\nexport interface CsrfEnforcementCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n /**\n * A valid session Cookie header (`<name>=<token>`). The probe deliberately\n * carries it so the request passes the auth guard and reaches the CSRF check.\n */\n authCookie: string;\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n}\n\n//? CSRF-enforcement layer: for each `auth.login: true` state-changing route,\n//? send an authenticated request (valid session Cookie) WITHOUT the CSRF\n//? header. Expect the framework's CSRF middleware to reject it. This is the\n//? inverse of the custom layer's CSRF token-passing — it proves the protection\n//? actually fires, so a consumer who misconfigures CSRF doesn't get a green run.\nexport const runCsrfEnforcementCheck = async (\n input: CsrfEnforcementCheckInput,\n): Promise<ContractCheckResult> => {\n const { endpoint, baseUrl } = input;\n //? CSRF is only enforced on state-changing methods (POST/PUT/DELETE). A GET\n //? probe would never reach the CSRF middleware regardless of headers, so it\n //? would always appear to pass — a false green. Guard here so the check is\n //? safe to call directly (the runner already filters, but direct callers may not).\n if (endpoint.method === 'GET') {\n return {\n endpoint,\n status: 'skipped',\n reason: `CSRF enforcement does not apply to ${endpoint.method} requests`,\n durationMs: 0,\n };\n }\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\n const body = input.inputFor ? input.inputFor(endpoint) : {};\n const started = Date.now();\n const expectedErrorCode = input.expectedErrorCode ?? DEFAULT_EXPECTED_ERROR_CODE;\n const expectedHttpStatus = input.expectedHttpStatus ?? DEFAULT_EXPECTED_HTTP_STATUS;\n\n //? Valid session Cookie + Origin, but NO CSRF header — that omission is the\n //? whole point of the probe.\n const result = await sendProbe({\n url,\n method: endpoint.method,\n baseUrl,\n body,\n headers: { Cookie: input.authCookie },\n requestTimeoutMs: input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n });\n\n if (!result) {\n return { endpoint, status: 'fail', reason: 'fetch returned no response', durationMs: Date.now() - started };\n }\n\n const { httpStatus, parsed } = result;\n const durationMs = Date.now() - started;\n\n if (parsed?.status === 'success') {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'success',\n reason: 'state-changing endpoint accepted an authenticated request with NO CSRF header — CSRF protection is not enforced',\n durationMs,\n };\n }\n\n if (parsed?.status !== 'error' || parsed.errorCode !== expectedErrorCode) {\n const responseStatus: ContractCheckResult['responseStatus'] =\n parsed?.status === 'success' || parsed?.status === 'error' ? parsed.status : 'unknown';\n return {\n endpoint, status: 'fail', httpStatus, responseStatus, errorCode: parsed?.errorCode,\n reason: `expected CSRF rejection '${expectedErrorCode}' but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\n durationMs,\n };\n }\n\n if (expectedHttpStatus !== false && httpStatus !== expectedHttpStatus) {\n return {\n endpoint, status: 'fail', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode,\n reason: `expected HTTP ${expectedHttpStatus} on CSRF rejection but got ${httpStatus}`,\n durationMs,\n };\n }\n\n return { endpoint, status: 'pass', httpStatus, responseStatus: 'error', errorCode: parsed.errorCode, durationMs };\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runCsrfEnforcementCheck } from './csrfEnforcementCheck';\nimport { shouldSkip, requiresLogin, hasMetaEntry, calculateSummary, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\nexport interface RunCsrfEnforcementTestsInput {\n apiMethodMap: ApiMethodMap;\n apiMetaMap: ApiMetaMap;\n baseUrl: string;\n /** Valid session Cookie header (`<name>=<token>`) the probes carry so they reach the CSRF check. */\n authCookie: string;\n skip?: string[];\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\n /** Per-call request timeout in ms forwarded to each check. */\n requestTimeoutMs?: number;\n /** Error code expected on CSRF rejection. Defaults to `auth.csrfMismatch`. */\n expectedErrorCode?: string;\n /** HTTP status expected on CSRF rejection. Defaults to 403; pass `false` to skip. */\n expectedHttpStatus?: number | false;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runCsrfEnforcementTests = async (\n input: RunCsrfEnforcementTestsInput,\n): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n //? Only login-required, state-changing routes are CSRF-protected. A missing\n //? meta entry means we can't know the auth requirement — skip silently here\n //? (the auth layer already surfaces the stale-map gap).\n if (!hasMetaEntry(input.apiMetaMap, endpoint)) continue;\n if (!requiresLogin(input.apiMetaMap, endpoint)) continue;\n if (!STATE_CHANGING_METHODS.has(endpoint.method)) continue;\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runCsrfEnforcementCheck({\n endpoint,\n baseUrl: input.baseUrl,\n authCookie: input.authCookie,\n inputFor: input.inputFor,\n requestTimeoutMs: input.requestTimeoutMs,\n expectedErrorCode: input.expectedErrorCode,\n expectedHttpStatus: input.expectedHttpStatus,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import type { ContractCheckResult, EndpointDescriptor } from './types';\nimport { sendProbe } from './probeRequest';\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\n\n//? Lightweight fuzz layer — no Zod yet. Sends well-known junk shapes and\n//? asserts the server doesn't 5xx or return a non-envelope body. Real\n//? schema-driven fuzzing (TS type -> random valid input) is deferred until\n//? the generator emits Zod schemas alongside the types.\nconst JUNK_PAYLOADS: readonly unknown[] = [\n null,\n [],\n [1, 2, 3],\n 'string-instead-of-object',\n 1_234_567_890,\n true,\n { nested: { deeply: { nested: { value: 'x'.repeat(10_000) } } } },\n //? `{ __proto__: ... }` serializes to `{}` via JSON.stringify (prototype keys\n //? are not own-enumerable), so it would probe nothing. Use `constructor` instead\n //? — a named own-key that some parsers misinterpret but survives JSON round-trip.\n { constructor: { polluted: true } },\n { key: null, other: undefined, third: Number.NaN },\n];\n\nexport interface FuzzCheckInput {\n endpoint: EndpointDescriptor;\n baseUrl: string;\n headers?: Record<string, string>;\n /** Per-call request timeout in ms. Defaults to 5000. */\n requestTimeoutMs?: number;\n}\n\nexport const runFuzzCheck = async (input: FuzzCheckInput): Promise<ContractCheckResult> => {\n const started = Date.now();\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = `${input.baseUrl.replace(/\\/$/, '')}/${input.endpoint.fullPath}`;\n\n for (const payload of JUNK_PAYLOADS) {\n const result = await sendProbe({\n url,\n method: input.endpoint.method,\n baseUrl: input.baseUrl,\n body: payload,\n headers: input.headers,\n requestTimeoutMs,\n });\n const durationMs = Date.now() - started;\n\n if (!result) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n reason: `fuzz probe crashed (no response) with payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n //? A 5xx that STILL returns a valid `{ status:'error', errorCode }` envelope\n //? is the framework gracefully catching an error (e.g. a route designed to\n //? throw), not a crash. The fuzz layer's concern is crashes / hangs /\n //? garbage — so we only fail on a non-envelope body below (which catches raw\n //? 5xx, HTML error pages, and truncated responses). A controlled 500\n //? envelope passes.\n if (!result.parsed || (result.parsed.status !== 'success' && result.parsed.status !== 'error')) {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz payload produced non-envelope response: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n\n if (result.parsed.status === 'error' && typeof result.parsed.errorCode !== 'string') {\n return {\n endpoint: input.endpoint,\n status: 'fail',\n httpStatus: result.httpStatus,\n reason: `fuzz error response missing errorCode for payload: ${JSON.stringify(payload).slice(0, 80)}`,\n durationMs,\n };\n }\n }\n\n return {\n endpoint: input.endpoint,\n status: 'pass',\n durationMs: Date.now() - started,\n };\n};\n","import { walkEndpoints } from './walkEndpoints';\nimport { runFuzzCheck } from './fuzzCheck';\nimport { shouldSkip, calculateSummary, STATE_CHANGING_METHODS } from './testLayerHelpers';\nimport type { ApiMethodMap, ContractCheckResult, RunContractSummary } from './types';\n\nexport interface RunFuzzTestsInput {\n apiMethodMap: ApiMethodMap;\n baseUrl: string;\n skip?: string[];\n headers?: Record<string, string>;\n onResult?: (result: ContractCheckResult) => void;\n}\n\nexport const runFuzzTests = async (input: RunFuzzTestsInput): Promise<RunContractSummary> => {\n const endpoints = walkEndpoints(input.apiMethodMap);\n const skip = input.skip ?? [];\n //? In cookie-mode (authenticated sweep) junk bodies sent to state-changing\n //? routes (POST/PUT/DELETE) bypass the body-ignore short-circuit and reach real\n //? mutation handlers — risking unintended side-effects on the test database.\n //? Skip those endpoints here; the contract layer already validates their shape\n //? with well-formed input, and the CSRF layer probes their auth guard separately.\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\n const results: ContractCheckResult[] = [];\n\n for (const endpoint of endpoints) {\n if (isAuthenticatedSweep && STATE_CHANGING_METHODS.has(endpoint.method)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'state-changing route skipped in cookie-mode fuzz sweep (mutation risk with junk bodies)',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n if (shouldSkip(endpoint, skip)) {\n const skipped: ContractCheckResult = {\n endpoint,\n status: 'skipped',\n durationMs: 0,\n reason: 'Explicitly skipped',\n };\n results.push(skipped);\n input.onResult?.(skipped);\n continue;\n }\n\n const result = await runFuzzCheck({\n endpoint,\n baseUrl: input.baseUrl,\n headers: input.headers,\n });\n results.push(result);\n input.onResult?.(result);\n }\n\n return calculateSummary(results);\n};\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst isProcessRunning = (pid: unknown): boolean => {\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return error instanceof Error && 'code' in error && error.code === 'EPERM';\n }\n};\n\nexport interface ResolveTestBaseUrlOptions {\n /** Project root containing node_modules/.luckystack/dev-server.json. */\n cwd?: string;\n /** Used when no explicit override or valid live advertisement exists. */\n fallbackUrl?: string;\n}\n\n//? Shared by the framework repo and generated consumers so a dev backend that\n//? auto-increments off its configured port does not turn connection failures\n//? against the stale port into false test failures.\nexport const resolveTestBaseUrl = (\n options: ResolveTestBaseUrlOptions = {},\n): string => {\n const explicit = process.env.TEST_BASE_URL;\n if (explicit) return explicit;\n\n try {\n const raw = fs.readFileSync(\n path.join(options.cwd ?? process.cwd(), 'node_modules', '.luckystack', 'dev-server.json'),\n 'utf8',\n );\n const info = JSON.parse(raw) as { port?: unknown; pid?: unknown };\n if (\n typeof info.port === 'number'\n && Number.isInteger(info.port)\n && info.port > 0\n && info.port <= 65_535\n && isProcessRunning(info.pid)\n ) {\n return `http://localhost:${String(info.port)}`;\n }\n } catch {\n //? No live dev advertisement — use the caller's config-derived fallback.\n }\n\n return options.fallbackUrl ?? 'http://localhost:80';\n};\n","//? @adr 0032 — test-process env/secret bootstrap through lazy consumer config.\n//? Test-process bootstrap for environment-backed integrations. The live server\n//? resolves secret-manager pointers in its own process, but Layer-5 tests also\n//? use Prisma/Redis directly inside the runner process. That process therefore\n//? needs the same env prefix BEFORE custom test modules are imported.\n\nimport { loadEnvFiles, tryCatch } from '@luckystack/core';\n\ntype MaybePromise<T> = T | Promise<T>;\n\ninterface SecretManagerConfigLike {\n url: string;\n token: string | { fromFile: string };\n [key: string]: unknown;\n}\n\nexport interface ResolveTestEnvironmentInput {\n /**\n * Loads the consumer's default-exported `config.ts` object after `.env` files\n * are available. Keeping this as a callback avoids hardcoding a consumer path\n * inside the package and keeps `@luckystack/secret-manager` optional.\n */\n loadProjectConfig?: () => MaybePromise<unknown>;\n}\n\ninterface RequiredProjectConfigLoader {\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n}\n\n/** @internal Runtime guard for untyped callers of the public orchestrators. */\nexport const assertProjectConfigLoader: (\n input: unknown,\n caller: 'runAllTests' | 'runCustomTests',\n) => asserts input is RequiredProjectConfigLoader = (input, caller) => {\n if (\n typeof input !== 'object'\n || input === null\n || !('loadProjectConfig' in input)\n || typeof input.loadProjectConfig !== 'function'\n ) {\n throw new TypeError(`[test-runner] ${caller} requires a lazy loadProjectConfig callback`);\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isToken = (value: unknown): value is SecretManagerConfigLike['token'] =>\n typeof value === 'string'\n || (isRecord(value) && typeof value.fromFile === 'string');\n\nconst readSecretManagerConfig = (projectConfig: unknown): SecretManagerConfigLike | null => {\n if (!isRecord(projectConfig)) return null;\n const candidate = projectConfig.secretManager;\n if (!isRecord(candidate)) return null;\n\n const url = candidate.url;\n if (url === undefined || url === '') return null;\n if (typeof url !== 'string') {\n throw new TypeError('[test-runner] config.secretManager.url must be a string.');\n }\n if (!isToken(candidate.token)) {\n throw new TypeError(\n '[test-runner] config.secretManager.token must be a string or { fromFile: string }.',\n );\n }\n\n return { ...candidate, url, token: candidate.token };\n};\n\n/**\n * Load the project's env files and resolve optional secret-manager pointers for\n * the TEST process. Call before importing integration-test modules that touch a\n * database, Redis, or another env-backed SDK.\n */\nexport const resolveTestEnvironment = async (\n input: ResolveTestEnvironmentInput = {},\n): Promise<void> => {\n loadEnvFiles();\n if (!input.loadProjectConfig) return;\n\n const projectConfig = await input.loadProjectConfig();\n const secretManagerConfig = readSecretManagerConfig(projectConfig);\n if (!secretManagerConfig) return;\n\n const [importError, secretManager] = await tryCatch(\n () => import('@luckystack/secret-manager'),\n );\n if (importError || !secretManager) {\n throw new Error(\n '[test-runner] config.secretManager.url is set, but @luckystack/secret-manager '\n + 'could not be loaded. Install the package or remove the secret-manager config; '\n + 'tests will not continue with unresolved env pointers.',\n { cause: importError ?? undefined },\n );\n }\n\n await secretManager.initSecretManager({\n ...secretManagerConfig,\n //? Match the normal server/ORM bootstrap: configured remote resolution is\n //? fail-fast. A raw DATABASE_URL_V<n> must never leak into Prisma.\n source: 'remote',\n });\n};\n","import type { z } from 'zod';\n\n//? Options threaded through the whole walk so nested string fields get the\n//? same treatment as top-level ones.\nexport interface SampleSchemaOptions {\n /**\n * Marker prefix applied to generated UNCONSTRAINED string values so any rows\n * a sweep creates are identifiable + collision-free (finding #98). Strings\n * that carry a format (email/url/uuid/datetime/…) or a check (min/max/\n * length/regex) are left as-is, since prefixing could break the route's own\n * validation and defeat the contract layer's \"valid input\" guarantee.\n */\n stringPrefix?: string;\n}\n\n//? Walk a Zod schema and produce a minimal valid value. Used by the\n//? contract/fuzz runners so \"valid input\" tests don't require hand-written\n//? fixtures per endpoint. Handles the shapes the generator emits\n//? (primitives, literals, unions, optionals, objects, records, arrays).\n//?\n//? Not a property-based generator — returns the SAME deterministic value\n//? every call. For randomized fuzz, swap to `fast-check` in a later session.\n\n//? zod 4 renamed the internal def accessor from `._def` to `.def`; recurse\n//? through whichever a child node exposes so the walker handles both versions.\nconst innerDef = (node: { _def?: unknown; def?: unknown } | undefined): unknown =>\n node ? (node.def ?? node._def) : undefined;\n\nconst sampleForDef = (def: unknown, options: SampleSchemaOptions | undefined): unknown => {\n //? Zod stores its runtime shape on `_def.type` (v3) or `_def.typeName`.\n //? Access is untyped because `def` can be any of dozens of internal shapes.\n const d = def as {\n typeName?: string;\n type?: string;\n innerType?: { _def?: unknown; def?: unknown };\n values?: unknown[] | Record<string, unknown>;\n entries?: Record<string, unknown>;\n value?: unknown;\n options?: { _def?: unknown; def?: unknown }[];\n shape?: (() => Record<string, { _def?: unknown; def?: unknown }>) | Record<string, { _def?: unknown; def?: unknown }>;\n valueType?: { _def: unknown };\n element?: { _def: unknown };\n items?: { _def?: unknown; def?: unknown }[];\n schema?: { _def?: unknown; def?: unknown };\n in?: { _def?: unknown; def?: unknown };\n //? String-constraint markers. zod 4 sets `format` + `check: 'string_format'`\n //? for email/url/uuid/datetime/cuid/… and a `checks` array for min/max/\n //? length/regex; zod 3 puts the lot on `checks`. A bare string has none.\n format?: string;\n check?: string;\n checks?: unknown[];\n };\n\n const name = d.typeName ?? d.type;\n\n switch (name) {\n case 'ZodString':\n case 'string': {\n //? Prefix only UNCONSTRAINED strings with the caller's test-data marker\n //? (finding #98). A format (email/url/uuid/datetime/…) or any check\n //? (min/max/length/regex) means a prefixed/lengthened value could fail the\n //? route's own Zod, turning the happy-path probe into a validation-error\n //? test — so those keep the plain `'test'`.\n const isConstrained = Boolean(d.format)\n || d.check === 'string_format'\n || (Array.isArray(d.checks) && d.checks.length > 0);\n if (options?.stringPrefix && !isConstrained) return `${options.stringPrefix}test`;\n return 'test';\n }\n case 'ZodNumber':\n case 'number': {\n return 0;\n }\n case 'ZodBoolean':\n case 'boolean': {\n return false;\n }\n case 'ZodNull':\n case 'null': {\n return null;\n }\n case 'ZodUndefined':\n case 'undefined': {\n return undefined;\n }\n case 'ZodAny':\n case 'any':\n case 'ZodUnknown':\n case 'unknown': {\n return null;\n }\n case 'ZodLiteral':\n case 'literal': {\n //? zod 3 stores the literal on `_def.value`; zod 4 stores `_def.values` (array).\n return d.value ?? (Array.isArray(d.values) ? d.values[0] : undefined);\n }\n case 'ZodOptional':\n case 'optional':\n case 'ZodNullable':\n case 'nullable': {\n //? Omitting optional fields is the most conservative valid input.\n return undefined;\n }\n case 'ZodEnum':\n case 'enum':\n case 'ZodNativeEnum':\n case 'nativeEnum': {\n //? Return the first declared value so an enum-typed required field gets a\n //? VALID member rather than `null` (which the route's own Zod rejects,\n //? silently turning the contract probe into a validation-error test).\n //? zod 3 stores members on `_def.values` (array); zod 4 on `_def.entries`\n //? (object) or `_def.values` (Set/array).\n if (Array.isArray(d.values)) return d.values[0];\n const entries = d.entries ?? (d.values && !Array.isArray(d.values) ? d.values : undefined);\n if (entries) {\n const first = Object.values(entries)[0];\n if (first !== undefined) return first;\n }\n return undefined;\n }\n case 'ZodDefault':\n case 'default':\n case 'ZodCatch':\n case 'catch': {\n //? Unwrap to the inner schema's sample (the field is still required at the\n //? type level; the default only applies when omitted).\n return sampleForDef(innerDef(d.innerType), options);\n }\n case 'ZodEffects':\n case 'effects':\n case 'ZodPipeline':\n case 'pipe':\n case 'ZodBranded':\n case 'branded':\n case 'ZodReadonly':\n case 'readonly': {\n //? `.refine`/`.transform`/`.pipe`/`.brand`/`.readonly` wrap an inner\n //? schema — recurse into it. A `.refine` may still reject the sample, but\n //? recursing yields a structurally-valid base far more often than `null`.\n return sampleForDef(innerDef(d.schema ?? d.in ?? d.innerType), options);\n }\n case 'ZodTuple':\n case 'tuple': {\n const items = d.items ?? [];\n return items.map(item => sampleForDef(innerDef(item), options));\n }\n case 'ZodUnion':\n case 'union':\n case 'ZodDiscriminatedUnion':\n case 'discriminatedUnion': {\n return d.options && d.options.length > 0 && d.options[0] ? sampleForDef(innerDef(d.options[0]), options) : undefined;\n }\n case 'ZodArray':\n case 'array': {\n return [];\n }\n case 'ZodObject':\n case 'object': {\n const out: Record<string, unknown> = {};\n //? zod 3 exposes `_def.shape` as a function; zod 4 as a plain object.\n const shape = typeof d.shape === 'function' ? d.shape() : (d.shape ?? {});\n for (const [key, valueSchema] of Object.entries(shape)) {\n const sample = sampleForDef(innerDef(valueSchema), options);\n if (sample !== undefined) out[key] = sample;\n }\n return out;\n }\n case 'ZodRecord':\n case 'record': {\n return {};\n }\n case 'ZodDate':\n case 'date': {\n return new Date().toISOString();\n }\n default: {\n //? Unknown node: OMIT (undefined) rather than emit `null`. A `null` for a\n //? required field of an unhandled type makes the route's own Zod reject\n //? the body, silently turning the \"happy path\" contract probe into a\n //? validation-error test. Omitting lets the object walker drop the key so\n //? the probe only fails if the field was genuinely required (a real,\n //? visible signal) instead of always.\n return undefined;\n }\n }\n};\n\nexport const sampleSchemaInput = (schema: z.ZodType, options?: SampleSchemaOptions): unknown => {\n //? zod 4 renamed the public def accessor from `._def` to `.def`; fall back to\n //? `._def` (zod 3) so the top-level entry handles both, matching the\n //? recursive `innerDef` helper.\n const node = schema as { def?: unknown; _def?: unknown };\n return sampleForDef(node.def ?? node._def, options);\n};\n","import { randomUUID } from 'node:crypto';\n\n//? Contract-test mutation safety (finding #98). The contract sweep posts\n//? generated sample payloads at every endpoint, which can create real rows.\n//? Tagging generated string values with a recognizable, run-unique marker\n//? makes any such row identifiable (so the reset bookends — and a human\n//? scanning the DB — can tell test data from real data) and collision-free\n//? (no chance a generated value matches an existing real record's field).\n\n//? Fixed leading token every generated test string starts with. Stable across\n//? runs so consumers / DB scans can match `lstest_*` to spot stray test data.\nexport const TEST_DATA_PREFIX = 'lstest_';\n\n//? Build a run-unique marker like `lstest_a1b2c3d4_`. The short uuid segment\n//? keeps two concurrent runs from generating colliding values while staying\n//? short enough not to blow past typical string length limits.\nexport const createTestDataMarker = (): string => `${TEST_DATA_PREFIX}${randomUUID().slice(0, 8)}_`;\n","import type { ContractCheckResult, RunContractSummary } from './types';\n\nconst statusIcon = (status: ContractCheckResult['status']): string => {\n switch (status) {\n case 'pass': { return '[PASS]';\n }\n case 'fail': { return '[FAIL]';\n }\n case 'skipped': { return '[SKIP]';\n }\n }\n};\n\nexport const logContractResult = (result: ContractCheckResult): void => {\n const { endpoint, durationMs, httpStatus, responseStatus, errorCode, reason } = result;\n const icon = statusIcon(result.status);\n const core = `${icon} ${endpoint.method} /${endpoint.fullPath} ${durationMs}ms`;\n\n if (result.status === 'pass') {\n const tail = responseStatus === 'error' ? ` (error: ${errorCode ?? 'unknown'})` : '';\n console.log(`${core} http=${httpStatus ?? '-'}${tail}`);\n return;\n }\n\n if (result.status === 'skipped') {\n console.log(`${core} ${reason ?? ''}`);\n return;\n }\n\n console.log(`${core} http=${httpStatus ?? '-'} reason=${reason ?? '(none)'}`);\n};\n\nexport const logContractSummary = (summary: RunContractSummary): void => {\n const { total, passed, failed, skipped } = summary;\n console.log('');\n console.log(`Contract tests: ${passed}/${total} passed, ${failed} failed, ${skipped} skipped`);\n\n if (failed > 0) {\n console.log('');\n console.log('Failures:');\n for (const result of summary.results) {\n if (result.status !== 'fail') continue;\n console.log(` - /${result.endpoint.fullPath}: ${result.reason ?? '(no reason)'}`);\n }\n }\n};\n","//? Per-route business-logic test layer. Discovers `*.tests.ts` files\n//? alongside `_api/` and `_sync/` route source, dynamic-imports each,\n//? builds a route-bound `TestContext`, runs the exported `customTests`\n//? cases. Spec: docs/ARCHITECTURE_TESTING.md.\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n buildRoutedGetUrl,\n getCsrfConfig,\n getPrismaClient,\n getProjectConfig,\n getSrcDir,\n tryCatch,\n tryCatchSync,\n} from '@luckystack/core';\n\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport {\n openStreamWatcher,\n type StreamChunkFrame,\n type StreamWatcher,\n} from './streamWatcher';\n//? `ApiMethodMap` mirrors the generated `apiMethodMap` shape (page → name →\n//? version → method) and is a type-only import, so there's still no import-time\n//? coupling to the consumer's generated artifact.\nimport type { ApiMethodMap } from './types';\n\nconst API_TEST_FILE_PATTERN = /_v(\\d+)\\.tests\\.ts$/;\nconst SYNC_SERVER_TEST_FILE_PATTERN = /_server_v(\\d+)\\.tests\\.ts$/;\n\n//? Default per-request timeout for the Layer-5 call helpers. Mirrors the sweep\n//? layers so a hung (non-erroring) server can't wedge the whole custom-test run\n//? indefinitely — without it the case's own tryCatch never fires.\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000;\n\nexport interface CustomTestCase {\n name: string;\n run: (ctx: TestContext) => Promise<void>;\n /**\n * Mark this case as a KNOWN, accepted failure (e.g. a documented bug not\n * yet fixed, or a scenario the current implementation cannot satisfy). The\n * string is the reason shown in the report. A case marked `expectedToFail`\n * that throws is reported as `xfail` — counted separately, NOT a red\n * failure. If it unexpectedly PASSES it is reported as `xpass` so the stale\n * marker can be removed. Leave undefined for normal cases.\n */\n expectedToFail?: string;\n}\n\nexport interface CustomTestResult {\n routePath: string;\n caseName: string;\n //? `xfail` = marked expectedToFail and failed as expected (known issue).\n //? `xpass` = marked expectedToFail but passed (remove the marker).\n status: 'pass' | 'fail' | 'xfail' | 'xpass';\n durationMs: number;\n reason?: string;\n /** Documented reason when the case carried an `expectedToFail` marker. */\n expectedToFail?: string;\n}\n\nexport interface RunCustomTestsSummary {\n total: number;\n passed: number;\n failed: number;\n /** Cases marked `expectedToFail` that failed as expected (known issues). */\n xfailed: number;\n /** Cases marked `expectedToFail` that unexpectedly passed (remove the marker). */\n xpassed: number;\n results: CustomTestResult[];\n}\n\nexport interface RunCustomTestsInput {\n baseUrl: string;\n /** Optional cookie name for the session token. Defaults to the project's configured cookie. */\n sessionCookieName?: string;\n /** Filter to a subset of routes — substring match against `<page>/<name>/<version>`. */\n filter?: string;\n /**\n * Generated `apiMethodMap` so the harness sends each API route its declared\n * HTTP method (e.g. logout is DELETE). Falls back to POST when absent.\n */\n apiMethodMap?: ApiMethodMap;\n /**\n * Lazy consumer config for direct Layer-5 runs. Required so the public path\n * cannot silently skip a configured secret manager; `runAllTests` prepares\n * the environment separately before using the internal Layer-5 entrypoint.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n onResult?: (result: CustomTestResult) => void;\n}\n\ntype PreparedRunCustomTestsInput = Omit<RunCustomTestsInput, 'loadProjectConfig'>;\n\ninterface DiscoveredTestFile {\n /** Absolute path to the test file. */\n filePath: string;\n /** `api` or `sync` — which transport this route uses. */\n kind: 'api' | 'sync';\n /** Route descriptor parsed from the filename + directory location. */\n route: { page: string; name: string; version: string };\n}\n\n//? Walk `src/` recursively. A file matches when its directory contains an\n//? `_api/` or `_sync/` segment AND the filename matches the version suffix\n//? pattern. The route's `page` is everything between `src/` and the marker\n//? segment, joined with `/`. Nested page folders are supported (e.g.\n//? `src/admin/users/_api/getUser_v1.tests.ts` → page `admin/users`).\nexport const discoverCustomTestFiles = (srcDir: string = getSrcDir()): DiscoveredTestFile[] => {\n if (!fs.existsSync(srcDir)) return [];\n const found: DiscoveredTestFile[] = [];\n const stack: string[] = [srcDir];\n //? Track visited real-paths so a cyclic symlink under `src/` can't drive an\n //? infinite walk / unbounded stack growth.\n const visited = new Set<string>();\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined) break;\n //? Resolve to the canonical path and skip if already seen (symlink cycle).\n const [realError, realPath] = tryCatchSync(() => fs.realpathSync(current));\n const canonical = realError || realPath === null ? current : realPath;\n if (visited.has(canonical)) continue;\n visited.add(canonical);\n //? Guard the readdir: a permission error or a directory deleted mid-walk\n //? throws synchronously and would otherwise abort the whole custom layer.\n //? Skip the unreadable directory instead.\n const [readError, entries] = tryCatchSync(() => fs.readdirSync(current, { withFileTypes: true }));\n if (readError || entries === null) continue;\n for (const entry of entries) {\n const fullPath = path.join(current, entry.name);\n //? Skip symlinked entries entirely — following them risks a cycle and\n //? a discovered test file should live in the real source tree.\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n stack.push(fullPath);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.tests.ts')) continue;\n const parentDir = path.basename(current);\n if (parentDir !== '_api' && parentDir !== '_sync') continue;\n const pattern = parentDir === '_api' ? API_TEST_FILE_PATTERN : SYNC_SERVER_TEST_FILE_PATTERN;\n const versionMatch = pattern.exec(entry.name);\n if (!versionMatch) continue;\n const version = `v${versionMatch[1]}`;\n const namePart = entry.name.replace(parentDir === '_api' ? /_v\\d+\\.tests\\.ts$/ : /_server_v\\d+\\.tests\\.ts$/, '');\n const pageDir = path.dirname(current);\n //? Top-level `src/_api/` (no page folder) routes under the framework's\n //? `system` page — mirror that, otherwise an empty page produces the\n //? malformed URL `/api//logout/v1` and the call 404s.\n const relativePage = path.relative(srcDir, pageDir).replaceAll('\\\\', '/') || 'system';\n found.push({\n filePath: fullPath,\n kind: parentDir === '_api' ? 'api' : 'sync',\n route: { page: relativePage, name: namePart, version },\n });\n }\n }\n return found.toSorted((a, b) => a.filePath.localeCompare(b.filePath));\n};\n\n//? Test fixture shapes. The TestContext is built per route — each per-route\n//? test file's `customTests` cases receive a context already bound to the\n//? route the file lives next to, so authors don't repeat `page/name/version`.\nexport interface TestContext {\n /**\n * Invoke the route under test. Page / name / version are baked in.\n *\n * `input` travels exactly like the real client sends it: as the JSON body\n * for POST / PUT / DELETE routes, and as the `__luckystack_data` query-string\n * value for GET routes (a GET cannot carry a body). Either way the server\n * validates it as the route's `data`, so a GET route with required fields\n * receives them.\n */\n //? Two type parameters by design — `TInput` lets callers document the\n //? typed shape they're sending even though we don't validate against it\n //? here (the route's own runtime validation does). The ESLint rule\n //? otherwise flags it as \"used once\"; the documentation value is real.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callApi: <TInput = unknown, TOutput = unknown>(input: TInput) => Promise<TOutput>;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: { receiver?: string }) => Promise<TOutput>;\n /**\n * Open a second socket connection that joins `roomCode` as a pure\n * subscriber and exposes the incoming chunk stream for assertions.\n *\n * The route is auto-derived from the test file location, so a watcher\n * only surfaces chunks for the route this test belongs to. Multiple\n * watchers per test are allowed; all are auto-closed when the test\n * case finishes (whether it passes or throws).\n */\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\n watchStream: <TChunk extends StreamChunkFrame = StreamChunkFrame>(roomCode: string) => Promise<StreamWatcher<TChunk>>;\n session: {\n login: (user?: { email?: string; id?: string; name?: string }) => Promise<{ token: string; userId: string }>;\n logout: () => Promise<void>;\n current: () => { token: string | null; userId: string | null };\n };\n prisma: ReturnType<typeof getPrismaClient>;\n expect: TestExpect;\n}\n\nexport interface TestExpect {\n eq: <T>(actual: T, expected: T, message?: string) => void;\n ok: (value: unknown, message?: string) => void;\n throws: (fn: () => Promise<unknown>, message?: string) => Promise<Error>;\n matches: (value: string, pattern: RegExp, message?: string) => void;\n}\n\nclass AssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AssertionError';\n }\n}\n\nconst buildExpect = (): TestExpect => ({\n eq: (actual, expected, message) => {\n const ok = Object.is(actual, expected)\n || (typeof actual === 'object' && typeof expected === 'object' && JSON.stringify(actual) === JSON.stringify(expected));\n if (!ok) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);\n }\n },\n ok: (value, message) => {\n if (!value) throw new AssertionError(message ?? `expected truthy value, got ${JSON.stringify(value)}`);\n },\n throws: async (fn, message) => {\n const [err] = await tryCatch(fn);\n if (!err) throw new AssertionError(message ?? 'expected fn to throw, but it did not');\n return err;\n },\n matches: (value, pattern, message) => {\n if (!pattern.test(value)) {\n const prefix = message ? `${message}: ` : '';\n throw new AssertionError(`${prefix}\"${value}\" does not match ${pattern}`);\n }\n },\n});\n\ninterface TestSessionState {\n token: string | null;\n userId: string | null;\n //? CSRF token minted by saveSession on login. Sent on authenticated\n //? state-changing requests so the server's CSRF middleware doesn't 403.\n csrfToken: string | null;\n //? Raw JSON body of the most recent callApi / callSync response. Surfaced in\n //? a failed case's reason so the report shows the server's actual errorCode\n //? (e.g. `api.rateLimitExceeded`) instead of only the assertion text.\n lastResponse: unknown;\n}\n\n//? Pull the server's errorCode (or non-success status) out of the last HTTP\n//? response so a failure's reason shows WHY the server rejected the call, not\n//? just the assertion text. Returns undefined for success / non-envelope bodies.\nconst extractErrorCode = (response: unknown): string | undefined => {\n if (!response || typeof response !== 'object') return undefined;\n const r = response as { errorCode?: unknown; status?: unknown };\n if (typeof r.errorCode === 'string' && r.errorCode.length > 0) return r.errorCode;\n if (typeof r.status === 'string' && r.status !== 'success') return r.status;\n return undefined;\n};\n\n//? Minimal session login: mints a session in Redis via the consumer's\n//? registered SessionAdapter (from `@luckystack/login`). Deliberately does\n//? NOT create the Prisma user record — User-table shape varies per\n//? project. If your test needs a real user row, create it via\n//? `ctx.prisma.user.create(...)` first, then pass `{ id, email }` to\n//? `session.login()`.\nconst buildSessionHelpers = (state: TestSessionState): TestContext['session'] => {\n return {\n login: async (user) => {\n const { saveSession, getSession } = await import('@luckystack/login');\n const userId = user?.id ?? `test-user-${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;\n const email = user?.email ?? `test-${Date.now()}-${crypto.randomBytes(8).toString('hex')}@example.com`;\n const name = user?.name ?? 'Test User';\n const token = `test-${Date.now()}-${crypto.randomBytes(16).toString('hex')}`;\n const sessionData = {\n id: userId,\n email,\n name,\n token,\n provider: 'credentials' as const,\n };\n await saveSession(token, sessionData, true);\n state.token = token;\n state.userId = userId;\n //? Read back the CSRF token saveSession minted so authenticated\n //? state-changing requests can pass the server's CSRF middleware.\n const persisted = await getSession(token);\n state.csrfToken = persisted?.csrfToken ?? null;\n return { token, userId };\n },\n logout: async () => {\n if (!state.token) return;\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(state.token);\n state.token = null;\n state.userId = null;\n state.csrfToken = null;\n },\n current: () => ({ token: state.token, userId: state.userId }),\n };\n};\n\n//? Parse a fetch Response as JSON behind tryCatch and store the raw body on the\n//? session state. A non-JSON body (HTML 500, empty 204, truncated) would\n//? otherwise throw a raw SyntaxError with `state.lastResponse` null, hiding the\n//? server's actual output. On parse failure we keep a snippet of the text body\n//? so a failed case's reason can show what the server actually returned.\nconst parseResponse = async (response: Response, state: TestSessionState): Promise<unknown> => {\n const [textError, text] = await tryCatch(() => response.text());\n if (textError || text === null) {\n state.lastResponse = null;\n throw new Error(`failed to read response body (HTTP ${response.status}): ${textError?.message ?? 'unknown'}`);\n }\n const [parseError, parsed] = tryCatchSync(() => JSON.parse(text) as unknown);\n if (parseError) {\n const snippet = text.length > 500 ? `${text.slice(0, 500)}…` : text;\n state.lastResponse = snippet;\n throw new Error(`response was not valid JSON (HTTP ${response.status}): ${snippet}`);\n }\n state.lastResponse = parsed;\n return parsed;\n};\n\n/** @internal Exported for the unit test only; consumers use `TestContext.callApi`. */\nexport const buildCallApi = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n //? The route's declared HTTP method (from the generated apiMethodMap). The\n //? server validates the method, so DELETE/PUT routes (e.g. logout) 405 if we\n //? always sent POST.\n method: string,\n): TestContext['callApi'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callApi'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput): Promise<TOutput> => {\n const path = `${baseUrl.replace(/\\/$/, '')}/api/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const init: RequestInit = { method, headers, signal: controller.signal };\n //? GET / HEAD requests cannot carry a body (fetch throws), so the payload\n //? rides the query string under the same reserved key the real client\n //? uses and the server reads. Every other method sends the JSON body.\n const bodyless = method === 'GET' || method === 'HEAD';\n const url = bodyless ? buildRoutedGetUrl(path, input ?? {}) : path;\n if (!bodyless) init.body = JSON.stringify(input ?? {});\n const [fetchError, response] = await tryCatch(() => fetch(url, init));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callApi ${method} ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? `parseResponse` returns `unknown`; TOutput is the caller-supplied expectation.\n //? The runtime shape is unverifiable here — the route's own validator owns that\n //? contract. The cast is required because parseResponse must return `unknown`.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, no-restricted-syntax -- parseResponse returns unknown; TOutput is the caller's type contract, verified at the call site\n return await parseResponse(response, state) as TOutput;\n };\n};\n\nconst buildCallSync = (\n baseUrl: string,\n routePath: string,\n state: TestSessionState,\n cookieName: string,\n): TestContext['callSync'] => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- must match TestContext['callSync'] generic signature\n return async <TInput = unknown, TOutput = unknown>(input: TInput, opts?: { receiver?: string }): Promise<TOutput> => {\n const url = `${baseUrl.replace(/\\/$/, '')}/sync/${routePath}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\n const body = { data: input ?? {}, receiver: opts?.receiver };\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, DEFAULT_CALL_TIMEOUT_MS);\n const [fetchError, response] = await tryCatch(() => fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n if (fetchError || !response) {\n state.lastResponse = null;\n throw new Error(`callSync ${routePath} failed: ${fetchError?.message ?? 'no response'}`);\n }\n //? parseResponse returns unknown; TOutput is the caller's type contract, verified at the call site\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, no-restricted-syntax -- parseResponse returns unknown; cast to TOutput is the boundary\n return await parseResponse(response, state) as TOutput;\n };\n};\n\ninterface BuiltContext {\n ctx: TestContext;\n /** Close every watcher opened during the case. Called by `runCustomTests` after each case. */\n closeAllWatchers: () => Promise<void>;\n /** Per-case session state — read after the run to surface the last response's errorCode. */\n state: TestSessionState;\n}\n\nconst buildContext = (\n discovery: DiscoveredTestFile,\n input: PreparedRunCustomTestsInput,\n): BuiltContext => {\n //? Default to the PROJECT's configured session cookie name (the server reads\n //? the token from this cookie). Hardcoding a name silently breaks every\n //? authenticated custom test when the project uses a different name.\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n const routeFullName = `sync/${routePath}`;\n //? API routes are invoked with their declared HTTP method; sync routes always\n //? POST. Default to POST when the map lacks the route (e.g. sync, or a map\n //? wasn't threaded through).\n const apiMethod = input.apiMethodMap?.[discovery.route.page]?.[discovery.route.name]?.[discovery.route.version] ?? 'POST';\n const state: TestSessionState = { token: null, userId: null, csrfToken: null, lastResponse: null };\n\n //? Watchers are tracked per case so a missing explicit `close()` can't\n //? leak socket B between tests.\n const watchers: StreamWatcher[] = [];\n const watchStream = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n roomCode: string,\n ): Promise<StreamWatcher<TChunk>> => {\n const watcher = await openStreamWatcher<TChunk>({\n baseUrl: input.baseUrl,\n roomCode,\n token: state.token,\n routeFullName,\n });\n //? Track watchers under the base `StreamChunkFrame` shape; the\n //? returned reference keeps the caller-supplied generic.\n watchers.push(watcher);\n return watcher;\n };\n\n const closeAllWatchers = async (): Promise<void> => {\n while (watchers.length > 0) {\n const watcher = watchers.pop();\n if (!watcher) continue;\n const [closeError] = await tryCatch(() => watcher.close());\n if (closeError) {\n //? Swallow — closing in cleanup must never mask the original test\n //? failure. Surface via logger if a project wires one up.\n //? (Intentionally not using getLogger here to avoid an extra import.)\n }\n }\n };\n\n const ctx: TestContext = {\n callApi: buildCallApi(input.baseUrl, routePath, state, cookieName, apiMethod),\n callSync: buildCallSync(input.baseUrl, routePath, state, cookieName),\n watchStream,\n session: buildSessionHelpers(state),\n //? Lazy: only resolves when a case actually touches `ctx.prisma`, so an\n //? orm:'none' project (no registered client) can still run DB-free custom\n //? tests — the eager resolve used to abort the whole custom-test phase.\n get prisma() {\n return getPrismaClient();\n },\n expect: buildExpect(),\n };\n\n return { ctx, closeAllWatchers, state };\n};\n\n/** @internal Environment is already prepared by `runAllTests`. */\nexport const runCustomTestsAfterEnvironmentPrepared = async (\n input: PreparedRunCustomTestsInput,\n): Promise<RunCustomTestsSummary> => {\n const srcDir = getSrcDir();\n const files = discoverCustomTestFiles(srcDir);\n const results: CustomTestResult[] = [];\n\n for (const discovery of files) {\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\n if (input.filter && !routePath.includes(input.filter)) continue;\n\n //? Canonical-path containment guard: resolve to the real path and verify\n //? it is still inside srcDir. Protects against symlink-escaped paths or\n //? an adversarially crafted filePath that slipped through the walker.\n const [realPathErr, realFilePath] = tryCatchSync(() => fs.realpathSync(discovery.filePath));\n const resolvedPath = realPathErr ? discovery.filePath : (realFilePath ?? discovery.filePath);\n const relToSrc = path.relative(srcDir, resolvedPath);\n if (relToSrc.startsWith('..') || path.isAbsolute(relToSrc)) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `test file path escapes srcDir and was rejected: ${discovery.filePath}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const [importError, mod] = await tryCatch(\n () => import(pathToFileURL(resolvedPath).href) as Promise<Record<string, unknown>>,\n );\n if (importError || !mod) {\n const r: CustomTestResult = {\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\n reason: `failed to import ${discovery.filePath}: ${importError?.message ?? 'unknown'}`,\n };\n results.push(r);\n input.onResult?.(r);\n continue;\n }\n\n const exported = (mod as { customTests?: unknown }).customTests;\n //? DD-TR-various — empty-stub contract (documented decision):\n //? A test file that exports an empty array (or no `customTests` export) is\n //? silently skipped, NOT emitted as a `skipped` result. Rationale: AI-generated\n //? stub files start life empty; surfacing them as \"skipped\" every run adds noise\n //? before the author fills them in. The `CustomTestResult` status union therefore\n //? stays `'pass' | 'fail' | 'xfail' | 'xpass'` — no `'skipped'` variant.\n //? If you need to track unfilled stubs, scan `discoverCustomTestFiles()` and\n //? compare against the results array.\n if (!Array.isArray(exported) || exported.length === 0) {\n continue;\n }\n\n for (const rawCase of exported) {\n const c = rawCase as CustomTestCase;\n if (typeof c.name !== 'string' || typeof c.run !== 'function') continue;\n const expectedToFail = typeof c.expectedToFail === 'string' ? c.expectedToFail : undefined;\n const built = buildContext(discovery, input);\n const started = Date.now();\n const [runError] = await tryCatch(async () => c.run(built.ctx));\n //? Always close watchers regardless of pass/fail so the next case\n //? starts clean. tryCatch on the cleanup itself prevents a teardown\n //? throw from masking the original failure.\n await tryCatch(() => built.closeAllWatchers());\n //? Best-effort session cleanup: if `session.login()` minted a Redis\n //? session but the case didn't call `session.logout()`, the token\n //? accumulates until its TTL expires. Delete it now to avoid a slow\n //? session-key leak across a full sweep run.\n const residualToken = built.state.token;\n if (residualToken) {\n await tryCatch(async () => {\n const { deleteSession } = await import('@luckystack/login');\n await deleteSession(residualToken);\n });\n }\n const durationMs = Date.now() - started;\n\n let r: CustomTestResult;\n if (runError) {\n //? Augment the assertion message with the server's actual errorCode so\n //? the report explains the rejection, not just the failed expectation.\n const errorCode = extractErrorCode(built.state.lastResponse);\n const reason = errorCode ? `${runError.message} (server: ${errorCode})` : runError.message;\n r = expectedToFail\n ? { routePath, caseName: c.name, status: 'xfail', durationMs, reason, expectedToFail }\n : { routePath, caseName: c.name, status: 'fail', durationMs, reason };\n } else {\n r = expectedToFail\n ? {\n routePath, caseName: c.name, status: 'xpass', durationMs,\n reason: `marked expectedToFail (\"${expectedToFail}\") but passed — remove the marker`,\n expectedToFail,\n }\n : { routePath, caseName: c.name, status: 'pass', durationMs };\n }\n results.push(r);\n input.onResult?.(r);\n }\n }\n\n return {\n total: results.length,\n passed: results.filter(r => r.status === 'pass').length,\n failed: results.filter(r => r.status === 'fail').length,\n xfailed: results.filter(r => r.status === 'xfail').length,\n xpassed: results.filter(r => r.status === 'xpass').length,\n results,\n };\n};\n\n//? Public direct Layer-5 path: custom modules are imported only after env files\n//? and optional secret pointers resolve. The process-wide orchestrator calls the\n//? internal prepared function above, so it neither reloads pointers nor exposes\n//? a public `skipBootstrap` flag that callers could misuse accidentally.\nexport const runCustomTests = async (input: RunCustomTestsInput): Promise<RunCustomTestsSummary> => {\n assertProjectConfigLoader(input, 'runCustomTests');\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n return runCustomTestsAfterEnvironmentPrepared(input);\n};\n","//? Second-socket harness for Layer 5 custom tests. Opens a separate\n//? socket.io-client connection (socket B), joins the receiver room as a\n//? pure subscriber, and exposes the broadcast/streamTo chunk stream for\n//? assertions. Without this, `ctx.callSync` only surfaces the final\n//? envelope — chunks (LLM tokens, progress ticks) emitted via\n//? `broadcastStream` / `streamTo` flow on the `sync` event channel to\n//? room members other than the request originator's socket.\n//?\n//? Spec: see `docs/contract-tests.md` §watchStream and CLAUDE Rule 21\n//? (no `as unknown as X` / `as any` — typed generics throughout).\n//?\n//? Wire shape, sourced from `@luckystack/sync` `_shared/streamEmitters.ts`:\n//? io.to(receiver).emit('sync', { ...payload, cb, fullName, status: 'stream' })\n//? where `fullName` = `sync/<page>/<name>/<version>`. We filter incoming\n//? frames by that `fullName` so a watcher only sees chunks for the route\n//? its test file lives next to.\n\nimport { io as createSocketIoClient, Socket } from 'socket.io-client';\nimport {\n buildJoinRoomResponseEventName,\n socketEventNames,\n tryCatch,\n getProjectConfig,\n} from '@luckystack/core';\n\nexport interface StreamChunkFrame {\n /** Per-request correlation id stamped by the server. */\n cb?: string;\n /** Full route name (`sync/<page>/<name>/<version>`). */\n fullName?: string;\n /** Always `'stream'` for chunk frames. */\n status?: 'stream';\n /** Payload fields spread on top of the envelope. */\n [key: string]: unknown;\n}\n\nexport interface StreamWatcher<TChunk extends StreamChunkFrame = StreamChunkFrame> {\n /** Chunks received so far. Array reference is stable; entries are pushed in arrival order. */\n readonly chunks: TChunk[];\n /** Wait until `predicate(chunks)` returns true, or until timeout (default 5000ms). */\n stopAt(predicate: (chunks: TChunk[]) => boolean, timeoutMs?: number): Promise<void>;\n /** Wait until N total chunks received, or until timeout. */\n waitForCount(n: number, timeoutMs?: number): Promise<void>;\n /** Close socket B. Tests SHOULD call this in cleanup; ctx auto-closes all watchers when the test ends. */\n close(): Promise<void>;\n}\n\nexport interface OpenStreamWatcherInput {\n /** Server URL (same as `ctx`'s `baseUrl`). */\n baseUrl: string;\n /** Room to join as a pure subscriber. */\n roomCode: string;\n /** Active session token (used for `joinRoom` auth). May be null for routes that don't require auth. */\n token: string | null;\n /**\n * Resolved route name `sync/<page>/<name>/<version>` — chunk frames are\n * filtered by their `fullName` property so a watcher only surfaces chunks\n * for the route its test is bound to.\n */\n routeFullName: string;\n /** Default 5000ms — overridable per `stopAt` / `waitForCount` call. */\n defaultTimeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst JOIN_RESPONSE_TIMEOUT_MS = 3000;\nconst CONNECT_TIMEOUT_MS = 3000;\n\ninterface JoinRoomResponse {\n status?: string;\n errorCode?: string;\n}\n\nconst isJoinRoomResponse = (value: unknown): value is JoinRoomResponse =>\n typeof value === 'object' && value !== null;\n\nconst isStreamChunkFrame = (value: unknown): value is StreamChunkFrame =>\n typeof value === 'object' && value !== null;\n\n//? Monotonic counter — each watcher uses a fresh responseIndex so the\n//? `buildJoinRoomResponseEventName(...)` listener doesn't collide with a\n//? sibling watcher on the same process.\nlet responseIndexCounter = 0;\nconst nextResponseIndex = (): number => {\n responseIndexCounter += 1;\n return responseIndexCounter;\n};\n\nconst waitForConnect = (socket: Socket): Promise<void> => {\n return new Promise<void>((resolve, reject) => {\n if (socket.connected) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`watchStream: socket failed to connect within ${CONNECT_TIMEOUT_MS}ms`));\n }, CONNECT_TIMEOUT_MS);\n const onConnect = (): void => {\n cleanup();\n resolve();\n };\n const onConnectError = (err: Error): void => {\n cleanup();\n reject(new Error(`watchStream: connect_error — ${err.message}`));\n };\n const cleanup = (): void => {\n clearTimeout(timer);\n socket.off('connect', onConnect);\n socket.off('connect_error', onConnectError);\n };\n socket.on('connect', onConnect);\n socket.on('connect_error', onConnectError);\n });\n};\n\nconst waitForJoinAck = (socket: Socket, responseIndex: number): Promise<void> => {\n const eventName = buildJoinRoomResponseEventName(responseIndex);\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n socket.off(eventName, onAck);\n reject(new Error(`watchStream: joinRoom ack timeout after ${JOIN_RESPONSE_TIMEOUT_MS}ms`));\n }, JOIN_RESPONSE_TIMEOUT_MS);\n const onAck = (payload: unknown): void => {\n clearTimeout(timer);\n socket.off(eventName, onAck);\n if (isJoinRoomResponse(payload) && payload.status === 'error') {\n reject(new Error(`watchStream: joinRoom rejected — ${payload.errorCode ?? 'unknown'}`));\n return;\n }\n resolve();\n };\n socket.on(eventName, onAck);\n });\n};\n\nexport const openStreamWatcher = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\n input: OpenStreamWatcherInput,\n): Promise<StreamWatcher<TChunk>> => {\n const defaultTimeoutMs = input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;\n const chunks: TChunk[] = [];\n const waiters: { predicate: (chunks: TChunk[]) => boolean; resolve: () => void }[] = [];\n\n //? Authenticate the observer socket in BOTH token modes. `auth.token` is the\n //? sessionBasedToken (sessionStorage) path; in the DEFAULT cookie mode the\n //? server reads the token ONLY from the session cookie (extractTokenFromSocket\n //? ignores handshake.auth.token unless `acceptBearerInCookieMode`), so without\n //? the Cookie header the observer socket connects anonymously and every\n //? `joinRoom` is rejected with `auth.required`. In Node, socket.io-client honours\n //? `extraHeaders` on the websocket handshake, so we set the cookie there.\n const sessionCookieName = getProjectConfig().http.sessionCookieName;\n const socket: Socket = createSocketIoClient(input.baseUrl, {\n transports: ['websocket'],\n forceNew: true,\n reconnection: false,\n auth: input.token ? { token: input.token } : undefined,\n extraHeaders: input.token ? { Cookie: `${sessionCookieName}=${input.token}` } : undefined,\n });\n\n const handleChunk = (raw: unknown): void => {\n if (!isStreamChunkFrame(raw)) return;\n if (raw.status !== 'stream') return;\n if (raw.fullName !== input.routeFullName) return;\n //? Server emits chunk frames typed as `StreamChunkFrame` — caller's\n //? `TChunk` narrows the payload shape (e.g. `{ chunk: string }` for\n //? `streamBroadcast`). The runtime guards above confirm the wire shape;\n //? narrowing the static type is the test author's responsibility.\n const chunk = raw as TChunk;\n chunks.push(chunk);\n //? Drain waiters whose predicates are now satisfied. Iterate in reverse\n //? so we can splice without breaking indices.\n for (let i = waiters.length - 1; i >= 0; i--) {\n const waiter = waiters[i];\n if (!waiter) continue;\n if (waiter.predicate(chunks)) {\n waiters.splice(i, 1);\n waiter.resolve();\n }\n }\n };\n\n socket.on(socketEventNames.sync, handleChunk);\n\n //? On a connect timeout / connect_error the socket (and its `sync` listener)\n //? must be torn down — otherwise every failed attempt leaks one socket that\n //? is never auto-closed (the caller only tracks the watcher after this\n //? resolves). Mirror the join-ack cleanup path below.\n const [connectError] = await tryCatch(() => waitForConnect(socket));\n if (connectError) {\n socket.off(socketEventNames.sync, handleChunk);\n socket.disconnect();\n throw connectError;\n }\n\n const responseIndex = nextResponseIndex();\n socket.emit(socketEventNames.joinRoom, {\n group: input.roomCode,\n responseIndex,\n });\n const [joinError] = await tryCatch(() => waitForJoinAck(socket, responseIndex));\n if (joinError) {\n socket.off(socketEventNames.sync, handleChunk);\n socket.disconnect();\n throw joinError;\n }\n\n let closed = false;\n const close = (): Promise<void> => {\n if (closed) return Promise.resolve();\n closed = true;\n socket.off(socketEventNames.sync, handleChunk);\n //? Drain any outstanding waiters so callers don't hang past test end.\n for (const waiter of waiters) waiter.resolve();\n waiters.length = 0;\n socket.disconnect();\n return Promise.resolve();\n };\n\n const stopAt = (\n predicate: (chunks: TChunk[]) => boolean,\n timeoutMs: number = defaultTimeoutMs,\n ): Promise<void> => {\n if (predicate(chunks)) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const waiter = {\n predicate,\n resolve: () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n },\n };\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n const idx = waiters.indexOf(waiter);\n if (idx !== -1) waiters.splice(idx, 1);\n reject(new Error(\n `watchStream.stopAt: predicate not satisfied within ${timeoutMs}ms (received ${chunks.length} chunks)`,\n ));\n }, timeoutMs);\n waiters.push(waiter);\n });\n };\n\n const waitForCount = (n: number, timeoutMs: number = defaultTimeoutMs): Promise<void> => {\n return stopAt((current) => current.length >= n, timeoutMs);\n };\n\n return {\n chunks,\n stopAt,\n waitForCount,\n close,\n };\n};\n","//? Orchestrator — runs all five test layers (contract, auth-enforcement,\n//? rate-limit, fuzz, custom) against a running server and returns a single\n//? combined summary. Consumers call this from their own thin script that\n//? imports the generated `apiMethodMap` + `apiInputSchemas`.\n\nimport { clearAllRateLimits, getCsrfConfig, getProjectConfig } from '@luckystack/core';\n\nimport { sampleSchemaInput } from './schemaSampleInput';\nimport { createTestDataMarker } from './testDataMarker';\nimport { resetServerState } from './resetServerState';\nimport { runContractTests } from './runContractTests';\nimport { runAuthEnforcementTests } from './runAuthEnforcementTests';\nimport { runRateLimitTests } from './runRateLimitTests';\nimport { runCsrfEnforcementTests } from './runCsrfEnforcementTests';\nimport { runFuzzTests } from './runFuzzTests';\nimport { runCustomTestsAfterEnvironmentPrepared } from './customTests';\nimport {\n assertProjectConfigLoader,\n resolveTestEnvironment,\n type ResolveTestEnvironmentInput,\n} from './resolveTestEnvironment';\nimport { calculateSummary, LAYER_KEYS } from './testLayerHelpers';\nimport type { CustomTestResult, RunCustomTestsSummary } from './customTests';\nimport type { ApiMethodMap, ApiMetaMap, ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\nimport type { ZodType } from 'zod';\n\ntype ApiInputSchemas = Partial<Record<string, Partial<Record<string, Partial<Record<string, ZodType | undefined>>>>>>;\n\nexport interface RunAllTestsInput {\n apiMethodMap: ApiMethodMap;\n /**\n * Lazy loader for the consumer's default `config.ts` export. Required so\n * runAllTests cannot silently skip a configured secret manager; resolution\n * happens in THIS process before any layer or env-backed client is touched.\n */\n loadProjectConfig: NonNullable<ResolveTestEnvironmentInput['loadProjectConfig']>;\n apiMetaMap: ApiMetaMap;\n apiInputSchemas: ApiInputSchemas;\n baseUrl: string;\n /** Cookie name for the session token. Defaults to the project's configured session cookie name (`projectConfig.http.sessionCookieName`). */\n sessionCookieName?: string;\n /** Auth token applied as a `Cookie` header to sweep-layer requests that need a session. */\n authToken?: string;\n /** Endpoints to skip from the sweep layers, e.g. routes that need a file upload. */\n skip?: string[];\n /** Substring filter applied to `<page>/<name>/<version>`. */\n filter?: string;\n /** Disable individual layers (faster local iteration). */\n noSweep?: boolean;\n noFuzz?: boolean;\n noRateLimit?: boolean;\n /**\n * Disable the CSRF-enforcement layer. Defaults to ON (the layer runs) — but it\n * can only probe past the auth guard with a session, so it is a no-op unless an\n * `authToken` is supplied.\n */\n noCsrf?: boolean;\n noCustom?: boolean;\n /**\n * Token sent as `X-Test-Reset-Token` when the rate-limit layer resets the\n * shared bucket between endpoints. The server's `/_test/reset` requires\n * `TEST_RESET_TOKEN` to be set and matching — an unset/wrong token is 403, so\n * `resetBetweenEndpoints` would silently no-op. Defaults to\n * `process.env.TEST_RESET_TOKEN` so a CI run with the env set works with no\n * extra wiring; pass explicitly to override.\n */\n resetToken?: string;\n /**\n * Contract-test mutation safety (finding #98). When a reset token is\n * available, the sweep POSTs `/_test/reset` ONCE before the first layer\n * (clean slate — no real row the generated payloads could clobber) and ONCE\n * after the last (drop whatever the sweep created, so no test data persists).\n * Defaults to ON; set `false` to opt out. With no token the bookends are\n * silently skipped with a logged note (they can't run — `/_test/reset` 403s).\n */\n resetBookends?: boolean;\n}\n\nexport interface RunAllTestsSummary {\n contract?: RunContractSummary;\n auth?: RunContractSummary;\n rateLimit?: RunContractSummary;\n csrf?: RunContractSummary;\n fuzz?: RunContractSummary;\n custom?: RunCustomTestsSummary;\n totalPassed: number;\n totalFailed: number;\n}\n\nconst inputForEndpoint = (apiInputSchemas: ApiInputSchemas, stringPrefix: string) =>\n (endpoint: EndpointDescriptor): unknown => {\n const schema = apiInputSchemas[endpoint.page]?.[endpoint.name]?.[endpoint.version];\n //? Tag generated strings with the run-unique test-data marker so any rows\n //? the sweep creates are identifiable + collision-free (finding #98).\n return schema ? sampleSchemaInput(schema, { stringPrefix }) : {};\n };\n\n//? Decide whether the contract-sweep reset bookends should run. Returns the\n//? token to use, or null to skip. When the bookends are requested (the default)\n//? but no token is available, warn once — `/_test/reset` requires the token and\n//? would 403, so the bookends silently can't run; the sweep proceeds unguarded.\nconst resolveResetBookendToken = (input: RunAllTestsInput): string | null => {\n if (input.resetBookends === false) return null;\n const token = input.resetToken ?? process.env.TEST_RESET_TOKEN;\n if (!token) {\n console.warn(\n '[test-runner] runAllTests: reset bookends skipped — no reset token '\n + '(set TEST_RESET_TOKEN or pass resetToken/resetBookends). /_test/reset '\n + 'requires it, so the clean-slate + cleanup resets around the sweep are skipped.',\n );\n return null;\n }\n return token;\n};\n\nconst matchesFilter = (endpoint: EndpointDescriptor, filter: string | undefined): boolean => {\n if (!filter) return true;\n return `${endpoint.page}/${endpoint.name}/${endpoint.version}`.includes(filter);\n};\n\nconst filterResults = (results: ContractCheckResult[], filter: string | undefined): ContractCheckResult[] =>\n filter ? results.filter(r => matchesFilter(r.endpoint, filter)) : results;\n\nconst cloneSummary = (summary: RunContractSummary, filter: string | undefined): RunContractSummary => {\n if (!filter) return summary;\n return calculateSummary(filterResults(summary.results, filter));\n};\n\n//? Build the Cookie (and CSRF) headers sweep layers send when an `authToken`\n//? is supplied. In cookie-mode the server's CSRF middleware rejects POST/PUT/DELETE\n//? without a matching CSRF token, so we look up the session record and attach the\n//? token the same way customTests.ts does: call getSession(authToken) and read\n//? csrfToken off the persisted record. Token-mode sweeps (no Cookie header) and\n//? runs without authToken are unaffected — the CSRF header is never added.\nconst buildAuthHeaders = async (input: RunAllTestsInput): Promise<Record<string, string>> => {\n const headers: Record<string, string> = {};\n if (!input.authToken) return headers;\n\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\n headers.Cookie = `${cookieName}=${input.authToken}`;\n\n //? Resolve the CSRF token lazily — import @luckystack/login at call time so\n //? projects that run the test-runner without login installed don't crash on\n //? import. Mirror the same dynamic import pattern used in customTests.ts.\n try {\n const { getSession } = await import('@luckystack/login');\n const session = await getSession(input.authToken);\n if (session?.csrfToken) {\n headers[getCsrfConfig().headerName] = session.csrfToken;\n }\n } catch (error) {\n //? @luckystack/login not installed or session lookup failed.\n //? Warn so the operator knows the sweep is running without a CSRF header\n //? (degraded mode) — the sweep continues, but CSRF-protected state-changing\n //? endpoints will be unreachable in cookie-mode.\n console.warn(\n '[test-runner] buildAuthHeaders: could not resolve CSRF token —',\n error instanceof Error ? error.message : String(error),\n '— sweep will run without the CSRF header (degraded mode).',\n );\n }\n\n return headers;\n};\n\n//? Run the four auto-sweep layers (contract → auth → rate-limit → fuzz) in\n//? order, honoring the per-layer disable flags, and write each filtered\n//? summary onto `summary`. Mutates `summary` in place — identical to the\n//? prior inline block. The auth and rate-limit / fuzz nesting + ordering is\n//? preserved exactly (auth always runs in the sweep; rate-limit + fuzz are\n//? individually gated).\nconst runSweepLayers = async (\n input: RunAllTestsInput,\n summary: RunAllTestsSummary,\n headers: Record<string, string>,\n inputFor: (endpoint: EndpointDescriptor) => unknown,\n): Promise<void> => {\n //? Reset bookend — BEFORE the sweep. With a clean DB no real record exists for\n //? the generated sample payloads to accidentally edit/delete (finding #98).\n const resetBookendToken = resolveResetBookendToken(input);\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken });\n }\n\n const contract = await runContractTests({\n apiMethodMap: input.apiMethodMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n headers,\n inputFor,\n });\n summary.contract = cloneSummary(contract, input.filter);\n\n const auth = await runAuthEnforcementTests({\n apiMethodMap: input.apiMethodMap,\n apiMetaMap: input.apiMetaMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n inputFor,\n });\n summary.auth = cloneSummary(auth, input.filter);\n\n if (!input.noRateLimit) {\n const rateLimit = await runRateLimitTests({\n apiMethodMap: input.apiMethodMap,\n apiMetaMap: input.apiMetaMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n headers,\n inputFor,\n //? Reset the shared per-IP rate-limit bucket between endpoints so a\n //? neighbour saturating the window doesn't cause false pass/fail on the\n //? next endpoint. Requires the server to expose /_test/reset (NODE_ENV in\n //? { 'development', 'test' } AND TEST_RESET_TOKEN set + matching header).\n resetBetweenEndpoints: true,\n //? Thread the reset token (explicit input, else the TEST_RESET_TOKEN env the\n //? server validates against) — without it `/_test/reset` returns 403 and the\n //? between-endpoint reset silently never happens.\n resetToken: input.resetToken ?? process.env.TEST_RESET_TOKEN,\n });\n summary.rateLimit = cloneSummary(rateLimit, input.filter);\n }\n\n //? CSRF-enforcement layer: probes that the framework's CSRF middleware rejects\n //? state-changing authenticated requests carrying NO CSRF header. It can only\n //? reach the CSRF check past the auth guard with a valid session, so it runs\n //? only when an `authToken` is supplied (and the layer isn't disabled). Without\n //? a token there is nothing to probe — the layer is silently skipped, leaving\n //? behavior unchanged for tokenless sweeps.\n if (!input.noCsrf && headers.Cookie) {\n const csrf = await runCsrfEnforcementTests({\n apiMethodMap: input.apiMethodMap,\n apiMetaMap: input.apiMetaMap,\n baseUrl: input.baseUrl,\n authCookie: headers.Cookie,\n skip: input.skip,\n inputFor,\n });\n summary.csrf = cloneSummary(csrf, input.filter);\n }\n\n if (!input.noFuzz) {\n const fuzz = await runFuzzTests({\n apiMethodMap: input.apiMethodMap,\n baseUrl: input.baseUrl,\n skip: input.skip,\n headers,\n });\n summary.fuzz = cloneSummary(fuzz, input.filter);\n }\n\n //? Reset bookend — AFTER the sweep. Drops every row the sweep's generated\n //? payloads created so no `lstest_*` test data persists in the DB (finding #98).\n if (resetBookendToken) {\n await resetServerState({ baseUrl: input.baseUrl, token: resetBookendToken });\n }\n};\n\n//? Run the custom (Layer 5) sweep, after clearing the per-route rate-limit\n//? buckets the sweep layers drained. Mutates `summary` in place — identical to\n//? the prior inline block.\nconst runCustomLayer = async (input: RunAllTestsInput, summary: RunAllTestsSummary): Promise<void> => {\n //? The rate-limit layer drains per-route buckets; clear them before the\n //? custom layer so business-logic tests on low-limit routes (e.g.\n //? confirmReset, sendReset) aren't spuriously rejected with\n //? api.rateLimitExceeded. Runs in-process against the same Redis the server\n //? uses. Non-fatal if the active strategy has no clear().\n try {\n await clearAllRateLimits();\n } catch {\n //? swallow — a missing clear() is a test-quality degrade, not a crash.\n }\n const custom = await runCustomTestsAfterEnvironmentPrepared({\n baseUrl: input.baseUrl,\n sessionCookieName: input.sessionCookieName,\n filter: input.filter,\n //? Pass the generated method map so the harness invokes each API route\n //? with its declared HTTP method (e.g. logout is DELETE, not POST).\n apiMethodMap: input.apiMethodMap,\n });\n summary.custom = custom;\n};\n\n//? The ordered list of sweep-layer keys that map to `RunContractSummary`\n//? slots on `RunAllTestsSummary`. Drives `computeTotals` and the reporter so\n//? adding a new sweep layer only requires adding it here + to the summary type.\nconst SWEEP_LAYER_ORDER = [\n LAYER_KEYS.contract,\n LAYER_KEYS.auth,\n LAYER_KEYS.rateLimit,\n LAYER_KEYS.csrf,\n LAYER_KEYS.fuzz,\n] as const;\n\ntype SweepLayerKey = typeof SWEEP_LAYER_ORDER[number];\n\n//? Map from each canonical layer key to the matching summary property name on\n//? `RunAllTestsSummary`. Property names diverged from LAYER_KEYS values early\n//? (LAYER_KEYS uses display strings like `'auth-enforcement'`); keeping this\n//? map here means neither the reporter nor computeTotals need to hand-code the\n//? mapping.\nconst SWEEP_SUMMARY_PROP: Record<SweepLayerKey, keyof Pick<RunAllTestsSummary, 'contract' | 'auth' | 'rateLimit' | 'csrf' | 'fuzz'>> = {\n [LAYER_KEYS.contract]: 'contract',\n [LAYER_KEYS.auth]: 'auth',\n [LAYER_KEYS.rateLimit]: 'rateLimit',\n [LAYER_KEYS.csrf]: 'csrf',\n [LAYER_KEYS.fuzz]: 'fuzz',\n};\n\n//? Iterate every sweep layer + the custom layer to sum passed/failed.\n//? DD-TR-various — xpass exit-code policy (documented decision):\n//? `xpass` (a case marked `expectedToFail` that unexpectedly passes) is NOT\n//? counted in `totalFailed`. Rationale: an xpass is a positive signal — the\n//? known issue is gone and the marker just needs removing. Treating it as a\n//? failure would make CI red when a bug is fixed, which is backwards.\n//? It IS shown prominently in the summary line and the \"Unexpectedly passed\"\n//? section so the stale marker can't rot silently behind a green run.\n//? To opt into strict mode (xpass = CI failure), inspect `summary.custom?.xpassed`\n//? in your own script and `process.exit(1)` when it is > 0.\nconst computeTotals = (summary: RunAllTestsSummary): void => {\n let passed = 0;\n let failed = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n passed += s?.passed ?? 0;\n failed += s?.failed ?? 0;\n }\n summary.totalPassed = passed + (summary.custom?.passed ?? 0);\n summary.totalFailed = failed + (summary.custom?.failed ?? 0);\n};\n\nexport const runAllTests = async (input: RunAllTestsInput): Promise<RunAllTestsSummary> => {\n assertProjectConfigLoader(input, 'runAllTests');\n //? The live server resolves its own env, but Layer-5 tests may use ctx.prisma\n //? or Redis directly in THIS process. Resolve before importing custom tests or\n //? constructing those clients, otherwise DATABASE_URL_V<n> reaches Prisma raw.\n await resolveTestEnvironment({ loadProjectConfig: input.loadProjectConfig });\n\n const summary: RunAllTestsSummary = { totalPassed: 0, totalFailed: 0 };\n\n //? Warn when a non-empty apiMethodMap is paired with an empty apiMetaMap.\n //? The auth-enforcement and rate-limit layers both use the meta map to decide\n //? which routes require login and which have declared rate limits. An absent\n //? or stale map silently skips every auth/rate-limit check — the run looks\n //? green but provides no real coverage.\n if (!input.noSweep) {\n const hasRoutes = Object.keys(input.apiMethodMap).length > 0;\n const hasMeta = Object.keys(input.apiMetaMap).length > 0;\n if (hasRoutes && !hasMeta) {\n console.warn(\n '[test-runner] runAllTests: apiMetaMap is empty but apiMethodMap has routes. '\n + 'The auth-enforcement and rate-limit layers will skip every route. '\n + 'Pass the generated apiMetaMap to enable those checks.',\n );\n }\n }\n\n const headers = await buildAuthHeaders(input);\n //? One marker per run so every generated string this sweep posts is tagged\n //? with the same recognizable `lstest_<uuid>_` prefix (finding #98).\n const inputFor = inputForEndpoint(input.apiInputSchemas, createTestDataMarker());\n\n if (!input.noSweep) {\n await runSweepLayers(input, summary, headers, inputFor);\n }\n\n if (!input.noCustom) {\n await runCustomLayer(input, summary);\n }\n\n computeTotals(summary);\n\n return summary;\n};\n\n//? ── Colored, list-based reporting ───────────────────────────────────────\n//? Honors NO_COLOR / FORCE_COLOR (https://no-color.org) and falls back to\n//? plain text when stdout is not a TTY (piped to a file / CI log).\nconst useColor = ((): boolean => {\n if (process.env.NO_COLOR) return false;\n if (process.env.FORCE_COLOR) return true;\n return process.stdout.isTTY;\n})();\nconst paint = (code: string, text: string): string => (useColor ? `\\u001B[${code}m${text}\\u001B[0m` : text);\nconst green = (t: string): string => paint('32', t);\nconst red = (t: string): string => paint('31', t);\nconst yellow = (t: string): string => paint('33', t);\nconst dim = (t: string): string => paint('2', t);\nconst bold = (t: string): string => paint('1', t);\n\ninterface ReportRow {\n label: string;\n reason?: string;\n}\n\n//? DD-TR-various — leaky summary union tightened:\n//? `LayerSummary` is the minimal shape the reporter cares about — the fields\n//? every layer summary has in common. The extra-field helpers discriminate via\n//? `in` to stay type-safe without widening callers to `unknown`.\ninterface LayerSummary {\n total: number;\n passed: number;\n failed: number;\n}\n\nconst xfailOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('xfailed' in s)) return 0;\n const v = (s as Record<string, unknown>).xfailed;\n return typeof v === 'number' ? v : 0;\n};\nconst skippedOf = (s: LayerSummary | undefined): number => {\n if (s === undefined || !('skipped' in s)) return 0;\n const v = (s as Record<string, unknown>).skipped;\n return typeof v === 'number' ? v : 0;\n};\n\n//? Per-layer headline: green \"X/Y passed\" when clean, red \"Z failed\" when not,\n//? plus dim xfail / skipped counts. This is the \"14/20 in groen, 6/20 in rood\"\n//? the report leads with.\nconst formatLayerLine = (label: string, s: LayerSummary | undefined): string => {\n if (!s) return ` ${dim('–')} ${label.padEnd(18)} ${dim('(layer skipped)')}`;\n const clean = s.failed === 0;\n const mark = clean ? green('✓') : red('✗');\n const head = clean\n ? green(`${s.passed}/${s.total} passed`)\n : `${green(`${s.passed}/${s.total} passed`)} ${red(`${s.failed}/${s.total} failed`)}`;\n const extras: string[] = [];\n const xf = xfailOf(s);\n const sk = skippedOf(s);\n if (xf) extras.push(yellow(`${xf} expected-fail`));\n if (sk) extras.push(dim(`${sk} skipped`));\n const extra = extras.length > 0 ? ` ${dim('·')} ${extras.join(dim(', '))}` : '';\n return ` ${mark} ${label.padEnd(18)} ${head}${extra}`;\n};\n\nconst sweepFailRows = (s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'fail')\n .map((r) => ({\n label: `${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.errorCode ? `${r.reason ?? 'failed'} [${r.errorCode}]` : r.reason,\n }));\n\nconst sweepSkipRows = (layer: string, s: RunContractSummary | undefined): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === 'skipped')\n .map((r) => ({\n label: `${layer}: ${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\n reason: r.reason,\n }));\n\nconst customRows = (s: RunCustomTestsSummary | undefined, status: CustomTestResult['status']): ReportRow[] =>\n (s?.results ?? [])\n .filter((r) => r.status === status)\n .map((r) => ({ label: `${r.routePath} :: ${r.caseName}`, reason: r.reason }));\n\nconst printSection = (title: (t: string) => string, heading: string, rows: ReportRow[], bullet: string): void => {\n if (rows.length === 0) return;\n console.log('');\n console.log(title(bold(`${heading} (${rows.length})`)));\n for (const row of rows) {\n console.log(` ${title(bullet)} ${row.label}`);\n if (row.reason) console.log(` ${dim(row.reason)}`);\n }\n};\n\nexport const logRunAllSummary = (summary: RunAllTestsSummary): void => {\n console.log('');\n\n //? Print a headline line for every sweep layer (ordered) then the custom layer.\n for (const key of SWEEP_LAYER_ORDER) {\n console.log(formatLayerLine(key, summary[SWEEP_SUMMARY_PROP[key]]));\n }\n console.log(formatLayerLine(LAYER_KEYS.custom, summary.custom));\n\n //? Real failures across every layer — these MUST be fixed (a green test that\n //? went red, or a wrong test). Sweep failures + un-marked custom failures.\n const failed: ReportRow[] = [\n ...SWEEP_LAYER_ORDER.flatMap((key) => sweepFailRows(summary[SWEEP_SUMMARY_PROP[key]])),\n ...customRows(summary.custom, 'fail'),\n ];\n printSection(red, 'Failed — must be fixed (real bugs / wrong tests)', failed, '✗');\n\n //? Known, accepted failures — red is OK here; they are tracked, not regressions.\n printSection(yellow, 'Expected failures — known issues, allowed to fail', customRows(summary.custom, 'xfail'), '⚠');\n\n //? Stale markers — these passed but are still flagged expectedToFail.\n printSection(yellow, 'Unexpectedly passed — remove the expectedToFail marker', customRows(summary.custom, 'xpass'), '?');\n\n //? Skipped with reason — not run in this mode (login-gated, high rate-limit, …).\n const skipped: ReportRow[] = SWEEP_LAYER_ORDER.flatMap((key) =>\n sweepSkipRows(key, summary[SWEEP_SUMMARY_PROP[key]]),\n );\n printSection(dim, 'Skipped — not run (with reason)', skipped, '–');\n\n //? Sum xfail and skipped across all layers (sweep + custom) in one pass.\n let totalXfail = 0;\n let totalSkipped = 0;\n for (const key of SWEEP_LAYER_ORDER) {\n const s = summary[SWEEP_SUMMARY_PROP[key]];\n totalXfail += xfailOf(s);\n totalSkipped += skippedOf(s);\n }\n totalXfail += xfailOf(summary.custom);\n totalSkipped += skippedOf(summary.custom);\n\n //? `xpass` (a case still marked expectedToFail that now passes) is NOT a\n //? failure, so it doesn't move the exit code — but it MUST be visible in the\n //? final line, otherwise a stale marker rots silently behind a green run.\n const totalXpass = summary.custom?.xpassed ?? 0;\n\n console.log('');\n const parts = [\n green(`${summary.totalPassed} passed`),\n summary.totalFailed > 0 ? red(`${summary.totalFailed} failed`) : dim('0 failed'),\n totalXfail > 0 ? yellow(`${totalXfail} expected-fail`) : dim('0 expected-fail'),\n totalXpass > 0 ? yellow(`${totalXpass} unexpectedly-passed`) : dim('0 unexpectedly-passed'),\n totalSkipped > 0 ? yellow(`${totalSkipped} skipped`) : dim('0 skipped'),\n ];\n console.log(`Summary: ${parts.join(dim(' · '))}`);\n console.log(dim(' legend: ✗ red = must fix · ⚠ yellow = known/allowed · – skipped (not run)'));\n};\n","//? Extension registry for the test runner. Consumers plug in custom test\n//? layers, realistic-payload fixtures, and result reporters without\n//? forking the framework.\n//?\n//? Three slots:\n//?\n//? 1. Test layers — additional checks per endpoint (CORS enforcement,\n//? business rules, custom auth schemes, multi-tenant isolation).\n//? 2. Fixtures — realistic input payloads (valid + invalid) per typeKey\n//? so the fuzz layer can use them before falling back to schema-random.\n//? 3. Reporters — per-result and per-summary callbacks + optional\n//? webhook URL the runner POSTs the batch summary to.\n//?\n//? All slots are append-only at registration time; clearing requires\n//? `resetTestExtensionsForTests()` (kept out of the production surface\n//? to prevent accidental wipes from a misbehaving plugin).\n\nexport interface TestLayerInput {\n /** Resolved route name (`api/billing/getInvoice/v1`). */\n endpoint: string;\n /** Method when applicable, undefined for sync endpoints. */\n method?: string;\n /** Bearer token / session token to use for auth-aware checks. */\n authToken?: string;\n}\n\nexport interface TestLayerResult {\n passed: boolean;\n /** Short reason key — i18n-style errorCode when failed. */\n message?: string;\n /** Optional extras for the reporter (latency, response sample, ...). */\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestLayer {\n name: string;\n run: (input: TestLayerInput) => Promise<TestLayerResult> | TestLayerResult;\n}\n\nexport interface TestFixture<TPayload = unknown> {\n valid: TPayload[];\n invalid: TPayload[];\n}\n\nexport interface TestResult {\n layer: string;\n endpoint: string;\n passed: boolean;\n message?: string;\n durationMs?: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TestSummary {\n totalLayers: number;\n totalEndpoints: number;\n passed: number;\n failed: number;\n results: TestResult[];\n}\n\nexport interface TestReporter {\n /** Called once per individual TestResult as the runner progresses. */\n onResult?: (result: TestResult) => void | Promise<void>;\n /** Called once at the end with the aggregated summary. */\n onSummary?: (summary: TestSummary) => void | Promise<void>;\n /** Optional webhook the runner POSTs the summary to. */\n webhookUrl?: string;\n webhookAuth?: { type: 'bearer'; token: string };\n}\n\nconst layers = new Map<string, TestLayer>();\nconst fixtures = new Map<string, TestFixture>();\nlet reporter: TestReporter | null = null;\n\n/**\n * Register a custom test layer. Re-registering the same name replaces\n * the previous entry. Use for CORS enforcement, business-rule checks,\n * custom auth schemes, multi-tenant isolation, GDPR data-flow checks.\n */\nexport const registerTestLayer = (layer: TestLayer): void => {\n layers.set(layer.name, layer);\n};\n\nexport const listTestLayers = (): TestLayer[] => [...layers.values()];\n\n/**\n * Register a realistic payload fixture for a specific type key. The fuzz\n * layer prefers these over schema-random generated inputs when a fixture\n * exists for the endpoint's input type.\n *\n * `valid` payloads are used to verify happy-path behavior; `invalid`\n * payloads probe the validator's error responses.\n */\nexport const registerTestFixture = <TPayload = unknown>(typeKey: string, fixture: TestFixture<TPayload>): void => {\n fixtures.set(typeKey, fixture);\n};\n\nexport const getTestFixture = (typeKey: string): TestFixture | undefined => fixtures.get(typeKey);\n\n/**\n * Register a test reporter. Set `null` to unregister. The runner calls\n * `onResult` per individual result as the run progresses and `onSummary`\n * once at the end. When `webhookUrl` is set, the runner additionally\n * POSTs the JSON-serialised summary to that URL.\n */\nexport const registerTestReporter = (newReporter: TestReporter | null): void => {\n reporter = newReporter;\n};\n\nexport const getTestReporter = (): TestReporter | null => reporter;\n\nexport const resetTestExtensionsForTests = (): void => {\n layers.clear();\n fixtures.clear();\n reporter = null;\n};\n","//? Runs every layer registered via `registerTestLayer` against every endpoint\n//? in the given `apiMethodMap`, fans each result to the registered reporter\n//? (`onResult` / `onSummary`), and — when the reporter declares a `webhookUrl`\n//? — POSTs the JSON-serialised summary to it.\n//?\n//? Built-in sweep layers (contract/auth/rate-limit/fuzz) are NOT run here and\n//? `runAllTests` does NOT call this function automatically. The extension registry\n//? is a coordination surface for consumer test harnesses: read the layers with\n//? `listTestLayers()`, then call `runRegisteredLayers({ apiMethodMap, authToken })`\n//? from your own aggregator after the built-in sweeps finish. See\n//? docs/extension-hooks.md for the full lifecycle and examples.\n\nimport { tryCatch, tryCatchSync } from '@luckystack/core';\n\nimport { walkEndpoints } from './walkEndpoints';\nimport { listTestLayers, getTestReporter } from './extensionRegistry';\nimport { shouldSkip } from './testLayerHelpers';\nimport type { TestResult, TestSummary } from './extensionRegistry';\nimport type { ApiMethodMap } from './types';\n\nexport interface RunRegisteredLayersInput {\n apiMethodMap: ApiMethodMap;\n /** Session/bearer token forwarded to each layer's `run({ authToken })`. */\n authToken?: string;\n /**\n * Endpoints to skip — matched against `<page>/<name>` (version-agnostic) and\n * `<page>/<name>/<version>`, same convention as the built-in sweeps.\n */\n skip?: string[];\n}\n\n//? Whether a webhook URL targets a loopback host (localhost / 127.0.0.0/8 / ::1).\n//? Plaintext http to a non-loopback target leaks the route inventory (+ bearer)\n//? over the wire, so we warn — but never block, since the URL is consumer-self-\n//? registered.\nconst isLoopbackHost = (host: string): boolean =>\n host === 'localhost'\n || host === '::1'\n || host === '[::1]'\n || /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host);\n\n//? POST the summary to the reporter's webhook, if one is configured. Best-effort\n//? — a webhook failure must never fail the test run.\nconst postWebhook = async (summary: TestSummary): Promise<void> => {\n const reporter = getTestReporter();\n const webhookUrl = reporter?.webhookUrl;\n if (!reporter || !webhookUrl) return;\n //? Warn (don't block) when forwarding the summary — which carries endpoint\n //? paths, error codes and reporter metadata verbatim — plaintext to a\n //? non-loopback host. Mirrors the plaintext-target caution the other network\n //? helpers carry.\n const [, parsedUrl] = tryCatchSync(() => new URL(webhookUrl));\n if (parsedUrl?.protocol === 'http:' && !isLoopbackHost(parsedUrl.hostname)) {\n console.warn(\n `[test-runner] reporter.webhookUrl is plaintext http to a non-loopback host (${parsedUrl.hostname}); `\n + 'the summary (endpoint paths, error codes, metadata) and any bearer token are sent unencrypted.',\n );\n }\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (reporter.webhookAuth?.type === 'bearer') headers.Authorization = `Bearer ${reporter.webhookAuth.token}`;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => { controller.abort(); }, 10_000);\n await tryCatch(() => fetch(webhookUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(summary),\n signal: controller.signal,\n }));\n clearTimeout(timeoutHandle);\n};\n\nexport const runRegisteredLayers = async (input: RunRegisteredLayersInput): Promise<TestSummary> => {\n const layers = listTestLayers();\n const endpoints = walkEndpoints(input.apiMethodMap);\n const reporter = getTestReporter();\n const skip = input.skip ?? [];\n const results: TestResult[] = [];\n\n for (const layer of layers) {\n for (const endpoint of endpoints) {\n if (shouldSkip(endpoint, skip)) continue;\n const started = Date.now();\n const [runError, layerResult] = await tryCatch(async () =>\n layer.run({ endpoint: endpoint.fullPath, method: endpoint.method, authToken: input.authToken }));\n const durationMs = Date.now() - started;\n\n const result: TestResult = runError || !layerResult\n ? {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: false,\n message: runError?.message ?? 'layer returned no result',\n durationMs,\n }\n : {\n layer: layer.name,\n endpoint: endpoint.fullPath,\n passed: layerResult.passed,\n message: layerResult.message,\n durationMs,\n metadata: layerResult.metadata,\n };\n\n results.push(result);\n await tryCatch(async () => reporter?.onResult?.(result));\n }\n }\n\n const summary: TestSummary = {\n totalLayers: layers.length,\n totalEndpoints: endpoints.length,\n passed: results.filter(r => r.passed).length,\n failed: results.filter(r => !r.passed).length,\n results,\n };\n\n await tryCatch(async () => reporter?.onSummary?.(summary));\n await postWebhook(summary);\n\n return summary;\n};\n"],"mappings":"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","names":["DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_REQUEST_TIMEOUT_MS","tryCatch","fs","path","getProjectConfig","tryCatch","tryCatch","getProjectConfig","fs","path","tryCatch","getProjectConfig","getCsrfConfig","getProjectConfig","getProjectConfig","getCsrfConfig","tryCatch","tryCatchSync","reporter","tryCatchSync","tryCatch","layers"]}
@@ -291,6 +291,7 @@ await runContractTests({
291
291
  ## Edge cases and gotchas
292
292
 
293
293
  - **`GET` endpoints ignore `inputFor`.** The handler still calls `inputFor`, but the body is dropped before `fetch` sees it. Query-string fuzz is not in scope for the contract layer.
294
+ - **Layer 5 (`ctx.callApi`) does NOT drop `GET` input.** Unlike the contract layer above, a custom test's `callApi(input)` on a `GET` route sends `input` as the `__luckystack_data` query-string value — the same encoding the real client uses and the server reads — so a `GET` route with required fields is exercised with real data. Only the contract layer treats `GET` as data-less.
294
295
  - **`headers` are merged after `Content-Type: application/json`.** If you pass `Content-Type` yourself you will override the default; the framework parses based on the header so this is intentional.
295
296
  - **`baseUrl` trailing slash is normalized once.** `http://localhost/` and `http://localhost` produce identical requests. Embedded `//` further down the path is not collapsed.
296
297
  - **`requestTimeoutMs` is per-request, not per-sweep.** A 30-endpoint sweep with the default 5000ms timeout can run for over two minutes if every endpoint stalls. The sweep itself has no overall timeout.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckystack/test-runner",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -54,11 +54,11 @@
54
54
  "test": "vitest run"
55
55
  },
56
56
  "dependencies": {
57
- "@luckystack/core": "^0.9.2"
57
+ "@luckystack/core": "^0.10.1"
58
58
  },
59
59
  "peerDependencies": {
60
- "@luckystack/login": "^0.9.2",
61
- "@luckystack/secret-manager": "^0.9.2",
60
+ "@luckystack/login": "^0.10.1",
61
+ "@luckystack/secret-manager": "^0.10.1",
62
62
  "socket.io-client": "^4.8.0",
63
63
  "zod": "^4.0.0"
64
64
  },