@luckystack/test-runner 0.1.4 → 0.1.6
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 +14 -14
- package/dist/index.js.map +1 -1
- package/docs/auth-tests.md +226 -226
- package/docs/contract-tests.md +259 -259
- package/docs/extension-hooks.md +438 -438
- package/docs/fuzz-tests.md +249 -249
- package/docs/rate-limit-tests.md +256 -256
- package/package.json +68 -68
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@luckystack/test-runner` are documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
## [0.1.0]
|
|
11
|
-
|
|
12
|
-
### Added
|
|
13
|
-
|
|
14
|
-
- Initial public release as part of the LuckyStack package split.
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@luckystack/test-runner` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial public release as part of the LuckyStack package split.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/walkEndpoints.ts","../src/contractCheck.ts","../src/runContractTests.ts","../src/authEnforcementCheck.ts","../src/runAuthEnforcementTests.ts","../src/rateLimitCheck.ts","../src/resetServerState.ts","../src/runRateLimitTests.ts","../src/fuzzCheck.ts","../src/runFuzzTests.ts","../src/schemaSampleInput.ts","../src/reporter.ts","../src/customTests.ts","../src/streamWatcher.ts","../src/runAllTests.ts","../src/extensionRegistry.ts"],"sourcesContent":["import type { EndpointDescriptor, HttpMethod } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\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, method] of Object.entries(versionMap)) {\n if (method === undefined) continue;\n endpoints.push({\n page,\n name,\n version,\n method: method as HttpMethod,\n fullPath: `api/${page}/${name}/${version}`,\n });\n }\n }\n }\n return endpoints;\n};\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\r\n\r\nexport interface ContractCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n /**\r\n * Optional input factory. Defaults to `{}`. When an endpoint requires a\r\n * specific shape to not crash, callers can override per-endpoint here.\r\n */\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n /**\r\n * Optional headers (auth tokens, cookies). Applied to every request.\r\n */\r\n headers?: Record<string, string>;\r\n /**\r\n * Per-call request timeout in ms. Defaults to 5000. Bump for slow endpoints\r\n * (AI calls, large reports) where the framework default would false-fail\r\n * the contract check.\r\n */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\n//? Contract layer: send any-shape input, accept any response where\r\n//? `status` is `success` or `error` (with an `errorCode`). Anything else is\r\n//? a contract violation — 5xx, thrown exceptions, or shapes outside this\r\n//? envelope.\r\n//?\r\n//? The goal here is NOT to prove every endpoint works end-to-end; it's to\r\n//? catch endpoints that crash the server or return garbage on well-formed\r\n//? empty input. Real behavior tests go in the per-endpoint test file.\r\nexport const runContractCheck = async (input: ContractCheckInput): Promise<ContractCheckResult> => {\r\n const { endpoint, baseUrl } = input;\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const body = input.inputFor ? input.inputFor(endpoint) : {};\r\n const started = Date.now();\r\n\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n\r\n const [fetchError, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n //? Browsers attach Origin on state-changing requests; the server's origin\r\n //? policy fail-closes POST/PUT/DELETE without one. Send the same-origin\r\n //? value so the request reaches the API pipeline (consumers can override).\r\n 'Origin': new URL(baseUrl).origin,\r\n ...input.headers,\r\n },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n\r\n if (fetchError || !response) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n reason: fetchError?.message ?? 'fetch returned no response',\r\n durationMs: Date.now() - started,\r\n };\r\n }\r\n\r\n const [parseError, parsed] = await tryCatch<{ status?: 'success' | 'error'; errorCode?: string } | null, undefined>(\r\n async () => await response.json() as { status?: 'success' | 'error'; errorCode?: string },\r\n );\r\n\r\n const durationMs = Date.now() - started;\r\n\r\n if (parseError) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: `JSON parse failed: ${parseError.message}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (!parsed || (parsed.status !== 'success' && parsed.status !== 'error')) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: 'Response missing standard `status` envelope',\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed.status === 'error' && !parsed.errorCode) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'error',\r\n reason: 'Error response missing `errorCode`',\r\n durationMs,\r\n };\r\n }\r\n\r\n return {\r\n endpoint,\r\n status: 'pass',\r\n httpStatus: response.status,\r\n responseStatus: parsed.status,\r\n errorCode: parsed.errorCode,\r\n durationMs,\r\n };\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\nimport { runContractCheck } from './contractCheck';\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\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\nconst 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\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 {\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};\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\r\n\r\n//? Canonical error code the framework emits for unauthenticated requests\r\n//? to `auth.login: true` endpoints. See packages/api/src/handleHttpApiRequest.ts.\r\nconst EXPECTED_ERROR_CODE = 'auth.required';\r\n\r\nexport interface AuthEnforcementCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n /** Per-call request timeout in ms. Defaults to 5000. */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\n//? Auth-enforcement layer: for each `auth.login: true` endpoint, call it with\r\n//? NO session headers/cookies. Expect the server to reject with `auth.required`.\r\n//?\r\n//? A passing endpoint here proves the login guard isn't silently bypassed.\r\n//? Endpoints that return `success` without a session are a critical finding.\r\nexport const runAuthEnforcementCheck = async (\r\n input: AuthEnforcementCheckInput,\r\n): Promise<ContractCheckResult> => {\r\n const { endpoint, baseUrl } = input;\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const body = input.inputFor ? input.inputFor(endpoint) : {};\r\n const started = Date.now();\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n\r\n const [fetchError, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n //? No session cookie, no auth header — deliberately. Origin IS sent so the\r\n //? server's origin policy doesn't 403 before the auth check can run.\r\n headers: { 'Content-Type': 'application/json', 'Origin': new URL(baseUrl).origin },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n\r\n if (fetchError || !response) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n reason: fetchError?.message ?? 'fetch returned no response',\r\n durationMs: Date.now() - started,\r\n };\r\n }\r\n\r\n const [parseError, parsed] = await tryCatch<{ status?: 'success' | 'error'; errorCode?: string } | null, undefined>(\r\n async () => await response.json() as { status?: 'success' | 'error'; errorCode?: string },\r\n );\r\n const durationMs = Date.now() - started;\r\n\r\n if (parseError) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: `JSON parse failed: ${parseError.message}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed?.status === 'success') {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'success',\r\n reason: `auth.login endpoint returned success without a session`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed?.status !== 'error') {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: 'Response missing standard `status` envelope',\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed.errorCode !== EXPECTED_ERROR_CODE) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'error',\r\n errorCode: parsed.errorCode,\r\n reason: `expected errorCode '${EXPECTED_ERROR_CODE}' but got '${parsed.errorCode ?? '(missing)'}'`,\r\n durationMs,\r\n };\r\n }\r\n\r\n return {\r\n endpoint,\r\n status: 'pass',\r\n httpStatus: response.status,\r\n responseStatus: 'error',\r\n errorCode: parsed.errorCode,\r\n durationMs,\r\n };\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\r\nimport { runAuthEnforcementCheck } from './authEnforcementCheck';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\ntype ApiMetaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, {\r\n method: string;\r\n auth: { login: boolean; additional?: Record<string, unknown>[] };\r\n rateLimit?: number | false;\r\n}>>>>>>;\r\n\r\nexport interface RunAuthEnforcementTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n apiMetaMap: ApiMetaMap;\r\n baseUrl: string;\r\n skip?: string[];\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n onResult?: (result: ContractCheckResult) => void;\r\n}\r\n\r\nconst shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\r\n if (skip.length === 0) return false;\r\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\r\n const versionless = `${endpoint.page}/${endpoint.name}`;\r\n return skip.includes(versioned) || skip.includes(versionless);\r\n};\r\n\r\nconst requiresLogin = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\r\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n return meta?.auth.login ?? false;\r\n};\r\n\r\nexport const runAuthEnforcementTests = async (\r\n input: RunAuthEnforcementTestsInput,\r\n): Promise<RunContractSummary> => {\r\n const endpoints = walkEndpoints(input.apiMethodMap);\r\n const skip = input.skip ?? [];\r\n const results: ContractCheckResult[] = [];\r\n\r\n for (const endpoint of endpoints) {\r\n if (!requiresLogin(input.apiMetaMap, endpoint)) {\r\n //? Public endpoints can't be tested by this layer — skip silently, no\r\n //? noise. They're still covered by the contract layer.\r\n continue;\r\n }\r\n\r\n if (shouldSkip(endpoint, skip)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'Explicitly skipped',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n const result = await runAuthEnforcementCheck({\r\n endpoint,\r\n baseUrl: input.baseUrl,\r\n inputFor: input.inputFor,\r\n });\r\n results.push(result);\r\n input.onResult?.(result);\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\r\nconst EXPECTED_ERROR_CODE = 'api.rateLimitExceeded';\r\n\r\nexport interface RateLimitCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n rateLimit: number;\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n headers?: Record<string, string>;\r\n /** Per-call request timeout in ms. Defaults to 10000. */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\n//? Rate-limit layer: fire `rateLimit + 1` requests back to back and assert the\r\n//? last one is rejected with `api.rateLimitExceeded`. All preceding requests\r\n//? are allowed to return either success or any other envelope — only the N+1\r\n//? matters for this layer.\r\n//?\r\n//? This layer mutates server-side state (limiter counters). Run it after the\r\n//? contract + auth layers, or against a dedicated test instance. The server's\r\n//? clearAllRateLimits() utility can reset state between runs.\r\nexport const runRateLimitCheck = async (input: RateLimitCheckInput): Promise<ContractCheckResult> => {\r\n const { endpoint, baseUrl, rateLimit } = input;\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const body = input.inputFor ? input.inputFor(endpoint) : {};\r\n const started = Date.now();\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n\r\n const send = async (): Promise<Response | null> => {\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n const [error, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Origin': new URL(baseUrl).origin,\r\n ...input.headers,\r\n },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n if (error || !response) return null;\r\n return response;\r\n };\r\n\r\n //? Fire `rateLimit` allowed requests first (drain the bucket), then a final\r\n //? request that should be blocked. Serial so the server sees a clean order.\r\n for (let i = 0; i < rateLimit; i += 1) {\r\n await send();\r\n }\r\n\r\n const final = await send();\r\n const durationMs = Date.now() - started;\r\n\r\n if (!final) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n reason: 'Final rate-limit probe request failed to return a response',\r\n durationMs,\r\n };\r\n }\r\n\r\n const [, parsed] = await tryCatch<{ status?: 'success' | 'error'; errorCode?: string } | null, undefined>(\r\n async () => await final.json() as { status?: 'success' | 'error'; errorCode?: string },\r\n );\r\n\r\n if (parsed?.status === 'error' && parsed.errorCode === EXPECTED_ERROR_CODE) {\r\n return {\r\n endpoint,\r\n status: 'pass',\r\n httpStatus: final.status,\r\n responseStatus: 'error',\r\n errorCode: parsed.errorCode,\r\n durationMs,\r\n };\r\n }\r\n\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: final.status,\r\n responseStatus: parsed?.status ?? 'unknown',\r\n errorCode: parsed?.errorCode,\r\n reason: `expected '${EXPECTED_ERROR_CODE}' on request ${rateLimit + 1} but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\r\n durationMs,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\n\r\n//? Calls the server's /_test/reset endpoint. Used between rate-limit test\r\n//? endpoints to drain the shared IP-based limiter bucket so one endpoint's\r\n//? N+1 assertion doesn't consume the next endpoint's window.\r\n//?\r\n//? /_test/reset is gated by `NODE_ENV in { 'development', 'test' }` on the\r\n//? server side AND requires `TEST_RESET_TOKEN` to be set unconditionally\r\n//? (no token = 403). For staging/preview deploys that enable it for CI,\r\n//? set `TEST_RESET_TOKEN` on the server and pass the same value here.\r\n\r\nexport interface ResetServerStateInput {\r\n baseUrl: string;\r\n token?: string;\r\n}\r\n\r\nexport const resetServerState = async ({ baseUrl, token }: ResetServerStateInput): Promise<boolean> => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/_test/reset`;\r\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\r\n if (token) headers['X-Test-Reset-Token'] = token;\r\n\r\n const [error, response] = await tryCatch(() => fetch(url, { method: 'POST', headers }));\r\n if (error || !response) return false;\r\n return response.ok;\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\r\nimport { runRateLimitCheck } from './rateLimitCheck';\r\nimport { resetServerState } from './resetServerState';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\ntype ApiMetaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, {\r\n method: string;\r\n auth: { login: boolean; additional?: Record<string, unknown>[] };\r\n rateLimit?: number | false;\r\n}>>>>>>;\r\n\r\nexport interface RunRateLimitTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n apiMetaMap: ApiMetaMap;\r\n baseUrl: string;\r\n skip?: string[];\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n headers?: Record<string, string>;\r\n /**\r\n * Max rateLimit value to test. Endpoints with a higher limit are skipped\r\n * to avoid firing thousands of requests in CI.\r\n */\r\n maxRateLimitToTest?: number;\r\n /**\r\n * When true, hit `/_test/reset` before each endpoint so limiter state is\r\n * clean. Requires the server to allow the endpoint (NODE_ENV !== production\r\n * or `TEST_RESET_TOKEN` configured). Without resetting, the shared IP bucket\r\n * leaks state between endpoints.\r\n */\r\n resetBetweenEndpoints?: boolean;\r\n /**\r\n * Token forwarded as `X-Test-Reset-Token` when resetting. Only relevant in\r\n * staging/preview deploys that enable the endpoint behind a token.\r\n */\r\n resetToken?: string;\r\n onResult?: (result: ContractCheckResult) => void;\r\n}\r\n\r\nconst getRateLimit = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): number | null => {\r\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n if (!meta || meta.rateLimit === false || meta.rateLimit === undefined) return null;\r\n return meta.rateLimit;\r\n};\r\n\r\nconst requiresLogin = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\r\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n return meta?.auth.login ?? false;\r\n};\r\n\r\nconst shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\r\n if (skip.length === 0) return false;\r\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\r\n const versionless = `${endpoint.page}/${endpoint.name}`;\r\n return skip.includes(versioned) || skip.includes(versionless);\r\n};\r\n\r\nexport const runRateLimitTests = async (\r\n input: RunRateLimitTestsInput,\r\n): Promise<RunContractSummary> => {\r\n const endpoints = walkEndpoints(input.apiMethodMap);\r\n const skip = input.skip ?? [];\r\n const maxRateLimit = input.maxRateLimitToTest ?? 50;\r\n //? A login-required route returns `auth.required` BEFORE the rate limiter\r\n //? runs, so it can only be rate-limit-tested with a session. A Cookie header\r\n //? means an authenticated sweep. We deliberately do NOT auto-authenticate the\r\n //? sweep — that would execute real mutations (delete/update) with junk input.\r\n //? Set TEST_AUTH_TOKEN to opt in and cover login-gated routes.\r\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\r\n const results: ContractCheckResult[] = [];\r\n\r\n for (const endpoint of endpoints) {\r\n const rateLimit = getRateLimit(input.apiMetaMap, endpoint);\r\n if (rateLimit === null) continue;\r\n\r\n if (!isAuthenticatedSweep && requiresLogin(input.apiMetaMap, endpoint)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'login-required route — set TEST_AUTH_TOKEN to rate-limit-test it (unauthenticated calls hit auth.required before the limiter)',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n if (rateLimit > maxRateLimit) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: `rateLimit ${rateLimit} exceeds maxRateLimitToTest=${maxRateLimit}`,\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n if (shouldSkip(endpoint, skip)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'Explicitly skipped',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n if (input.resetBetweenEndpoints) {\r\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken });\r\n }\r\n\r\n const result = await runRateLimitCheck({\r\n endpoint,\r\n baseUrl: input.baseUrl,\r\n rateLimit,\r\n inputFor: input.inputFor,\r\n headers: input.headers,\r\n });\r\n results.push(result);\r\n input.onResult?.(result);\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\r\n\r\n//? Lightweight fuzz layer — no Zod yet. Sends well-known junk shapes and\r\n//? asserts the server doesn't 5xx or return a non-envelope body. Real\r\n//? schema-driven fuzzing (TS type -> random valid input) is deferred until\r\n//? the generator emits Zod schemas alongside the types.\r\nconst JUNK_PAYLOADS: unknown[] = [\r\n null,\r\n [],\r\n [1, 2, 3],\r\n 'string-instead-of-object',\r\n 1_234_567_890,\r\n true,\r\n { nested: { deeply: { nested: { value: 'x'.repeat(10_000) } } } },\r\n { __proto__: { polluted: true } },\r\n { key: null, other: undefined, third: Number.NaN },\r\n];\r\n\r\nexport interface FuzzCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n headers?: Record<string, string>;\r\n /** Per-call request timeout in ms. Defaults to 5000. */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\nconst probe = async (\r\n endpoint: EndpointDescriptor,\r\n baseUrl: string,\r\n body: unknown,\r\n headers: Record<string, string> | undefined,\r\n requestTimeoutMs: number,\r\n): Promise<{ httpStatus: number; parsed: { status?: unknown; errorCode?: unknown } | null } | null> => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n const [fetchError, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n headers: { 'Content-Type': 'application/json', 'Origin': new URL(baseUrl).origin, ...headers },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n if (fetchError || !response) return null;\r\n\r\n const [, parsed] = await tryCatch<{ status?: unknown; errorCode?: unknown } | null, undefined>(\r\n async () => await response.json() as { status?: unknown; errorCode?: unknown },\r\n );\r\n return { httpStatus: response.status, parsed: parsed ?? null };\r\n};\r\n\r\nexport const runFuzzCheck = async (input: FuzzCheckInput): Promise<ContractCheckResult> => {\r\n const started = Date.now();\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n\r\n for (const payload of JUNK_PAYLOADS) {\r\n const result = await probe(input.endpoint, input.baseUrl, payload, input.headers, requestTimeoutMs);\r\n const durationMs = Date.now() - started;\r\n\r\n if (!result) {\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'fail',\r\n reason: `fuzz probe crashed (no response) with payload: ${JSON.stringify(payload).slice(0, 80)}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n //? A 5xx that STILL returns a valid `{ status:'error', errorCode }` envelope\r\n //? is the framework gracefully catching an error (e.g. a route designed to\r\n //? throw), not a crash. The fuzz layer's concern is crashes / hangs /\r\n //? garbage — so we only fail on a non-envelope body below (which catches raw\r\n //? 5xx, HTML error pages, and truncated responses). A controlled 500\r\n //? envelope passes.\r\n if (!result.parsed || (result.parsed.status !== 'success' && result.parsed.status !== 'error')) {\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'fail',\r\n httpStatus: result.httpStatus,\r\n reason: `fuzz payload produced non-envelope response: ${JSON.stringify(payload).slice(0, 80)}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (result.parsed.status === 'error' && typeof result.parsed.errorCode !== 'string') {\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'fail',\r\n httpStatus: result.httpStatus,\r\n reason: `fuzz error response missing errorCode for payload: ${JSON.stringify(payload).slice(0, 80)}`,\r\n durationMs,\r\n };\r\n }\r\n }\r\n\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'pass',\r\n durationMs: Date.now() - started,\r\n };\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\nimport { runFuzzCheck } from './fuzzCheck';\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\n\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\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\nconst 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\nexport const runFuzzTests = async (input: RunFuzzTestsInput): 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 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 {\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};\n","import type { z } from 'zod';\r\n\r\n//? Walk a Zod schema and produce a minimal valid value. Used by the\r\n//? contract/fuzz runners so \"valid input\" tests don't require hand-written\r\n//? fixtures per endpoint. Handles the shapes the generator emits\r\n//? (primitives, literals, unions, optionals, objects, records, arrays).\r\n//?\r\n//? Not a property-based generator — returns the SAME deterministic value\r\n//? every call. For randomized fuzz, swap to `fast-check` in a later session.\r\n\r\nconst sampleForDef = (def: unknown): unknown => {\r\n //? Zod stores its runtime shape on `_def.type` (v3) or `_def.typeName`.\r\n //? Access is untyped because `def` can be any of dozens of internal shapes.\r\n const d = def as { typeName?: string; type?: string; innerType?: { _def: unknown }; values?: unknown[]; value?: unknown; options?: { _def: unknown }[]; shape?: (() => Record<string, { _def: unknown }>) | Record<string, { _def: unknown }>; valueType?: { _def: unknown }; element?: { _def: unknown } };\r\n\r\n const name = d.typeName ?? d.type;\r\n\r\n switch (name) {\r\n case 'ZodString':\r\n case 'string': {\r\n return 'test';\r\n }\r\n case 'ZodNumber':\r\n case 'number': {\r\n return 0;\r\n }\r\n case 'ZodBoolean':\r\n case 'boolean': {\r\n return false;\r\n }\r\n case 'ZodNull':\r\n case 'null': {\r\n return null;\r\n }\r\n case 'ZodUndefined':\r\n case 'undefined': {\r\n return undefined;\r\n }\r\n case 'ZodAny':\r\n case 'any':\r\n case 'ZodUnknown':\r\n case 'unknown': {\r\n return null;\r\n }\r\n case 'ZodLiteral':\r\n case 'literal': {\r\n //? zod 3 stores the literal on `_def.value`; zod 4 stores `_def.values` (array).\r\n return d.value ?? d.values?.[0];\r\n }\r\n case 'ZodOptional':\r\n case 'optional':\r\n case 'ZodNullable':\r\n case 'nullable': {\r\n //? Omitting optional fields is the most conservative valid input.\r\n return undefined;\r\n }\r\n case 'ZodUnion':\r\n case 'union': {\r\n return d.options && d.options.length > 0 && d.options[0] ? sampleForDef(d.options[0]._def) : null;\r\n }\r\n case 'ZodArray':\r\n case 'array': {\r\n return [];\r\n }\r\n case 'ZodObject':\r\n case 'object': {\r\n const out: Record<string, unknown> = {};\r\n //? zod 3 exposes `_def.shape` as a function; zod 4 as a plain object.\r\n const shape = typeof d.shape === 'function' ? d.shape() : (d.shape ?? {});\r\n for (const [key, valueSchema] of Object.entries(shape)) {\r\n const sample = sampleForDef(valueSchema._def);\r\n if (sample !== undefined) out[key] = sample;\r\n }\r\n return out;\r\n }\r\n case 'ZodRecord':\r\n case 'record': {\r\n return {};\r\n }\r\n case 'ZodDate':\r\n case 'date': {\r\n return new Date().toISOString();\r\n }\r\n default: {\r\n return null;\r\n }\r\n }\r\n};\r\n\r\nexport const sampleSchemaInput = (schema: z.ZodType): unknown => {\r\n //? zod 4 renamed the public def accessor from `._def` to `.def`.\r\n return sampleForDef(schema.def);\r\n};\r\n","import type { ContractCheckResult, RunContractSummary } from './types';\r\n\r\nconst statusIcon = (status: ContractCheckResult['status']): string => {\r\n switch (status) {\r\n case 'pass': { return '[PASS]';\r\n }\r\n case 'fail': { return '[FAIL]';\r\n }\r\n case 'skipped': { return '[SKIP]';\r\n }\r\n }\r\n};\r\n\r\nexport const logContractResult = (result: ContractCheckResult): void => {\r\n const { endpoint, durationMs, httpStatus, responseStatus, errorCode, reason } = result;\r\n const icon = statusIcon(result.status);\r\n const core = `${icon} ${endpoint.method} /${endpoint.fullPath} ${durationMs}ms`;\r\n\r\n if (result.status === 'pass') {\r\n const tail = responseStatus === 'error' ? ` (error: ${errorCode ?? 'unknown'})` : '';\r\n console.log(`${core} http=${httpStatus ?? '-'}${tail}`);\r\n return;\r\n }\r\n\r\n if (result.status === 'skipped') {\r\n console.log(`${core} ${reason ?? ''}`);\r\n return;\r\n }\r\n\r\n console.log(`${core} http=${httpStatus ?? '-'} reason=${reason ?? '(none)'}`);\r\n};\r\n\r\nexport const logContractSummary = (summary: RunContractSummary): void => {\r\n const { total, passed, failed, skipped } = summary;\r\n console.log('');\r\n console.log(`Contract tests: ${passed}/${total} passed, ${failed} failed, ${skipped} skipped`);\r\n\r\n if (failed > 0) {\r\n console.log('');\r\n console.log('Failures:');\r\n for (const result of summary.results) {\r\n if (result.status !== 'fail') continue;\r\n console.log(` - /${result.endpoint.fullPath}: ${result.reason ?? '(no reason)'}`);\r\n }\r\n }\r\n};\r\n","//? Per-route business-logic test layer. Discovers `*.tests.ts` files\r\n//? alongside `_api/` and `_sync/` route source, dynamic-imports each,\r\n//? builds a route-bound `TestContext`, runs the exported `customTests`\r\n//? cases. Spec: docs/ARCHITECTURE_TESTING.md.\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { pathToFileURL } from 'node:url';\r\n\r\nimport { getCsrfConfig, getPrismaClient, getProjectConfig, getSrcDir, tryCatch } from '@luckystack/core';\r\n\r\nimport {\r\n openStreamWatcher,\r\n type StreamChunkFrame,\r\n type StreamWatcher,\r\n} from './streamWatcher';\r\n\r\nconst API_TEST_FILE_PATTERN = /_v(\\d+)\\.tests\\.ts$/;\r\nconst SYNC_SERVER_TEST_FILE_PATTERN = /_server_v(\\d+)\\.tests\\.ts$/;\r\n\r\n//? Mirror of the generated `apiMethodMap` shape (page → name → version →\r\n//? method). Kept local so the test-runner has no import-time coupling to the\r\n//? consumer's generated artifact.\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\n\r\nexport interface CustomTestCase {\r\n name: string;\r\n run: (ctx: TestContext) => Promise<void>;\r\n /**\r\n * Mark this case as a KNOWN, accepted failure (e.g. a documented bug not\r\n * yet fixed, or a scenario the current implementation cannot satisfy). The\r\n * string is the reason shown in the report. A case marked `expectedToFail`\r\n * that throws is reported as `xfail` — counted separately, NOT a red\r\n * failure. If it unexpectedly PASSES it is reported as `xpass` so the stale\r\n * marker can be removed. Leave undefined for normal cases.\r\n */\r\n expectedToFail?: string;\r\n}\r\n\r\nexport interface CustomTestResult {\r\n routePath: string;\r\n caseName: string;\r\n //? `xfail` = marked expectedToFail and failed as expected (known issue).\r\n //? `xpass` = marked expectedToFail but passed (remove the marker).\r\n status: 'pass' | 'fail' | 'xfail' | 'xpass';\r\n durationMs: number;\r\n reason?: string;\r\n /** Documented reason when the case carried an `expectedToFail` marker. */\r\n expectedToFail?: string;\r\n}\r\n\r\nexport interface RunCustomTestsSummary {\r\n total: number;\r\n passed: number;\r\n failed: number;\r\n /** Cases marked `expectedToFail` that failed as expected (known issues). */\r\n xfailed: number;\r\n /** Cases marked `expectedToFail` that unexpectedly passed (remove the marker). */\r\n xpassed: number;\r\n results: CustomTestResult[];\r\n}\r\n\r\nexport interface RunCustomTestsInput {\r\n baseUrl: string;\r\n /** Optional cookie name for the session token. Defaults to the project's configured cookie. */\r\n sessionCookieName?: string;\r\n /** Filter to a subset of routes — substring match against `<page>/<name>/<version>`. */\r\n filter?: string;\r\n /**\r\n * Generated `apiMethodMap` so the harness sends each API route its declared\r\n * HTTP method (e.g. logout is DELETE). Falls back to POST when absent.\r\n */\r\n apiMethodMap?: ApiMethodMap;\r\n onResult?: (result: CustomTestResult) => void;\r\n}\r\n\r\ninterface DiscoveredTestFile {\r\n /** Absolute path to the test file. */\r\n filePath: string;\r\n /** `api` or `sync` — which transport this route uses. */\r\n kind: 'api' | 'sync';\r\n /** Route descriptor parsed from the filename + directory location. */\r\n route: { page: string; name: string; version: string };\r\n}\r\n\r\n//? Walk `src/` recursively. A file matches when its directory contains an\r\n//? `_api/` or `_sync/` segment AND the filename matches the version suffix\r\n//? pattern. The route's `page` is everything between `src/` and the marker\r\n//? segment, joined with `/`. Nested page folders are supported (e.g.\r\n//? `src/admin/users/_api/getUser_v1.tests.ts` → page `admin/users`).\r\nexport const discoverCustomTestFiles = (srcDir: string = getSrcDir()): DiscoveredTestFile[] => {\r\n if (!fs.existsSync(srcDir)) return [];\r\n const found: DiscoveredTestFile[] = [];\r\n const stack: string[] = [srcDir];\r\n while (stack.length > 0) {\r\n const current = stack.pop();\r\n if (current === undefined) break;\r\n const entries = fs.readdirSync(current, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(current, entry.name);\r\n if (entry.isDirectory()) {\r\n stack.push(fullPath);\r\n continue;\r\n }\r\n if (!entry.isFile() || !entry.name.endsWith('.tests.ts')) continue;\r\n const parentDir = path.basename(current);\r\n if (parentDir !== '_api' && parentDir !== '_sync') continue;\r\n const pattern = parentDir === '_api' ? API_TEST_FILE_PATTERN : SYNC_SERVER_TEST_FILE_PATTERN;\r\n const versionMatch = pattern.exec(entry.name);\r\n if (!versionMatch) continue;\r\n const version = `v${versionMatch[1]}`;\r\n const namePart = entry.name.replace(parentDir === '_api' ? /_v\\d+\\.tests\\.ts$/ : /_server_v\\d+\\.tests\\.ts$/, '');\r\n const pageDir = path.dirname(current);\r\n //? Top-level `src/_api/` (no page folder) routes under the framework's\r\n //? `system` page — mirror that, otherwise an empty page produces the\r\n //? malformed URL `/api//logout/v1` and the call 404s.\r\n const relativePage = path.relative(srcDir, pageDir).replaceAll('\\\\', '/') || 'system';\r\n found.push({\r\n filePath: fullPath,\r\n kind: parentDir === '_api' ? 'api' : 'sync',\r\n route: { page: relativePage, name: namePart, version },\r\n });\r\n }\r\n }\r\n return found.toSorted((a, b) => a.filePath.localeCompare(b.filePath));\r\n};\r\n\r\n//? Test fixture shapes. The TestContext is built per route — each per-route\r\n//? test file's `customTests` cases receive a context already bound to the\r\n//? route the file lives next to, so authors don't repeat `page/name/version`.\r\nexport interface TestContext {\r\n /** Invoke the route under test. Page / name / version are baked in. */\r\n //? Two type parameters by design — `TInput` lets callers document the\r\n //? typed shape they're sending even though we don't validate against it\r\n //? here (the route's own runtime validation does). The ESLint rule\r\n //? otherwise flags it as \"used once\"; the documentation value is real.\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\r\n callApi: <TInput = unknown, TOutput = unknown>(input: TInput) => Promise<TOutput>;\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\r\n callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: { receiver?: string }) => Promise<TOutput>;\r\n /**\r\n * Open a second socket connection that joins `roomCode` as a pure\r\n * subscriber and exposes the incoming chunk stream for assertions.\r\n *\r\n * The route is auto-derived from the test file location, so a watcher\r\n * only surfaces chunks for the route this test belongs to. Multiple\r\n * watchers per test are allowed; all are auto-closed when the test\r\n * case finishes (whether it passes or throws).\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\r\n watchStream: <TChunk extends StreamChunkFrame = StreamChunkFrame>(roomCode: string) => Promise<StreamWatcher<TChunk>>;\r\n session: {\r\n login: (user?: { email?: string; id?: string; name?: string }) => Promise<{ token: string; userId: string }>;\r\n logout: () => Promise<void>;\r\n current: () => { token: string | null; userId: string | null };\r\n };\r\n prisma: ReturnType<typeof getPrismaClient>;\r\n expect: TestExpect;\r\n}\r\n\r\nexport interface TestExpect {\r\n eq: <T>(actual: T, expected: T, message?: string) => void;\r\n ok: (value: unknown, message?: string) => void;\r\n throws: (fn: () => Promise<unknown>, message?: string) => Promise<Error>;\r\n matches: (value: string, pattern: RegExp, message?: string) => void;\r\n}\r\n\r\nclass AssertionError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'AssertionError';\r\n }\r\n}\r\n\r\nconst buildExpect = (): TestExpect => ({\r\n eq: (actual, expected, message) => {\r\n const ok = Object.is(actual, expected)\r\n || (typeof actual === 'object' && typeof expected === 'object' && JSON.stringify(actual) === JSON.stringify(expected));\r\n if (!ok) {\r\n const prefix = message ? `${message}: ` : '';\r\n throw new AssertionError(`${prefix}expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);\r\n }\r\n },\r\n ok: (value, message) => {\r\n if (!value) throw new AssertionError(message ?? `expected truthy value, got ${JSON.stringify(value)}`);\r\n },\r\n throws: async (fn, message) => {\r\n const [err] = await tryCatch(fn);\r\n if (!err) throw new AssertionError(message ?? 'expected fn to throw, but it did not');\r\n return err;\r\n },\r\n matches: (value, pattern, message) => {\r\n if (!pattern.test(value)) {\r\n const prefix = message ? `${message}: ` : '';\r\n throw new AssertionError(`${prefix}\"${value}\" does not match ${pattern}`);\r\n }\r\n },\r\n});\r\n\r\ninterface TestSessionState {\r\n token: string | null;\r\n userId: string | null;\r\n //? CSRF token minted by saveSession on login. Sent on authenticated\r\n //? state-changing requests so the server's CSRF middleware doesn't 403.\r\n csrfToken: string | null;\r\n //? Raw JSON body of the most recent callApi / callSync response. Surfaced in\r\n //? a failed case's reason so the report shows the server's actual errorCode\r\n //? (e.g. `api.rateLimitExceeded`) instead of only the assertion text.\r\n lastResponse: unknown;\r\n}\r\n\r\n//? Pull the server's errorCode (or non-success status) out of the last HTTP\r\n//? response so a failure's reason shows WHY the server rejected the call, not\r\n//? just the assertion text. Returns undefined for success / non-envelope bodies.\r\nconst extractErrorCode = (response: unknown): string | undefined => {\r\n if (!response || typeof response !== 'object') return undefined;\r\n const r = response as { errorCode?: unknown; status?: unknown };\r\n if (typeof r.errorCode === 'string' && r.errorCode.length > 0) return r.errorCode;\r\n if (typeof r.status === 'string' && r.status !== 'success') return r.status;\r\n return undefined;\r\n};\r\n\r\n//? Minimal session login: mints a session in Redis via the consumer's\r\n//? registered SessionAdapter (from `@luckystack/login`). Deliberately does\r\n//? NOT create the Prisma user record — User-table shape varies per\r\n//? project. If your test needs a real user row, create it via\r\n//? `ctx.prisma.user.create(...)` first, then pass `{ id, email }` to\r\n//? `session.login()`.\r\nconst buildSessionHelpers = (state: TestSessionState): TestContext['session'] => {\r\n return {\r\n login: async (user) => {\r\n const { saveSession, getSession } = await import('@luckystack/login');\r\n const userId = user?.id ?? `test-user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\r\n const email = user?.email ?? `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@example.com`;\r\n const name = user?.name ?? 'Test User';\r\n const token = `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\r\n const sessionData = {\r\n id: userId,\r\n email,\r\n name,\r\n token,\r\n provider: 'credentials' as const,\r\n };\r\n await saveSession(token, sessionData, true);\r\n state.token = token;\r\n state.userId = userId;\r\n //? Read back the CSRF token saveSession minted so authenticated\r\n //? state-changing requests can pass the server's CSRF middleware.\r\n const persisted = await getSession(token);\r\n state.csrfToken = persisted?.csrfToken ?? null;\r\n return { token, userId };\r\n },\r\n logout: async () => {\r\n if (!state.token) return;\r\n const { deleteSession } = await import('@luckystack/login');\r\n await deleteSession(state.token);\r\n state.token = null;\r\n state.userId = null;\r\n state.csrfToken = null;\r\n },\r\n current: () => ({ token: state.token, userId: state.userId }),\r\n };\r\n};\r\n\r\nconst buildCallApi = (\r\n baseUrl: string,\r\n routePath: string,\r\n state: TestSessionState,\r\n cookieName: string,\r\n //? The route's declared HTTP method (from the generated apiMethodMap). The\r\n //? server validates the method, so DELETE/PUT routes (e.g. logout) 405 if we\r\n //? always sent POST.\r\n method: string,\r\n): TestContext['callApi'] => {\r\n return async (input) => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/api/${routePath}`;\r\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\r\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\r\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\r\n const init: RequestInit = { method, headers };\r\n //? GET / HEAD requests cannot carry a body (fetch throws). Every other\r\n //? method sends the JSON payload.\r\n if (method !== 'GET' && method !== 'HEAD') init.body = JSON.stringify(input ?? {});\r\n const response = await fetch(url, init);\r\n const json = (await response.json()) as never;\r\n state.lastResponse = json;\r\n return json;\r\n };\r\n};\r\n\r\nconst buildCallSync = (\r\n baseUrl: string,\r\n routePath: string,\r\n state: TestSessionState,\r\n cookieName: string,\r\n): TestContext['callSync'] => {\r\n return async (input, opts) => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/sync/${routePath}`;\r\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\r\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\r\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\r\n const body = { data: input ?? {}, receiver: opts?.receiver };\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(body),\r\n });\r\n const json = (await response.json()) as never;\r\n state.lastResponse = json;\r\n return json;\r\n };\r\n};\r\n\r\ninterface BuiltContext {\r\n ctx: TestContext;\r\n /** Close every watcher opened during the case. Called by `runCustomTests` after each case. */\r\n closeAllWatchers: () => Promise<void>;\r\n /** Per-case session state — read after the run to surface the last response's errorCode. */\r\n state: TestSessionState;\r\n}\r\n\r\nconst buildContext = (\r\n discovery: DiscoveredTestFile,\r\n input: RunCustomTestsInput,\r\n): BuiltContext => {\r\n //? Default to the PROJECT's configured session cookie name (the server reads\r\n //? the token from this cookie). Hardcoding a name silently breaks every\r\n //? authenticated custom test when the project uses a different name.\r\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\r\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\r\n const routeFullName = `sync/${routePath}`;\r\n //? API routes are invoked with their declared HTTP method; sync routes always\r\n //? POST. Default to POST when the map lacks the route (e.g. sync, or a map\r\n //? wasn't threaded through).\r\n const apiMethod = input.apiMethodMap?.[discovery.route.page]?.[discovery.route.name]?.[discovery.route.version] ?? 'POST';\r\n const state: TestSessionState = { token: null, userId: null, csrfToken: null, lastResponse: null };\r\n const prisma = getPrismaClient();\r\n\r\n //? Watchers are tracked per case so a missing explicit `close()` can't\r\n //? leak socket B between tests.\r\n const watchers: StreamWatcher[] = [];\r\n const watchStream = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\r\n roomCode: string,\r\n ): Promise<StreamWatcher<TChunk>> => {\r\n const watcher = await openStreamWatcher<TChunk>({\r\n baseUrl: input.baseUrl,\r\n roomCode,\r\n token: state.token,\r\n routeFullName,\r\n });\r\n //? Track watchers under the base `StreamChunkFrame` shape; the\r\n //? returned reference keeps the caller-supplied generic.\r\n watchers.push(watcher);\r\n return watcher;\r\n };\r\n\r\n const closeAllWatchers = async (): Promise<void> => {\r\n while (watchers.length > 0) {\r\n const watcher = watchers.pop();\r\n if (!watcher) continue;\r\n const [closeError] = await tryCatch(() => watcher.close());\r\n if (closeError) {\r\n //? Swallow — closing in cleanup must never mask the original test\r\n //? failure. Surface via logger if a project wires one up.\r\n //? (Intentionally not using getLogger here to avoid an extra import.)\r\n }\r\n }\r\n };\r\n\r\n const ctx: TestContext = {\r\n callApi: buildCallApi(input.baseUrl, routePath, state, cookieName, apiMethod),\r\n callSync: buildCallSync(input.baseUrl, routePath, state, cookieName),\r\n watchStream,\r\n session: buildSessionHelpers(state),\r\n prisma,\r\n expect: buildExpect(),\r\n };\r\n\r\n return { ctx, closeAllWatchers, state };\r\n};\r\n\r\nexport const runCustomTests = async (input: RunCustomTestsInput): Promise<RunCustomTestsSummary> => {\r\n const files = discoverCustomTestFiles();\r\n const results: CustomTestResult[] = [];\r\n\r\n for (const discovery of files) {\r\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\r\n if (input.filter && !routePath.includes(input.filter)) continue;\r\n\r\n const [importError, mod] = await tryCatch(\r\n () => import(pathToFileURL(discovery.filePath).href) as Promise<Record<string, unknown>>,\r\n );\r\n if (importError || !mod) {\r\n const r: CustomTestResult = {\r\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\r\n reason: `failed to import ${discovery.filePath}: ${importError?.message ?? 'unknown'}`,\r\n };\r\n results.push(r);\r\n input.onResult?.(r);\r\n continue;\r\n }\r\n\r\n const exported = (mod as { customTests?: unknown }).customTests;\r\n if (!Array.isArray(exported) || exported.length === 0) {\r\n //? File present but no cases — treat as silent skip; the AI hasn't filled it in yet.\r\n continue;\r\n }\r\n\r\n for (const rawCase of exported) {\r\n const c = rawCase as CustomTestCase;\r\n if (typeof c.name !== 'string' || typeof c.run !== 'function') continue;\r\n const expectedToFail = typeof c.expectedToFail === 'string' ? c.expectedToFail : undefined;\r\n const built = buildContext(discovery, input);\r\n const started = Date.now();\r\n const [runError] = await tryCatch(async () => c.run(built.ctx));\r\n //? Always close watchers regardless of pass/fail so the next case\r\n //? starts clean. tryCatch on the cleanup itself prevents a teardown\r\n //? throw from masking the original failure.\r\n await tryCatch(() => built.closeAllWatchers());\r\n const durationMs = Date.now() - started;\r\n\r\n let r: CustomTestResult;\r\n if (runError) {\r\n //? Augment the assertion message with the server's actual errorCode so\r\n //? the report explains the rejection, not just the failed expectation.\r\n const errorCode = extractErrorCode(built.state.lastResponse);\r\n const reason = errorCode ? `${runError.message} (server: ${errorCode})` : runError.message;\r\n r = expectedToFail\r\n ? { routePath, caseName: c.name, status: 'xfail', durationMs, reason, expectedToFail }\r\n : { routePath, caseName: c.name, status: 'fail', durationMs, reason };\r\n } else {\r\n r = expectedToFail\r\n ? {\r\n routePath, caseName: c.name, status: 'xpass', durationMs,\r\n reason: `marked expectedToFail (\"${expectedToFail}\") but passed — remove the marker`,\r\n expectedToFail,\r\n }\r\n : { routePath, caseName: c.name, status: 'pass', durationMs };\r\n }\r\n results.push(r);\r\n input.onResult?.(r);\r\n }\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n xfailed: results.filter(r => r.status === 'xfail').length,\r\n xpassed: results.filter(r => r.status === 'xpass').length,\r\n results,\r\n };\r\n};\r\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} 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 const socket: Socket = createSocketIoClient(input.baseUrl, {\n transports: ['websocket'],\n forceNew: true,\n reconnection: false,\n auth: input.token ? { token: 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 await waitForConnect(socket);\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,\r\n//? rate-limit, fuzz, custom) against a running server and returns a single\r\n//? combined summary. Consumers call this from their own thin script that\r\n//? imports the generated `apiMethodMap` + `apiInputSchemas`.\r\n\r\nimport { clearAllRateLimits, getProjectConfig } from '@luckystack/core';\r\n\r\nimport { sampleSchemaInput } from './schemaSampleInput';\r\nimport { runContractTests } from './runContractTests';\r\nimport { runAuthEnforcementTests } from './runAuthEnforcementTests';\r\nimport { runRateLimitTests } from './runRateLimitTests';\r\nimport { runFuzzTests } from './runFuzzTests';\r\nimport { runCustomTests } from './customTests';\r\nimport type { CustomTestResult, RunCustomTestsSummary } from './customTests';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\nimport type { ZodType } from 'zod';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\ntype ApiMetaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, {\r\n method: string;\r\n auth: { login: boolean; additional?: Record<string, unknown>[] };\r\n rateLimit?: number | false;\r\n}>>>>>>;\r\ntype ApiInputSchemas = Partial<Record<string, Partial<Record<string, Partial<Record<string, ZodType | undefined>>>>>>;\r\n\r\nexport interface RunAllTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n apiMetaMap: ApiMetaMap;\r\n apiInputSchemas: ApiInputSchemas;\r\n baseUrl: string;\r\n /** Cookie name for the session token. Defaults to `luckystack_token`. */\r\n sessionCookieName?: string;\r\n /** Auth token applied as a `Cookie` header to sweep-layer requests that need a session. */\r\n authToken?: string;\r\n /** Endpoints to skip from the sweep layers, e.g. routes that need a file upload. */\r\n skip?: string[];\r\n /** Substring filter applied to `<page>/<name>/<version>`. */\r\n filter?: string;\r\n /** Disable individual layers (faster local iteration). */\r\n noSweep?: boolean;\r\n noFuzz?: boolean;\r\n noRateLimit?: boolean;\r\n noCustom?: boolean;\r\n}\r\n\r\nexport interface RunAllTestsSummary {\r\n contract?: RunContractSummary;\r\n auth?: RunContractSummary;\r\n rateLimit?: RunContractSummary;\r\n fuzz?: RunContractSummary;\r\n custom?: RunCustomTestsSummary;\r\n totalPassed: number;\r\n totalFailed: number;\r\n}\r\n\r\nconst inputForEndpoint = (apiInputSchemas: ApiInputSchemas) =>\r\n (endpoint: EndpointDescriptor): unknown => {\r\n const schema = apiInputSchemas[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n return schema ? sampleSchemaInput(schema) : {};\r\n };\r\n\r\nconst matchesFilter = (endpoint: EndpointDescriptor, filter: string | undefined): boolean => {\r\n if (!filter) return true;\r\n return `${endpoint.page}/${endpoint.name}/${endpoint.version}`.includes(filter);\r\n};\r\n\r\nconst filterResults = (results: ContractCheckResult[], filter: string | undefined): ContractCheckResult[] =>\r\n filter ? results.filter(r => matchesFilter(r.endpoint, filter)) : results;\r\n\r\nconst cloneSummary = (summary: RunContractSummary, filter: string | undefined): RunContractSummary => {\r\n if (!filter) return summary;\r\n const results = filterResults(summary.results, filter);\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n\r\nexport const runAllTests = async (input: RunAllTestsInput): Promise<RunAllTestsSummary> => {\r\n const summary: RunAllTestsSummary = { totalPassed: 0, totalFailed: 0 };\r\n const headers: Record<string, string> = {};\r\n if (input.authToken) {\r\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\r\n headers.Cookie = `${cookieName}=${input.authToken}`;\r\n }\r\n const inputFor = inputForEndpoint(input.apiInputSchemas);\r\n\r\n if (!input.noSweep) {\r\n const contract = await runContractTests({\r\n apiMethodMap: input.apiMethodMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n headers,\r\n inputFor,\r\n });\r\n summary.contract = cloneSummary(contract, input.filter);\r\n\r\n const auth = await runAuthEnforcementTests({\r\n apiMethodMap: input.apiMethodMap,\r\n apiMetaMap: input.apiMetaMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n inputFor,\r\n });\r\n summary.auth = cloneSummary(auth, input.filter);\r\n\r\n if (!input.noRateLimit) {\r\n const rateLimit = await runRateLimitTests({\r\n apiMethodMap: input.apiMethodMap,\r\n apiMetaMap: input.apiMetaMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n headers,\r\n inputFor,\r\n });\r\n summary.rateLimit = cloneSummary(rateLimit, input.filter);\r\n }\r\n\r\n if (!input.noFuzz) {\r\n const fuzz = await runFuzzTests({\r\n apiMethodMap: input.apiMethodMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n headers,\r\n });\r\n summary.fuzz = cloneSummary(fuzz, input.filter);\r\n }\r\n }\r\n\r\n if (!input.noCustom) {\r\n //? The rate-limit layer drains per-route buckets; clear them before the\r\n //? custom layer so business-logic tests on low-limit routes (e.g.\r\n //? confirmReset, sendReset) aren't spuriously rejected with\r\n //? api.rateLimitExceeded. Runs in-process against the same Redis the server\r\n //? uses. Non-fatal if the active strategy has no clear().\r\n try {\r\n await clearAllRateLimits();\r\n } catch {\r\n //? swallow — a missing clear() is a test-quality degrade, not a crash.\r\n }\r\n const custom = await runCustomTests({\r\n baseUrl: input.baseUrl,\r\n sessionCookieName: input.sessionCookieName,\r\n filter: input.filter,\r\n //? Pass the generated method map so the harness invokes each API route\r\n //? with its declared HTTP method (e.g. logout is DELETE, not POST).\r\n apiMethodMap: input.apiMethodMap,\r\n });\r\n summary.custom = custom;\r\n }\r\n\r\n summary.totalPassed = (summary.contract?.passed ?? 0)\r\n + (summary.auth?.passed ?? 0)\r\n + (summary.rateLimit?.passed ?? 0)\r\n + (summary.fuzz?.passed ?? 0)\r\n + (summary.custom?.passed ?? 0);\r\n summary.totalFailed = (summary.contract?.failed ?? 0)\r\n + (summary.auth?.failed ?? 0)\r\n + (summary.rateLimit?.failed ?? 0)\r\n + (summary.fuzz?.failed ?? 0)\r\n + (summary.custom?.failed ?? 0);\r\n\r\n return summary;\r\n};\r\n\r\n//? ── Colored, list-based reporting ───────────────────────────────────────\r\n//? Honors NO_COLOR / FORCE_COLOR (https://no-color.org) and falls back to\r\n//? plain text when stdout is not a TTY (piped to a file / CI log).\r\nconst useColor = ((): boolean => {\r\n if (process.env.NO_COLOR) return false;\r\n if (process.env.FORCE_COLOR) return true;\r\n return process.stdout.isTTY;\r\n})();\r\nconst paint = (code: string, text: string): string => (useColor ? `\\u001B[${code}m${text}\\u001B[0m` : text);\r\nconst green = (t: string): string => paint('32', t);\r\nconst red = (t: string): string => paint('31', t);\r\nconst yellow = (t: string): string => paint('33', t);\r\nconst dim = (t: string): string => paint('2', t);\r\nconst bold = (t: string): string => paint('1', t);\r\n\r\ninterface ReportRow {\r\n label: string;\r\n reason?: string;\r\n}\r\n\r\nconst xfailOf = (s: RunContractSummary | RunCustomTestsSummary | undefined): number =>\r\n s && 'xfailed' in s ? s.xfailed : 0;\r\nconst skippedOf = (s: RunContractSummary | RunCustomTestsSummary | undefined): number =>\r\n s && 'skipped' in s ? s.skipped : 0;\r\n\r\n//? Per-layer headline: green \"X/Y passed\" when clean, red \"Z failed\" when not,\r\n//? plus dim xfail / skipped counts. This is the \"14/20 in groen, 6/20 in rood\"\r\n//? the report leads with.\r\nconst formatLayerLine = (label: string, s: RunContractSummary | RunCustomTestsSummary | undefined): string => {\r\n if (!s) return ` ${dim('–')} ${label.padEnd(18)} ${dim('(layer skipped)')}`;\r\n const clean = s.failed === 0;\r\n const mark = clean ? green('✓') : red('✗');\r\n const head = clean\r\n ? green(`${s.passed}/${s.total} passed`)\r\n : `${green(`${s.passed}/${s.total} passed`)} ${red(`${s.failed}/${s.total} failed`)}`;\r\n const extras: string[] = [];\r\n const xf = xfailOf(s);\r\n const sk = skippedOf(s);\r\n if (xf) extras.push(yellow(`${xf} expected-fail`));\r\n if (sk) extras.push(dim(`${sk} skipped`));\r\n const extra = extras.length > 0 ? ` ${dim('·')} ${extras.join(dim(', '))}` : '';\r\n return ` ${mark} ${label.padEnd(18)} ${head}${extra}`;\r\n};\r\n\r\nconst sweepFailRows = (s: RunContractSummary | undefined): ReportRow[] =>\r\n (s?.results ?? [])\r\n .filter((r) => r.status === 'fail')\r\n .map((r) => ({\r\n label: `${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\r\n reason: r.errorCode ? `${r.reason ?? 'failed'} [${r.errorCode}]` : r.reason,\r\n }));\r\n\r\nconst sweepSkipRows = (layer: string, s: RunContractSummary | undefined): ReportRow[] =>\r\n (s?.results ?? [])\r\n .filter((r) => r.status === 'skipped')\r\n .map((r) => ({\r\n label: `${layer}: ${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\r\n reason: r.reason,\r\n }));\r\n\r\nconst customRows = (s: RunCustomTestsSummary | undefined, status: CustomTestResult['status']): ReportRow[] =>\r\n (s?.results ?? [])\r\n .filter((r) => r.status === status)\r\n .map((r) => ({ label: `${r.routePath} :: ${r.caseName}`, reason: r.reason }));\r\n\r\nconst printSection = (title: (t: string) => string, heading: string, rows: ReportRow[], bullet: string): void => {\r\n if (rows.length === 0) return;\r\n console.log('');\r\n console.log(title(bold(`${heading} (${rows.length})`)));\r\n for (const row of rows) {\r\n console.log(` ${title(bullet)} ${row.label}`);\r\n if (row.reason) console.log(` ${dim(row.reason)}`);\r\n }\r\n};\r\n\r\nexport const logRunAllSummary = (summary: RunAllTestsSummary): void => {\r\n console.log('');\r\n console.log(formatLayerLine('contract', summary.contract));\r\n console.log(formatLayerLine('auth-enforcement', summary.auth));\r\n console.log(formatLayerLine('rate-limit', summary.rateLimit));\r\n console.log(formatLayerLine('fuzz', summary.fuzz));\r\n console.log(formatLayerLine('custom', summary.custom));\r\n\r\n //? Real failures across every layer — these MUST be fixed (a green test that\r\n //? went red, or a wrong test). Sweep failures + un-marked custom failures.\r\n const failed: ReportRow[] = [\r\n ...sweepFailRows(summary.contract),\r\n ...sweepFailRows(summary.auth),\r\n ...sweepFailRows(summary.rateLimit),\r\n ...sweepFailRows(summary.fuzz),\r\n ...customRows(summary.custom, 'fail'),\r\n ];\r\n printSection(red, 'Failed — must be fixed (real bugs / wrong tests)', failed, '✗');\r\n\r\n //? Known, accepted failures — red is OK here; they are tracked, not regressions.\r\n printSection(yellow, 'Expected failures — known issues, allowed to fail', customRows(summary.custom, 'xfail'), '⚠');\r\n\r\n //? Stale markers — these passed but are still flagged expectedToFail.\r\n printSection(yellow, 'Unexpectedly passed — remove the expectedToFail marker', customRows(summary.custom, 'xpass'), '?');\r\n\r\n //? Skipped with reason — not run in this mode (login-gated, high rate-limit, …).\r\n const skipped: ReportRow[] = [\r\n ...sweepSkipRows('contract', summary.contract),\r\n ...sweepSkipRows('auth', summary.auth),\r\n ...sweepSkipRows('rate-limit', summary.rateLimit),\r\n ...sweepSkipRows('fuzz', summary.fuzz),\r\n ];\r\n printSection(dim, 'Skipped — not run (with reason)', skipped, '–');\r\n\r\n const totalXfail = xfailOf(summary.contract) + xfailOf(summary.auth) + xfailOf(summary.rateLimit)\r\n + xfailOf(summary.fuzz) + xfailOf(summary.custom);\r\n const totalSkipped = skippedOf(summary.contract) + skippedOf(summary.auth) + skippedOf(summary.rateLimit)\r\n + skippedOf(summary.fuzz) + skippedOf(summary.custom);\r\n\r\n console.log('');\r\n const parts = [\r\n green(`${summary.totalPassed} passed`),\r\n summary.totalFailed > 0 ? red(`${summary.totalFailed} failed`) : dim('0 failed'),\r\n totalXfail > 0 ? yellow(`${totalXfail} expected-fail`) : dim('0 expected-fail'),\r\n totalSkipped > 0 ? yellow(`${totalSkipped} skipped`) : dim('0 skipped'),\r\n ];\r\n console.log(`Summary: ${parts.join(dim(' · '))}`);\r\n console.log(dim(' legend: ✗ red = must fix · ⚠ yellow = known/allowed · – skipped (not run)'));\r\n};\r\n","//? Extension registry for the test runner. Consumers plug in custom test\r\n//? layers, realistic-payload fixtures, and result reporters without\r\n//? forking the framework.\r\n//?\r\n//? Three slots:\r\n//?\r\n//? 1. Test layers — additional checks per endpoint (CORS enforcement,\r\n//? business rules, custom auth schemes, multi-tenant isolation).\r\n//? 2. Fixtures — realistic input payloads (valid + invalid) per typeKey\r\n//? so the fuzz layer can use them before falling back to schema-random.\r\n//? 3. Reporters — per-result and per-summary callbacks + optional\r\n//? webhook URL the runner POSTs the batch summary to.\r\n//?\r\n//? All slots are append-only at registration time; clearing requires\r\n//? `resetTestExtensionsForTests()` (kept out of the production surface\r\n//? to prevent accidental wipes from a misbehaving plugin).\r\n\r\nexport interface TestLayerInput {\r\n /** Resolved route name (`api/billing/getInvoice/v1`). */\r\n endpoint: string;\r\n /** Method when applicable, undefined for sync endpoints. */\r\n method?: string;\r\n /** Bearer token / session token to use for auth-aware checks. */\r\n authToken?: string;\r\n}\r\n\r\nexport interface TestLayerResult {\r\n passed: boolean;\r\n /** Short reason key — i18n-style errorCode when failed. */\r\n message?: string;\r\n /** Optional extras for the reporter (latency, response sample, ...). */\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TestLayer {\r\n name: string;\r\n run: (input: TestLayerInput) => Promise<TestLayerResult> | TestLayerResult;\r\n}\r\n\r\nexport interface TestFixture<TPayload = unknown> {\r\n valid: TPayload[];\r\n invalid: TPayload[];\r\n}\r\n\r\nexport interface TestResult {\r\n layer: string;\r\n endpoint: string;\r\n passed: boolean;\r\n message?: string;\r\n durationMs?: number;\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TestSummary {\r\n totalLayers: number;\r\n totalEndpoints: number;\r\n passed: number;\r\n failed: number;\r\n results: TestResult[];\r\n}\r\n\r\nexport interface TestReporter {\r\n /** Called once per individual TestResult as the runner progresses. */\r\n onResult?: (result: TestResult) => void | Promise<void>;\r\n /** Called once at the end with the aggregated summary. */\r\n onSummary?: (summary: TestSummary) => void | Promise<void>;\r\n /** Optional webhook the runner POSTs the summary to. */\r\n webhookUrl?: string;\r\n webhookAuth?: { type: 'bearer'; token: string };\r\n}\r\n\r\nconst layers = new Map<string, TestLayer>();\r\nconst fixtures = new Map<string, TestFixture>();\r\nlet reporter: TestReporter | null = null;\r\n\r\n/**\r\n * Register a custom test layer. Re-registering the same name replaces\r\n * the previous entry. Use for CORS enforcement, business-rule checks,\r\n * custom auth schemes, multi-tenant isolation, GDPR data-flow checks.\r\n */\r\nexport const registerTestLayer = (layer: TestLayer): void => {\r\n layers.set(layer.name, layer);\r\n};\r\n\r\nexport const listTestLayers = (): TestLayer[] => [...layers.values()];\r\n\r\n/**\r\n * Register a realistic payload fixture for a specific type key. The fuzz\r\n * layer prefers these over schema-random generated inputs when a fixture\r\n * exists for the endpoint's input type.\r\n *\r\n * `valid` payloads are used to verify happy-path behavior; `invalid`\r\n * payloads probe the validator's error responses.\r\n */\r\nexport const registerTestFixture = <TPayload = unknown>(typeKey: string, fixture: TestFixture<TPayload>): void => {\r\n fixtures.set(typeKey, fixture);\r\n};\r\n\r\nexport const getTestFixture = (typeKey: string): TestFixture | undefined => fixtures.get(typeKey);\r\n\r\n/**\r\n * Register a test reporter. Set `null` to unregister. The runner calls\r\n * `onResult` per individual result as the run progresses and `onSummary`\r\n * once at the end. When `webhookUrl` is set, the runner additionally\r\n * POSTs the JSON-serialised summary to that URL.\r\n */\r\nexport const registerTestReporter = (newReporter: TestReporter | null): void => {\r\n reporter = newReporter;\r\n};\r\n\r\nexport const getTestReporter = (): TestReporter | null => reporter;\r\n\r\nexport const resetTestExtensionsForTests = (): void => {\r\n layers.clear();\r\n fixtures.clear();\r\n reporter = null;\r\n};\r\n"],"mappings":"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","names":["tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","shouldSkip","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","EXPECTED_ERROR_CODE","tryCatch","requiresLogin","shouldSkip","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","shouldSkip","tryCatch","tryCatch","tryCatch","getProjectConfig","getProjectConfig"]}
|
|
1
|
+
{"version":3,"sources":["../src/walkEndpoints.ts","../src/contractCheck.ts","../src/runContractTests.ts","../src/authEnforcementCheck.ts","../src/runAuthEnforcementTests.ts","../src/rateLimitCheck.ts","../src/resetServerState.ts","../src/runRateLimitTests.ts","../src/fuzzCheck.ts","../src/runFuzzTests.ts","../src/schemaSampleInput.ts","../src/reporter.ts","../src/customTests.ts","../src/streamWatcher.ts","../src/runAllTests.ts","../src/extensionRegistry.ts"],"sourcesContent":["import type { EndpointDescriptor, HttpMethod } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\n\r\nexport const walkEndpoints = (apiMethodMap: ApiMethodMap): EndpointDescriptor[] => {\r\n const endpoints: EndpointDescriptor[] = [];\r\n for (const [page, nameMap] of Object.entries(apiMethodMap)) {\r\n if (!nameMap) continue;\r\n for (const [name, versionMap] of Object.entries(nameMap)) {\r\n if (!versionMap) continue;\r\n for (const [version, method] of Object.entries(versionMap)) {\r\n if (method === undefined) continue;\r\n endpoints.push({\r\n page,\r\n name,\r\n version,\r\n method: method as HttpMethod,\r\n fullPath: `api/${page}/${name}/${version}`,\r\n });\r\n }\r\n }\r\n }\r\n return endpoints;\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\r\n\r\nexport interface ContractCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n /**\r\n * Optional input factory. Defaults to `{}`. When an endpoint requires a\r\n * specific shape to not crash, callers can override per-endpoint here.\r\n */\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n /**\r\n * Optional headers (auth tokens, cookies). Applied to every request.\r\n */\r\n headers?: Record<string, string>;\r\n /**\r\n * Per-call request timeout in ms. Defaults to 5000. Bump for slow endpoints\r\n * (AI calls, large reports) where the framework default would false-fail\r\n * the contract check.\r\n */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\n//? Contract layer: send any-shape input, accept any response where\r\n//? `status` is `success` or `error` (with an `errorCode`). Anything else is\r\n//? a contract violation — 5xx, thrown exceptions, or shapes outside this\r\n//? envelope.\r\n//?\r\n//? The goal here is NOT to prove every endpoint works end-to-end; it's to\r\n//? catch endpoints that crash the server or return garbage on well-formed\r\n//? empty input. Real behavior tests go in the per-endpoint test file.\r\nexport const runContractCheck = async (input: ContractCheckInput): Promise<ContractCheckResult> => {\r\n const { endpoint, baseUrl } = input;\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const body = input.inputFor ? input.inputFor(endpoint) : {};\r\n const started = Date.now();\r\n\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n\r\n const [fetchError, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n //? Browsers attach Origin on state-changing requests; the server's origin\r\n //? policy fail-closes POST/PUT/DELETE without one. Send the same-origin\r\n //? value so the request reaches the API pipeline (consumers can override).\r\n 'Origin': new URL(baseUrl).origin,\r\n ...input.headers,\r\n },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n\r\n if (fetchError || !response) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n reason: fetchError?.message ?? 'fetch returned no response',\r\n durationMs: Date.now() - started,\r\n };\r\n }\r\n\r\n const [parseError, parsed] = await tryCatch<{ status?: 'success' | 'error'; errorCode?: string } | null, undefined>(\r\n async () => await response.json() as { status?: 'success' | 'error'; errorCode?: string },\r\n );\r\n\r\n const durationMs = Date.now() - started;\r\n\r\n if (parseError) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: `JSON parse failed: ${parseError.message}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (!parsed || (parsed.status !== 'success' && parsed.status !== 'error')) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: 'Response missing standard `status` envelope',\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed.status === 'error' && !parsed.errorCode) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'error',\r\n reason: 'Error response missing `errorCode`',\r\n durationMs,\r\n };\r\n }\r\n\r\n return {\r\n endpoint,\r\n status: 'pass',\r\n httpStatus: response.status,\r\n responseStatus: parsed.status,\r\n errorCode: parsed.errorCode,\r\n durationMs,\r\n };\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\r\nimport { runContractCheck } from './contractCheck';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\n\r\nexport interface RunContractTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n baseUrl: string;\r\n /**\r\n * Endpoints to skip. Matched against `<page>/<name>` (version-agnostic) and\r\n * `<page>/<name>/<version>` (version-specific). Useful for endpoints that\r\n * are known to need real input (e.g. a file upload).\r\n */\r\n skip?: string[];\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n headers?: Record<string, string>;\r\n onResult?: (result: ContractCheckResult) => void;\r\n}\r\n\r\nconst shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\r\n if (skip.length === 0) return false;\r\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\r\n const versionless = `${endpoint.page}/${endpoint.name}`;\r\n return skip.includes(versioned) || skip.includes(versionless);\r\n};\r\n\r\nexport const runContractTests = async (input: RunContractTestsInput): Promise<RunContractSummary> => {\r\n const endpoints = walkEndpoints(input.apiMethodMap);\r\n const skip = input.skip ?? [];\r\n const results: ContractCheckResult[] = [];\r\n\r\n for (const endpoint of endpoints) {\r\n if (shouldSkip(endpoint, skip)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'Explicitly skipped',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n //? Serial rather than parallel so we don't trip rate limiters or mask\r\n //? endpoint ordering bugs. Swap to a pool later if walk-time matters.\r\n const result = await runContractCheck({\r\n endpoint,\r\n baseUrl: input.baseUrl,\r\n inputFor: input.inputFor,\r\n headers: input.headers,\r\n });\r\n results.push(result);\r\n input.onResult?.(result);\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\r\n\r\n//? Canonical error code the framework emits for unauthenticated requests\r\n//? to `auth.login: true` endpoints. See packages/api/src/handleHttpApiRequest.ts.\r\nconst EXPECTED_ERROR_CODE = 'auth.required';\r\n\r\nexport interface AuthEnforcementCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n /** Per-call request timeout in ms. Defaults to 5000. */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\n//? Auth-enforcement layer: for each `auth.login: true` endpoint, call it with\r\n//? NO session headers/cookies. Expect the server to reject with `auth.required`.\r\n//?\r\n//? A passing endpoint here proves the login guard isn't silently bypassed.\r\n//? Endpoints that return `success` without a session are a critical finding.\r\nexport const runAuthEnforcementCheck = async (\r\n input: AuthEnforcementCheckInput,\r\n): Promise<ContractCheckResult> => {\r\n const { endpoint, baseUrl } = input;\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const body = input.inputFor ? input.inputFor(endpoint) : {};\r\n const started = Date.now();\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n\r\n const [fetchError, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n //? No session cookie, no auth header — deliberately. Origin IS sent so the\r\n //? server's origin policy doesn't 403 before the auth check can run.\r\n headers: { 'Content-Type': 'application/json', 'Origin': new URL(baseUrl).origin },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n\r\n if (fetchError || !response) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n reason: fetchError?.message ?? 'fetch returned no response',\r\n durationMs: Date.now() - started,\r\n };\r\n }\r\n\r\n const [parseError, parsed] = await tryCatch<{ status?: 'success' | 'error'; errorCode?: string } | null, undefined>(\r\n async () => await response.json() as { status?: 'success' | 'error'; errorCode?: string },\r\n );\r\n const durationMs = Date.now() - started;\r\n\r\n if (parseError) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: `JSON parse failed: ${parseError.message}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed?.status === 'success') {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'success',\r\n reason: `auth.login endpoint returned success without a session`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed?.status !== 'error') {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'unknown',\r\n reason: 'Response missing standard `status` envelope',\r\n durationMs,\r\n };\r\n }\r\n\r\n if (parsed.errorCode !== EXPECTED_ERROR_CODE) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: response.status,\r\n responseStatus: 'error',\r\n errorCode: parsed.errorCode,\r\n reason: `expected errorCode '${EXPECTED_ERROR_CODE}' but got '${parsed.errorCode ?? '(missing)'}'`,\r\n durationMs,\r\n };\r\n }\r\n\r\n return {\r\n endpoint,\r\n status: 'pass',\r\n httpStatus: response.status,\r\n responseStatus: 'error',\r\n errorCode: parsed.errorCode,\r\n durationMs,\r\n };\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\r\nimport { runAuthEnforcementCheck } from './authEnforcementCheck';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\ntype ApiMetaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, {\r\n method: string;\r\n auth: { login: boolean; additional?: Record<string, unknown>[] };\r\n rateLimit?: number | false;\r\n}>>>>>>;\r\n\r\nexport interface RunAuthEnforcementTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n apiMetaMap: ApiMetaMap;\r\n baseUrl: string;\r\n skip?: string[];\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n onResult?: (result: ContractCheckResult) => void;\r\n}\r\n\r\nconst shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\r\n if (skip.length === 0) return false;\r\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\r\n const versionless = `${endpoint.page}/${endpoint.name}`;\r\n return skip.includes(versioned) || skip.includes(versionless);\r\n};\r\n\r\nconst requiresLogin = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\r\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n return meta?.auth.login ?? false;\r\n};\r\n\r\nexport const runAuthEnforcementTests = async (\r\n input: RunAuthEnforcementTestsInput,\r\n): Promise<RunContractSummary> => {\r\n const endpoints = walkEndpoints(input.apiMethodMap);\r\n const skip = input.skip ?? [];\r\n const results: ContractCheckResult[] = [];\r\n\r\n for (const endpoint of endpoints) {\r\n if (!requiresLogin(input.apiMetaMap, endpoint)) {\r\n //? Public endpoints can't be tested by this layer — skip silently, no\r\n //? noise. They're still covered by the contract layer.\r\n continue;\r\n }\r\n\r\n if (shouldSkip(endpoint, skip)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'Explicitly skipped',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n const result = await runAuthEnforcementCheck({\r\n endpoint,\r\n baseUrl: input.baseUrl,\r\n inputFor: input.inputFor,\r\n });\r\n results.push(result);\r\n input.onResult?.(result);\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000;\r\nconst EXPECTED_ERROR_CODE = 'api.rateLimitExceeded';\r\n\r\nexport interface RateLimitCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n rateLimit: number;\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n headers?: Record<string, string>;\r\n /** Per-call request timeout in ms. Defaults to 10000. */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\n//? Rate-limit layer: fire `rateLimit + 1` requests back to back and assert the\r\n//? last one is rejected with `api.rateLimitExceeded`. All preceding requests\r\n//? are allowed to return either success or any other envelope — only the N+1\r\n//? matters for this layer.\r\n//?\r\n//? This layer mutates server-side state (limiter counters). Run it after the\r\n//? contract + auth layers, or against a dedicated test instance. The server's\r\n//? clearAllRateLimits() utility can reset state between runs.\r\nexport const runRateLimitCheck = async (input: RateLimitCheckInput): Promise<ContractCheckResult> => {\r\n const { endpoint, baseUrl, rateLimit } = input;\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const body = input.inputFor ? input.inputFor(endpoint) : {};\r\n const started = Date.now();\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n\r\n const send = async (): Promise<Response | null> => {\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n const [error, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Origin': new URL(baseUrl).origin,\r\n ...input.headers,\r\n },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n if (error || !response) return null;\r\n return response;\r\n };\r\n\r\n //? Fire `rateLimit` allowed requests first (drain the bucket), then a final\r\n //? request that should be blocked. Serial so the server sees a clean order.\r\n for (let i = 0; i < rateLimit; i += 1) {\r\n await send();\r\n }\r\n\r\n const final = await send();\r\n const durationMs = Date.now() - started;\r\n\r\n if (!final) {\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n reason: 'Final rate-limit probe request failed to return a response',\r\n durationMs,\r\n };\r\n }\r\n\r\n const [, parsed] = await tryCatch<{ status?: 'success' | 'error'; errorCode?: string } | null, undefined>(\r\n async () => await final.json() as { status?: 'success' | 'error'; errorCode?: string },\r\n );\r\n\r\n if (parsed?.status === 'error' && parsed.errorCode === EXPECTED_ERROR_CODE) {\r\n return {\r\n endpoint,\r\n status: 'pass',\r\n httpStatus: final.status,\r\n responseStatus: 'error',\r\n errorCode: parsed.errorCode,\r\n durationMs,\r\n };\r\n }\r\n\r\n return {\r\n endpoint,\r\n status: 'fail',\r\n httpStatus: final.status,\r\n responseStatus: parsed?.status ?? 'unknown',\r\n errorCode: parsed?.errorCode,\r\n reason: `expected '${EXPECTED_ERROR_CODE}' on request ${rateLimit + 1} but got ${parsed?.status ?? 'none'}/${parsed?.errorCode ?? '(none)'}`,\r\n durationMs,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\n\r\n//? Calls the server's /_test/reset endpoint. Used between rate-limit test\r\n//? endpoints to drain the shared IP-based limiter bucket so one endpoint's\r\n//? N+1 assertion doesn't consume the next endpoint's window.\r\n//?\r\n//? /_test/reset is gated by `NODE_ENV in { 'development', 'test' }` on the\r\n//? server side AND requires `TEST_RESET_TOKEN` to be set unconditionally\r\n//? (no token = 403). For staging/preview deploys that enable it for CI,\r\n//? set `TEST_RESET_TOKEN` on the server and pass the same value here.\r\n\r\nexport interface ResetServerStateInput {\r\n baseUrl: string;\r\n token?: string;\r\n}\r\n\r\nexport const resetServerState = async ({ baseUrl, token }: ResetServerStateInput): Promise<boolean> => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/_test/reset`;\r\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\r\n if (token) headers['X-Test-Reset-Token'] = token;\r\n\r\n const [error, response] = await tryCatch(() => fetch(url, { method: 'POST', headers }));\r\n if (error || !response) return false;\r\n return response.ok;\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\r\nimport { runRateLimitCheck } from './rateLimitCheck';\r\nimport { resetServerState } from './resetServerState';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\ntype ApiMetaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, {\r\n method: string;\r\n auth: { login: boolean; additional?: Record<string, unknown>[] };\r\n rateLimit?: number | false;\r\n}>>>>>>;\r\n\r\nexport interface RunRateLimitTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n apiMetaMap: ApiMetaMap;\r\n baseUrl: string;\r\n skip?: string[];\r\n inputFor?: (endpoint: EndpointDescriptor) => unknown;\r\n headers?: Record<string, string>;\r\n /**\r\n * Max rateLimit value to test. Endpoints with a higher limit are skipped\r\n * to avoid firing thousands of requests in CI.\r\n */\r\n maxRateLimitToTest?: number;\r\n /**\r\n * When true, hit `/_test/reset` before each endpoint so limiter state is\r\n * clean. Requires the server to allow the endpoint (NODE_ENV !== production\r\n * or `TEST_RESET_TOKEN` configured). Without resetting, the shared IP bucket\r\n * leaks state between endpoints.\r\n */\r\n resetBetweenEndpoints?: boolean;\r\n /**\r\n * Token forwarded as `X-Test-Reset-Token` when resetting. Only relevant in\r\n * staging/preview deploys that enable the endpoint behind a token.\r\n */\r\n resetToken?: string;\r\n onResult?: (result: ContractCheckResult) => void;\r\n}\r\n\r\nconst getRateLimit = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): number | null => {\r\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n if (!meta || meta.rateLimit === false || meta.rateLimit === undefined) return null;\r\n return meta.rateLimit;\r\n};\r\n\r\nconst requiresLogin = (apiMetaMap: ApiMetaMap, endpoint: EndpointDescriptor): boolean => {\r\n const meta = apiMetaMap[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n return meta?.auth.login ?? false;\r\n};\r\n\r\nconst shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\r\n if (skip.length === 0) return false;\r\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\r\n const versionless = `${endpoint.page}/${endpoint.name}`;\r\n return skip.includes(versioned) || skip.includes(versionless);\r\n};\r\n\r\nexport const runRateLimitTests = async (\r\n input: RunRateLimitTestsInput,\r\n): Promise<RunContractSummary> => {\r\n const endpoints = walkEndpoints(input.apiMethodMap);\r\n const skip = input.skip ?? [];\r\n const maxRateLimit = input.maxRateLimitToTest ?? 50;\r\n //? A login-required route returns `auth.required` BEFORE the rate limiter\r\n //? runs, so it can only be rate-limit-tested with a session. A Cookie header\r\n //? means an authenticated sweep. We deliberately do NOT auto-authenticate the\r\n //? sweep — that would execute real mutations (delete/update) with junk input.\r\n //? Set TEST_AUTH_TOKEN to opt in and cover login-gated routes.\r\n const isAuthenticatedSweep = Boolean(input.headers?.Cookie);\r\n const results: ContractCheckResult[] = [];\r\n\r\n for (const endpoint of endpoints) {\r\n const rateLimit = getRateLimit(input.apiMetaMap, endpoint);\r\n if (rateLimit === null) continue;\r\n\r\n if (!isAuthenticatedSweep && requiresLogin(input.apiMetaMap, endpoint)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'login-required route — set TEST_AUTH_TOKEN to rate-limit-test it (unauthenticated calls hit auth.required before the limiter)',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n if (rateLimit > maxRateLimit) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: `rateLimit ${rateLimit} exceeds maxRateLimitToTest=${maxRateLimit}`,\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n if (shouldSkip(endpoint, skip)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'Explicitly skipped',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n if (input.resetBetweenEndpoints) {\r\n await resetServerState({ baseUrl: input.baseUrl, token: input.resetToken });\r\n }\r\n\r\n const result = await runRateLimitCheck({\r\n endpoint,\r\n baseUrl: input.baseUrl,\r\n rateLimit,\r\n inputFor: input.inputFor,\r\n headers: input.headers,\r\n });\r\n results.push(result);\r\n input.onResult?.(result);\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n","import { tryCatch } from '@luckystack/core';\r\nimport type { ContractCheckResult, EndpointDescriptor } from './types';\r\n\r\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5000;\r\n\r\n//? Lightweight fuzz layer — no Zod yet. Sends well-known junk shapes and\r\n//? asserts the server doesn't 5xx or return a non-envelope body. Real\r\n//? schema-driven fuzzing (TS type -> random valid input) is deferred until\r\n//? the generator emits Zod schemas alongside the types.\r\nconst JUNK_PAYLOADS: unknown[] = [\r\n null,\r\n [],\r\n [1, 2, 3],\r\n 'string-instead-of-object',\r\n 1_234_567_890,\r\n true,\r\n { nested: { deeply: { nested: { value: 'x'.repeat(10_000) } } } },\r\n { __proto__: { polluted: true } },\r\n { key: null, other: undefined, third: Number.NaN },\r\n];\r\n\r\nexport interface FuzzCheckInput {\r\n endpoint: EndpointDescriptor;\r\n baseUrl: string;\r\n headers?: Record<string, string>;\r\n /** Per-call request timeout in ms. Defaults to 5000. */\r\n requestTimeoutMs?: number;\r\n}\r\n\r\nconst probe = async (\r\n endpoint: EndpointDescriptor,\r\n baseUrl: string,\r\n body: unknown,\r\n headers: Record<string, string> | undefined,\r\n requestTimeoutMs: number,\r\n): Promise<{ httpStatus: number; parsed: { status?: unknown; errorCode?: unknown } | null } | null> => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/${endpoint.fullPath}`;\r\n const controller = new AbortController();\r\n const timeoutHandle = setTimeout(() => { controller.abort(); }, requestTimeoutMs);\r\n const [fetchError, response] = await tryCatch(() => fetch(url, {\r\n method: endpoint.method,\r\n headers: { 'Content-Type': 'application/json', 'Origin': new URL(baseUrl).origin, ...headers },\r\n body: endpoint.method === 'GET' ? undefined : JSON.stringify(body),\r\n signal: controller.signal,\r\n }));\r\n clearTimeout(timeoutHandle);\r\n if (fetchError || !response) return null;\r\n\r\n const [, parsed] = await tryCatch<{ status?: unknown; errorCode?: unknown } | null, undefined>(\r\n async () => await response.json() as { status?: unknown; errorCode?: unknown },\r\n );\r\n return { httpStatus: response.status, parsed: parsed ?? null };\r\n};\r\n\r\nexport const runFuzzCheck = async (input: FuzzCheckInput): Promise<ContractCheckResult> => {\r\n const started = Date.now();\r\n const requestTimeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\r\n\r\n for (const payload of JUNK_PAYLOADS) {\r\n const result = await probe(input.endpoint, input.baseUrl, payload, input.headers, requestTimeoutMs);\r\n const durationMs = Date.now() - started;\r\n\r\n if (!result) {\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'fail',\r\n reason: `fuzz probe crashed (no response) with payload: ${JSON.stringify(payload).slice(0, 80)}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n //? A 5xx that STILL returns a valid `{ status:'error', errorCode }` envelope\r\n //? is the framework gracefully catching an error (e.g. a route designed to\r\n //? throw), not a crash. The fuzz layer's concern is crashes / hangs /\r\n //? garbage — so we only fail on a non-envelope body below (which catches raw\r\n //? 5xx, HTML error pages, and truncated responses). A controlled 500\r\n //? envelope passes.\r\n if (!result.parsed || (result.parsed.status !== 'success' && result.parsed.status !== 'error')) {\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'fail',\r\n httpStatus: result.httpStatus,\r\n reason: `fuzz payload produced non-envelope response: ${JSON.stringify(payload).slice(0, 80)}`,\r\n durationMs,\r\n };\r\n }\r\n\r\n if (result.parsed.status === 'error' && typeof result.parsed.errorCode !== 'string') {\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'fail',\r\n httpStatus: result.httpStatus,\r\n reason: `fuzz error response missing errorCode for payload: ${JSON.stringify(payload).slice(0, 80)}`,\r\n durationMs,\r\n };\r\n }\r\n }\r\n\r\n return {\r\n endpoint: input.endpoint,\r\n status: 'pass',\r\n durationMs: Date.now() - started,\r\n };\r\n};\r\n","import { walkEndpoints } from './walkEndpoints';\r\nimport { runFuzzCheck } from './fuzzCheck';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\n\r\nexport interface RunFuzzTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n baseUrl: string;\r\n skip?: string[];\r\n headers?: Record<string, string>;\r\n onResult?: (result: ContractCheckResult) => void;\r\n}\r\n\r\nconst shouldSkip = (endpoint: EndpointDescriptor, skip: string[]): boolean => {\r\n if (skip.length === 0) return false;\r\n const versioned = `${endpoint.page}/${endpoint.name}/${endpoint.version}`;\r\n const versionless = `${endpoint.page}/${endpoint.name}`;\r\n return skip.includes(versioned) || skip.includes(versionless);\r\n};\r\n\r\nexport const runFuzzTests = async (input: RunFuzzTestsInput): Promise<RunContractSummary> => {\r\n const endpoints = walkEndpoints(input.apiMethodMap);\r\n const skip = input.skip ?? [];\r\n const results: ContractCheckResult[] = [];\r\n\r\n for (const endpoint of endpoints) {\r\n if (shouldSkip(endpoint, skip)) {\r\n const skipped: ContractCheckResult = {\r\n endpoint,\r\n status: 'skipped',\r\n durationMs: 0,\r\n reason: 'Explicitly skipped',\r\n };\r\n results.push(skipped);\r\n input.onResult?.(skipped);\r\n continue;\r\n }\r\n\r\n const result = await runFuzzCheck({\r\n endpoint,\r\n baseUrl: input.baseUrl,\r\n headers: input.headers,\r\n });\r\n results.push(result);\r\n input.onResult?.(result);\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n","import type { z } from 'zod';\r\n\r\n//? Walk a Zod schema and produce a minimal valid value. Used by the\r\n//? contract/fuzz runners so \"valid input\" tests don't require hand-written\r\n//? fixtures per endpoint. Handles the shapes the generator emits\r\n//? (primitives, literals, unions, optionals, objects, records, arrays).\r\n//?\r\n//? Not a property-based generator — returns the SAME deterministic value\r\n//? every call. For randomized fuzz, swap to `fast-check` in a later session.\r\n\r\nconst sampleForDef = (def: unknown): unknown => {\r\n //? Zod stores its runtime shape on `_def.type` (v3) or `_def.typeName`.\r\n //? Access is untyped because `def` can be any of dozens of internal shapes.\r\n const d = def as { typeName?: string; type?: string; innerType?: { _def: unknown }; values?: unknown[]; value?: unknown; options?: { _def: unknown }[]; shape?: (() => Record<string, { _def: unknown }>) | Record<string, { _def: unknown }>; valueType?: { _def: unknown }; element?: { _def: unknown } };\r\n\r\n const name = d.typeName ?? d.type;\r\n\r\n switch (name) {\r\n case 'ZodString':\r\n case 'string': {\r\n return 'test';\r\n }\r\n case 'ZodNumber':\r\n case 'number': {\r\n return 0;\r\n }\r\n case 'ZodBoolean':\r\n case 'boolean': {\r\n return false;\r\n }\r\n case 'ZodNull':\r\n case 'null': {\r\n return null;\r\n }\r\n case 'ZodUndefined':\r\n case 'undefined': {\r\n return undefined;\r\n }\r\n case 'ZodAny':\r\n case 'any':\r\n case 'ZodUnknown':\r\n case 'unknown': {\r\n return null;\r\n }\r\n case 'ZodLiteral':\r\n case 'literal': {\r\n //? zod 3 stores the literal on `_def.value`; zod 4 stores `_def.values` (array).\r\n return d.value ?? d.values?.[0];\r\n }\r\n case 'ZodOptional':\r\n case 'optional':\r\n case 'ZodNullable':\r\n case 'nullable': {\r\n //? Omitting optional fields is the most conservative valid input.\r\n return undefined;\r\n }\r\n case 'ZodUnion':\r\n case 'union': {\r\n return d.options && d.options.length > 0 && d.options[0] ? sampleForDef(d.options[0]._def) : null;\r\n }\r\n case 'ZodArray':\r\n case 'array': {\r\n return [];\r\n }\r\n case 'ZodObject':\r\n case 'object': {\r\n const out: Record<string, unknown> = {};\r\n //? zod 3 exposes `_def.shape` as a function; zod 4 as a plain object.\r\n const shape = typeof d.shape === 'function' ? d.shape() : (d.shape ?? {});\r\n for (const [key, valueSchema] of Object.entries(shape)) {\r\n const sample = sampleForDef(valueSchema._def);\r\n if (sample !== undefined) out[key] = sample;\r\n }\r\n return out;\r\n }\r\n case 'ZodRecord':\r\n case 'record': {\r\n return {};\r\n }\r\n case 'ZodDate':\r\n case 'date': {\r\n return new Date().toISOString();\r\n }\r\n default: {\r\n return null;\r\n }\r\n }\r\n};\r\n\r\nexport const sampleSchemaInput = (schema: z.ZodType): unknown => {\r\n //? zod 4 renamed the public def accessor from `._def` to `.def`.\r\n return sampleForDef(schema.def);\r\n};\r\n","import type { ContractCheckResult, RunContractSummary } from './types';\r\n\r\nconst statusIcon = (status: ContractCheckResult['status']): string => {\r\n switch (status) {\r\n case 'pass': { return '[PASS]';\r\n }\r\n case 'fail': { return '[FAIL]';\r\n }\r\n case 'skipped': { return '[SKIP]';\r\n }\r\n }\r\n};\r\n\r\nexport const logContractResult = (result: ContractCheckResult): void => {\r\n const { endpoint, durationMs, httpStatus, responseStatus, errorCode, reason } = result;\r\n const icon = statusIcon(result.status);\r\n const core = `${icon} ${endpoint.method} /${endpoint.fullPath} ${durationMs}ms`;\r\n\r\n if (result.status === 'pass') {\r\n const tail = responseStatus === 'error' ? ` (error: ${errorCode ?? 'unknown'})` : '';\r\n console.log(`${core} http=${httpStatus ?? '-'}${tail}`);\r\n return;\r\n }\r\n\r\n if (result.status === 'skipped') {\r\n console.log(`${core} ${reason ?? ''}`);\r\n return;\r\n }\r\n\r\n console.log(`${core} http=${httpStatus ?? '-'} reason=${reason ?? '(none)'}`);\r\n};\r\n\r\nexport const logContractSummary = (summary: RunContractSummary): void => {\r\n const { total, passed, failed, skipped } = summary;\r\n console.log('');\r\n console.log(`Contract tests: ${passed}/${total} passed, ${failed} failed, ${skipped} skipped`);\r\n\r\n if (failed > 0) {\r\n console.log('');\r\n console.log('Failures:');\r\n for (const result of summary.results) {\r\n if (result.status !== 'fail') continue;\r\n console.log(` - /${result.endpoint.fullPath}: ${result.reason ?? '(no reason)'}`);\r\n }\r\n }\r\n};\r\n","//? Per-route business-logic test layer. Discovers `*.tests.ts` files\r\n//? alongside `_api/` and `_sync/` route source, dynamic-imports each,\r\n//? builds a route-bound `TestContext`, runs the exported `customTests`\r\n//? cases. Spec: docs/ARCHITECTURE_TESTING.md.\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { pathToFileURL } from 'node:url';\r\n\r\nimport { getCsrfConfig, getPrismaClient, getProjectConfig, getSrcDir, tryCatch } from '@luckystack/core';\r\n\r\nimport {\r\n openStreamWatcher,\r\n type StreamChunkFrame,\r\n type StreamWatcher,\r\n} from './streamWatcher';\r\n\r\nconst API_TEST_FILE_PATTERN = /_v(\\d+)\\.tests\\.ts$/;\r\nconst SYNC_SERVER_TEST_FILE_PATTERN = /_server_v(\\d+)\\.tests\\.ts$/;\r\n\r\n//? Mirror of the generated `apiMethodMap` shape (page → name → version →\r\n//? method). Kept local so the test-runner has no import-time coupling to the\r\n//? consumer's generated artifact.\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\n\r\nexport interface CustomTestCase {\r\n name: string;\r\n run: (ctx: TestContext) => Promise<void>;\r\n /**\r\n * Mark this case as a KNOWN, accepted failure (e.g. a documented bug not\r\n * yet fixed, or a scenario the current implementation cannot satisfy). The\r\n * string is the reason shown in the report. A case marked `expectedToFail`\r\n * that throws is reported as `xfail` — counted separately, NOT a red\r\n * failure. If it unexpectedly PASSES it is reported as `xpass` so the stale\r\n * marker can be removed. Leave undefined for normal cases.\r\n */\r\n expectedToFail?: string;\r\n}\r\n\r\nexport interface CustomTestResult {\r\n routePath: string;\r\n caseName: string;\r\n //? `xfail` = marked expectedToFail and failed as expected (known issue).\r\n //? `xpass` = marked expectedToFail but passed (remove the marker).\r\n status: 'pass' | 'fail' | 'xfail' | 'xpass';\r\n durationMs: number;\r\n reason?: string;\r\n /** Documented reason when the case carried an `expectedToFail` marker. */\r\n expectedToFail?: string;\r\n}\r\n\r\nexport interface RunCustomTestsSummary {\r\n total: number;\r\n passed: number;\r\n failed: number;\r\n /** Cases marked `expectedToFail` that failed as expected (known issues). */\r\n xfailed: number;\r\n /** Cases marked `expectedToFail` that unexpectedly passed (remove the marker). */\r\n xpassed: number;\r\n results: CustomTestResult[];\r\n}\r\n\r\nexport interface RunCustomTestsInput {\r\n baseUrl: string;\r\n /** Optional cookie name for the session token. Defaults to the project's configured cookie. */\r\n sessionCookieName?: string;\r\n /** Filter to a subset of routes — substring match against `<page>/<name>/<version>`. */\r\n filter?: string;\r\n /**\r\n * Generated `apiMethodMap` so the harness sends each API route its declared\r\n * HTTP method (e.g. logout is DELETE). Falls back to POST when absent.\r\n */\r\n apiMethodMap?: ApiMethodMap;\r\n onResult?: (result: CustomTestResult) => void;\r\n}\r\n\r\ninterface DiscoveredTestFile {\r\n /** Absolute path to the test file. */\r\n filePath: string;\r\n /** `api` or `sync` — which transport this route uses. */\r\n kind: 'api' | 'sync';\r\n /** Route descriptor parsed from the filename + directory location. */\r\n route: { page: string; name: string; version: string };\r\n}\r\n\r\n//? Walk `src/` recursively. A file matches when its directory contains an\r\n//? `_api/` or `_sync/` segment AND the filename matches the version suffix\r\n//? pattern. The route's `page` is everything between `src/` and the marker\r\n//? segment, joined with `/`. Nested page folders are supported (e.g.\r\n//? `src/admin/users/_api/getUser_v1.tests.ts` → page `admin/users`).\r\nexport const discoverCustomTestFiles = (srcDir: string = getSrcDir()): DiscoveredTestFile[] => {\r\n if (!fs.existsSync(srcDir)) return [];\r\n const found: DiscoveredTestFile[] = [];\r\n const stack: string[] = [srcDir];\r\n while (stack.length > 0) {\r\n const current = stack.pop();\r\n if (current === undefined) break;\r\n const entries = fs.readdirSync(current, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(current, entry.name);\r\n if (entry.isDirectory()) {\r\n stack.push(fullPath);\r\n continue;\r\n }\r\n if (!entry.isFile() || !entry.name.endsWith('.tests.ts')) continue;\r\n const parentDir = path.basename(current);\r\n if (parentDir !== '_api' && parentDir !== '_sync') continue;\r\n const pattern = parentDir === '_api' ? API_TEST_FILE_PATTERN : SYNC_SERVER_TEST_FILE_PATTERN;\r\n const versionMatch = pattern.exec(entry.name);\r\n if (!versionMatch) continue;\r\n const version = `v${versionMatch[1]}`;\r\n const namePart = entry.name.replace(parentDir === '_api' ? /_v\\d+\\.tests\\.ts$/ : /_server_v\\d+\\.tests\\.ts$/, '');\r\n const pageDir = path.dirname(current);\r\n //? Top-level `src/_api/` (no page folder) routes under the framework's\r\n //? `system` page — mirror that, otherwise an empty page produces the\r\n //? malformed URL `/api//logout/v1` and the call 404s.\r\n const relativePage = path.relative(srcDir, pageDir).replaceAll('\\\\', '/') || 'system';\r\n found.push({\r\n filePath: fullPath,\r\n kind: parentDir === '_api' ? 'api' : 'sync',\r\n route: { page: relativePage, name: namePart, version },\r\n });\r\n }\r\n }\r\n return found.toSorted((a, b) => a.filePath.localeCompare(b.filePath));\r\n};\r\n\r\n//? Test fixture shapes. The TestContext is built per route — each per-route\r\n//? test file's `customTests` cases receive a context already bound to the\r\n//? route the file lives next to, so authors don't repeat `page/name/version`.\r\nexport interface TestContext {\r\n /** Invoke the route under test. Page / name / version are baked in. */\r\n //? Two type parameters by design — `TInput` lets callers document the\r\n //? typed shape they're sending even though we don't validate against it\r\n //? here (the route's own runtime validation does). The ESLint rule\r\n //? otherwise flags it as \"used once\"; the documentation value is real.\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\r\n callApi: <TInput = unknown, TOutput = unknown>(input: TInput) => Promise<TOutput>;\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\r\n callSync: <TInput = unknown, TOutput = unknown>(input: TInput, opts?: { receiver?: string }) => Promise<TOutput>;\r\n /**\r\n * Open a second socket connection that joins `roomCode` as a pure\r\n * subscriber and exposes the incoming chunk stream for assertions.\r\n *\r\n * The route is auto-derived from the test file location, so a watcher\r\n * only surfaces chunks for the route this test belongs to. Multiple\r\n * watchers per test are allowed; all are auto-closed when the test\r\n * case finishes (whether it passes or throws).\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callsite-documentation use\r\n watchStream: <TChunk extends StreamChunkFrame = StreamChunkFrame>(roomCode: string) => Promise<StreamWatcher<TChunk>>;\r\n session: {\r\n login: (user?: { email?: string; id?: string; name?: string }) => Promise<{ token: string; userId: string }>;\r\n logout: () => Promise<void>;\r\n current: () => { token: string | null; userId: string | null };\r\n };\r\n prisma: ReturnType<typeof getPrismaClient>;\r\n expect: TestExpect;\r\n}\r\n\r\nexport interface TestExpect {\r\n eq: <T>(actual: T, expected: T, message?: string) => void;\r\n ok: (value: unknown, message?: string) => void;\r\n throws: (fn: () => Promise<unknown>, message?: string) => Promise<Error>;\r\n matches: (value: string, pattern: RegExp, message?: string) => void;\r\n}\r\n\r\nclass AssertionError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'AssertionError';\r\n }\r\n}\r\n\r\nconst buildExpect = (): TestExpect => ({\r\n eq: (actual, expected, message) => {\r\n const ok = Object.is(actual, expected)\r\n || (typeof actual === 'object' && typeof expected === 'object' && JSON.stringify(actual) === JSON.stringify(expected));\r\n if (!ok) {\r\n const prefix = message ? `${message}: ` : '';\r\n throw new AssertionError(`${prefix}expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);\r\n }\r\n },\r\n ok: (value, message) => {\r\n if (!value) throw new AssertionError(message ?? `expected truthy value, got ${JSON.stringify(value)}`);\r\n },\r\n throws: async (fn, message) => {\r\n const [err] = await tryCatch(fn);\r\n if (!err) throw new AssertionError(message ?? 'expected fn to throw, but it did not');\r\n return err;\r\n },\r\n matches: (value, pattern, message) => {\r\n if (!pattern.test(value)) {\r\n const prefix = message ? `${message}: ` : '';\r\n throw new AssertionError(`${prefix}\"${value}\" does not match ${pattern}`);\r\n }\r\n },\r\n});\r\n\r\ninterface TestSessionState {\r\n token: string | null;\r\n userId: string | null;\r\n //? CSRF token minted by saveSession on login. Sent on authenticated\r\n //? state-changing requests so the server's CSRF middleware doesn't 403.\r\n csrfToken: string | null;\r\n //? Raw JSON body of the most recent callApi / callSync response. Surfaced in\r\n //? a failed case's reason so the report shows the server's actual errorCode\r\n //? (e.g. `api.rateLimitExceeded`) instead of only the assertion text.\r\n lastResponse: unknown;\r\n}\r\n\r\n//? Pull the server's errorCode (or non-success status) out of the last HTTP\r\n//? response so a failure's reason shows WHY the server rejected the call, not\r\n//? just the assertion text. Returns undefined for success / non-envelope bodies.\r\nconst extractErrorCode = (response: unknown): string | undefined => {\r\n if (!response || typeof response !== 'object') return undefined;\r\n const r = response as { errorCode?: unknown; status?: unknown };\r\n if (typeof r.errorCode === 'string' && r.errorCode.length > 0) return r.errorCode;\r\n if (typeof r.status === 'string' && r.status !== 'success') return r.status;\r\n return undefined;\r\n};\r\n\r\n//? Minimal session login: mints a session in Redis via the consumer's\r\n//? registered SessionAdapter (from `@luckystack/login`). Deliberately does\r\n//? NOT create the Prisma user record — User-table shape varies per\r\n//? project. If your test needs a real user row, create it via\r\n//? `ctx.prisma.user.create(...)` first, then pass `{ id, email }` to\r\n//? `session.login()`.\r\nconst buildSessionHelpers = (state: TestSessionState): TestContext['session'] => {\r\n return {\r\n login: async (user) => {\r\n const { saveSession, getSession } = await import('@luckystack/login');\r\n const userId = user?.id ?? `test-user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\r\n const email = user?.email ?? `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@example.com`;\r\n const name = user?.name ?? 'Test User';\r\n const token = `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\r\n const sessionData = {\r\n id: userId,\r\n email,\r\n name,\r\n token,\r\n provider: 'credentials' as const,\r\n };\r\n await saveSession(token, sessionData, true);\r\n state.token = token;\r\n state.userId = userId;\r\n //? Read back the CSRF token saveSession minted so authenticated\r\n //? state-changing requests can pass the server's CSRF middleware.\r\n const persisted = await getSession(token);\r\n state.csrfToken = persisted?.csrfToken ?? null;\r\n return { token, userId };\r\n },\r\n logout: async () => {\r\n if (!state.token) return;\r\n const { deleteSession } = await import('@luckystack/login');\r\n await deleteSession(state.token);\r\n state.token = null;\r\n state.userId = null;\r\n state.csrfToken = null;\r\n },\r\n current: () => ({ token: state.token, userId: state.userId }),\r\n };\r\n};\r\n\r\nconst buildCallApi = (\r\n baseUrl: string,\r\n routePath: string,\r\n state: TestSessionState,\r\n cookieName: string,\r\n //? The route's declared HTTP method (from the generated apiMethodMap). The\r\n //? server validates the method, so DELETE/PUT routes (e.g. logout) 405 if we\r\n //? always sent POST.\r\n method: string,\r\n): TestContext['callApi'] => {\r\n return async (input) => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/api/${routePath}`;\r\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\r\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\r\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\r\n const init: RequestInit = { method, headers };\r\n //? GET / HEAD requests cannot carry a body (fetch throws). Every other\r\n //? method sends the JSON payload.\r\n if (method !== 'GET' && method !== 'HEAD') init.body = JSON.stringify(input ?? {});\r\n const response = await fetch(url, init);\r\n const json = (await response.json()) as never;\r\n state.lastResponse = json;\r\n return json;\r\n };\r\n};\r\n\r\nconst buildCallSync = (\r\n baseUrl: string,\r\n routePath: string,\r\n state: TestSessionState,\r\n cookieName: string,\r\n): TestContext['callSync'] => {\r\n return async (input, opts) => {\r\n const url = `${baseUrl.replace(/\\/$/, '')}/sync/${routePath}`;\r\n const headers: Record<string, string> = { 'Content-Type': 'application/json', Origin: new URL(baseUrl).origin };\r\n if (state.token) headers.Cookie = `${cookieName}=${state.token}`;\r\n if (state.csrfToken) headers[getCsrfConfig().headerName] = state.csrfToken;\r\n const body = { data: input ?? {}, receiver: opts?.receiver };\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(body),\r\n });\r\n const json = (await response.json()) as never;\r\n state.lastResponse = json;\r\n return json;\r\n };\r\n};\r\n\r\ninterface BuiltContext {\r\n ctx: TestContext;\r\n /** Close every watcher opened during the case. Called by `runCustomTests` after each case. */\r\n closeAllWatchers: () => Promise<void>;\r\n /** Per-case session state — read after the run to surface the last response's errorCode. */\r\n state: TestSessionState;\r\n}\r\n\r\nconst buildContext = (\r\n discovery: DiscoveredTestFile,\r\n input: RunCustomTestsInput,\r\n): BuiltContext => {\r\n //? Default to the PROJECT's configured session cookie name (the server reads\r\n //? the token from this cookie). Hardcoding a name silently breaks every\r\n //? authenticated custom test when the project uses a different name.\r\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\r\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\r\n const routeFullName = `sync/${routePath}`;\r\n //? API routes are invoked with their declared HTTP method; sync routes always\r\n //? POST. Default to POST when the map lacks the route (e.g. sync, or a map\r\n //? wasn't threaded through).\r\n const apiMethod = input.apiMethodMap?.[discovery.route.page]?.[discovery.route.name]?.[discovery.route.version] ?? 'POST';\r\n const state: TestSessionState = { token: null, userId: null, csrfToken: null, lastResponse: null };\r\n const prisma = getPrismaClient();\r\n\r\n //? Watchers are tracked per case so a missing explicit `close()` can't\r\n //? leak socket B between tests.\r\n const watchers: StreamWatcher[] = [];\r\n const watchStream = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\r\n roomCode: string,\r\n ): Promise<StreamWatcher<TChunk>> => {\r\n const watcher = await openStreamWatcher<TChunk>({\r\n baseUrl: input.baseUrl,\r\n roomCode,\r\n token: state.token,\r\n routeFullName,\r\n });\r\n //? Track watchers under the base `StreamChunkFrame` shape; the\r\n //? returned reference keeps the caller-supplied generic.\r\n watchers.push(watcher);\r\n return watcher;\r\n };\r\n\r\n const closeAllWatchers = async (): Promise<void> => {\r\n while (watchers.length > 0) {\r\n const watcher = watchers.pop();\r\n if (!watcher) continue;\r\n const [closeError] = await tryCatch(() => watcher.close());\r\n if (closeError) {\r\n //? Swallow — closing in cleanup must never mask the original test\r\n //? failure. Surface via logger if a project wires one up.\r\n //? (Intentionally not using getLogger here to avoid an extra import.)\r\n }\r\n }\r\n };\r\n\r\n const ctx: TestContext = {\r\n callApi: buildCallApi(input.baseUrl, routePath, state, cookieName, apiMethod),\r\n callSync: buildCallSync(input.baseUrl, routePath, state, cookieName),\r\n watchStream,\r\n session: buildSessionHelpers(state),\r\n prisma,\r\n expect: buildExpect(),\r\n };\r\n\r\n return { ctx, closeAllWatchers, state };\r\n};\r\n\r\nexport const runCustomTests = async (input: RunCustomTestsInput): Promise<RunCustomTestsSummary> => {\r\n const files = discoverCustomTestFiles();\r\n const results: CustomTestResult[] = [];\r\n\r\n for (const discovery of files) {\r\n const routePath = `${discovery.route.page}/${discovery.route.name}/${discovery.route.version}`;\r\n if (input.filter && !routePath.includes(input.filter)) continue;\r\n\r\n const [importError, mod] = await tryCatch(\r\n () => import(pathToFileURL(discovery.filePath).href) as Promise<Record<string, unknown>>,\r\n );\r\n if (importError || !mod) {\r\n const r: CustomTestResult = {\r\n routePath, caseName: '(import)', status: 'fail', durationMs: 0,\r\n reason: `failed to import ${discovery.filePath}: ${importError?.message ?? 'unknown'}`,\r\n };\r\n results.push(r);\r\n input.onResult?.(r);\r\n continue;\r\n }\r\n\r\n const exported = (mod as { customTests?: unknown }).customTests;\r\n if (!Array.isArray(exported) || exported.length === 0) {\r\n //? File present but no cases — treat as silent skip; the AI hasn't filled it in yet.\r\n continue;\r\n }\r\n\r\n for (const rawCase of exported) {\r\n const c = rawCase as CustomTestCase;\r\n if (typeof c.name !== 'string' || typeof c.run !== 'function') continue;\r\n const expectedToFail = typeof c.expectedToFail === 'string' ? c.expectedToFail : undefined;\r\n const built = buildContext(discovery, input);\r\n const started = Date.now();\r\n const [runError] = await tryCatch(async () => c.run(built.ctx));\r\n //? Always close watchers regardless of pass/fail so the next case\r\n //? starts clean. tryCatch on the cleanup itself prevents a teardown\r\n //? throw from masking the original failure.\r\n await tryCatch(() => built.closeAllWatchers());\r\n const durationMs = Date.now() - started;\r\n\r\n let r: CustomTestResult;\r\n if (runError) {\r\n //? Augment the assertion message with the server's actual errorCode so\r\n //? the report explains the rejection, not just the failed expectation.\r\n const errorCode = extractErrorCode(built.state.lastResponse);\r\n const reason = errorCode ? `${runError.message} (server: ${errorCode})` : runError.message;\r\n r = expectedToFail\r\n ? { routePath, caseName: c.name, status: 'xfail', durationMs, reason, expectedToFail }\r\n : { routePath, caseName: c.name, status: 'fail', durationMs, reason };\r\n } else {\r\n r = expectedToFail\r\n ? {\r\n routePath, caseName: c.name, status: 'xpass', durationMs,\r\n reason: `marked expectedToFail (\"${expectedToFail}\") but passed — remove the marker`,\r\n expectedToFail,\r\n }\r\n : { routePath, caseName: c.name, status: 'pass', durationMs };\r\n }\r\n results.push(r);\r\n input.onResult?.(r);\r\n }\r\n }\r\n\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n xfailed: results.filter(r => r.status === 'xfail').length,\r\n xpassed: results.filter(r => r.status === 'xpass').length,\r\n results,\r\n };\r\n};\r\n","//? Second-socket harness for Layer 5 custom tests. Opens a separate\r\n//? socket.io-client connection (socket B), joins the receiver room as a\r\n//? pure subscriber, and exposes the broadcast/streamTo chunk stream for\r\n//? assertions. Without this, `ctx.callSync` only surfaces the final\r\n//? envelope — chunks (LLM tokens, progress ticks) emitted via\r\n//? `broadcastStream` / `streamTo` flow on the `sync` event channel to\r\n//? room members other than the request originator's socket.\r\n//?\r\n//? Spec: see `docs/contract-tests.md` §watchStream and CLAUDE Rule 21\r\n//? (no `as unknown as X` / `as any` — typed generics throughout).\r\n//?\r\n//? Wire shape, sourced from `@luckystack/sync` `_shared/streamEmitters.ts`:\r\n//? io.to(receiver).emit('sync', { ...payload, cb, fullName, status: 'stream' })\r\n//? where `fullName` = `sync/<page>/<name>/<version>`. We filter incoming\r\n//? frames by that `fullName` so a watcher only sees chunks for the route\r\n//? its test file lives next to.\r\n\r\nimport { io as createSocketIoClient, Socket } from 'socket.io-client';\r\nimport {\r\n buildJoinRoomResponseEventName,\r\n socketEventNames,\r\n tryCatch,\r\n} from '@luckystack/core';\r\n\r\nexport interface StreamChunkFrame {\r\n /** Per-request correlation id stamped by the server. */\r\n cb?: string;\r\n /** Full route name (`sync/<page>/<name>/<version>`). */\r\n fullName?: string;\r\n /** Always `'stream'` for chunk frames. */\r\n status?: 'stream';\r\n /** Payload fields spread on top of the envelope. */\r\n [key: string]: unknown;\r\n}\r\n\r\nexport interface StreamWatcher<TChunk extends StreamChunkFrame = StreamChunkFrame> {\r\n /** Chunks received so far. Array reference is stable; entries are pushed in arrival order. */\r\n readonly chunks: TChunk[];\r\n /** Wait until `predicate(chunks)` returns true, or until timeout (default 5000ms). */\r\n stopAt(predicate: (chunks: TChunk[]) => boolean, timeoutMs?: number): Promise<void>;\r\n /** Wait until N total chunks received, or until timeout. */\r\n waitForCount(n: number, timeoutMs?: number): Promise<void>;\r\n /** Close socket B. Tests SHOULD call this in cleanup; ctx auto-closes all watchers when the test ends. */\r\n close(): Promise<void>;\r\n}\r\n\r\nexport interface OpenStreamWatcherInput {\r\n /** Server URL (same as `ctx`'s `baseUrl`). */\r\n baseUrl: string;\r\n /** Room to join as a pure subscriber. */\r\n roomCode: string;\r\n /** Active session token (used for `joinRoom` auth). May be null for routes that don't require auth. */\r\n token: string | null;\r\n /**\r\n * Resolved route name `sync/<page>/<name>/<version>` — chunk frames are\r\n * filtered by their `fullName` property so a watcher only surfaces chunks\r\n * for the route its test is bound to.\r\n */\r\n routeFullName: string;\r\n /** Default 5000ms — overridable per `stopAt` / `waitForCount` call. */\r\n defaultTimeoutMs?: number;\r\n}\r\n\r\nconst DEFAULT_TIMEOUT_MS = 5000;\r\nconst JOIN_RESPONSE_TIMEOUT_MS = 3000;\r\nconst CONNECT_TIMEOUT_MS = 3000;\r\n\r\ninterface JoinRoomResponse {\r\n status?: string;\r\n errorCode?: string;\r\n}\r\n\r\nconst isJoinRoomResponse = (value: unknown): value is JoinRoomResponse =>\r\n typeof value === 'object' && value !== null;\r\n\r\nconst isStreamChunkFrame = (value: unknown): value is StreamChunkFrame =>\r\n typeof value === 'object' && value !== null;\r\n\r\n//? Monotonic counter — each watcher uses a fresh responseIndex so the\r\n//? `buildJoinRoomResponseEventName(...)` listener doesn't collide with a\r\n//? sibling watcher on the same process.\r\nlet responseIndexCounter = 0;\r\nconst nextResponseIndex = (): number => {\r\n responseIndexCounter += 1;\r\n return responseIndexCounter;\r\n};\r\n\r\nconst waitForConnect = (socket: Socket): Promise<void> => {\r\n return new Promise<void>((resolve, reject) => {\r\n if (socket.connected) {\r\n resolve();\r\n return;\r\n }\r\n const timer = setTimeout(() => {\r\n cleanup();\r\n reject(new Error(`watchStream: socket failed to connect within ${CONNECT_TIMEOUT_MS}ms`));\r\n }, CONNECT_TIMEOUT_MS);\r\n const onConnect = (): void => {\r\n cleanup();\r\n resolve();\r\n };\r\n const onConnectError = (err: Error): void => {\r\n cleanup();\r\n reject(new Error(`watchStream: connect_error — ${err.message}`));\r\n };\r\n const cleanup = (): void => {\r\n clearTimeout(timer);\r\n socket.off('connect', onConnect);\r\n socket.off('connect_error', onConnectError);\r\n };\r\n socket.on('connect', onConnect);\r\n socket.on('connect_error', onConnectError);\r\n });\r\n};\r\n\r\nconst waitForJoinAck = (socket: Socket, responseIndex: number): Promise<void> => {\r\n const eventName = buildJoinRoomResponseEventName(responseIndex);\r\n return new Promise<void>((resolve, reject) => {\r\n const timer = setTimeout(() => {\r\n socket.off(eventName, onAck);\r\n reject(new Error(`watchStream: joinRoom ack timeout after ${JOIN_RESPONSE_TIMEOUT_MS}ms`));\r\n }, JOIN_RESPONSE_TIMEOUT_MS);\r\n const onAck = (payload: unknown): void => {\r\n clearTimeout(timer);\r\n socket.off(eventName, onAck);\r\n if (isJoinRoomResponse(payload) && payload.status === 'error') {\r\n reject(new Error(`watchStream: joinRoom rejected — ${payload.errorCode ?? 'unknown'}`));\r\n return;\r\n }\r\n resolve();\r\n };\r\n socket.on(eventName, onAck);\r\n });\r\n};\r\n\r\nexport const openStreamWatcher = async <TChunk extends StreamChunkFrame = StreamChunkFrame>(\r\n input: OpenStreamWatcherInput,\r\n): Promise<StreamWatcher<TChunk>> => {\r\n const defaultTimeoutMs = input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n const chunks: TChunk[] = [];\r\n const waiters: { predicate: (chunks: TChunk[]) => boolean; resolve: () => void }[] = [];\r\n\r\n const socket: Socket = createSocketIoClient(input.baseUrl, {\r\n transports: ['websocket'],\r\n forceNew: true,\r\n reconnection: false,\r\n auth: input.token ? { token: input.token } : undefined,\r\n });\r\n\r\n const handleChunk = (raw: unknown): void => {\r\n if (!isStreamChunkFrame(raw)) return;\r\n if (raw.status !== 'stream') return;\r\n if (raw.fullName !== input.routeFullName) return;\r\n //? Server emits chunk frames typed as `StreamChunkFrame` — caller's\r\n //? `TChunk` narrows the payload shape (e.g. `{ chunk: string }` for\r\n //? `streamBroadcast`). The runtime guards above confirm the wire shape;\r\n //? narrowing the static type is the test author's responsibility.\r\n const chunk = raw as TChunk;\r\n chunks.push(chunk);\r\n //? Drain waiters whose predicates are now satisfied. Iterate in reverse\r\n //? so we can splice without breaking indices.\r\n for (let i = waiters.length - 1; i >= 0; i--) {\r\n const waiter = waiters[i];\r\n if (!waiter) continue;\r\n if (waiter.predicate(chunks)) {\r\n waiters.splice(i, 1);\r\n waiter.resolve();\r\n }\r\n }\r\n };\r\n\r\n socket.on(socketEventNames.sync, handleChunk);\r\n\r\n await waitForConnect(socket);\r\n\r\n const responseIndex = nextResponseIndex();\r\n socket.emit(socketEventNames.joinRoom, {\r\n group: input.roomCode,\r\n responseIndex,\r\n });\r\n const [joinError] = await tryCatch(() => waitForJoinAck(socket, responseIndex));\r\n if (joinError) {\r\n socket.off(socketEventNames.sync, handleChunk);\r\n socket.disconnect();\r\n throw joinError;\r\n }\r\n\r\n let closed = false;\r\n const close = (): Promise<void> => {\r\n if (closed) return Promise.resolve();\r\n closed = true;\r\n socket.off(socketEventNames.sync, handleChunk);\r\n //? Drain any outstanding waiters so callers don't hang past test end.\r\n for (const waiter of waiters) waiter.resolve();\r\n waiters.length = 0;\r\n socket.disconnect();\r\n return Promise.resolve();\r\n };\r\n\r\n const stopAt = (\r\n predicate: (chunks: TChunk[]) => boolean,\r\n timeoutMs: number = defaultTimeoutMs,\r\n ): Promise<void> => {\r\n if (predicate(chunks)) return Promise.resolve();\r\n return new Promise<void>((resolve, reject) => {\r\n let settled = false;\r\n const waiter = {\r\n predicate,\r\n resolve: () => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n resolve();\r\n },\r\n };\r\n const timer = setTimeout(() => {\r\n if (settled) return;\r\n settled = true;\r\n const idx = waiters.indexOf(waiter);\r\n if (idx !== -1) waiters.splice(idx, 1);\r\n reject(new Error(\r\n `watchStream.stopAt: predicate not satisfied within ${timeoutMs}ms (received ${chunks.length} chunks)`,\r\n ));\r\n }, timeoutMs);\r\n waiters.push(waiter);\r\n });\r\n };\r\n\r\n const waitForCount = (n: number, timeoutMs: number = defaultTimeoutMs): Promise<void> => {\r\n return stopAt((current) => current.length >= n, timeoutMs);\r\n };\r\n\r\n return {\r\n chunks,\r\n stopAt,\r\n waitForCount,\r\n close,\r\n };\r\n};\r\n","//? Orchestrator — runs all five test layers (contract, auth-enforcement,\r\n//? rate-limit, fuzz, custom) against a running server and returns a single\r\n//? combined summary. Consumers call this from their own thin script that\r\n//? imports the generated `apiMethodMap` + `apiInputSchemas`.\r\n\r\nimport { clearAllRateLimits, getProjectConfig } from '@luckystack/core';\r\n\r\nimport { sampleSchemaInput } from './schemaSampleInput';\r\nimport { runContractTests } from './runContractTests';\r\nimport { runAuthEnforcementTests } from './runAuthEnforcementTests';\r\nimport { runRateLimitTests } from './runRateLimitTests';\r\nimport { runFuzzTests } from './runFuzzTests';\r\nimport { runCustomTests } from './customTests';\r\nimport type { CustomTestResult, RunCustomTestsSummary } from './customTests';\r\nimport type { ContractCheckResult, EndpointDescriptor, RunContractSummary } from './types';\r\nimport type { ZodType } from 'zod';\r\n\r\ntype ApiMethodMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, string>>>>>>;\r\ntype ApiMetaMap = Partial<Record<string, Partial<Record<string, Partial<Record<string, {\r\n method: string;\r\n auth: { login: boolean; additional?: Record<string, unknown>[] };\r\n rateLimit?: number | false;\r\n}>>>>>>;\r\ntype ApiInputSchemas = Partial<Record<string, Partial<Record<string, Partial<Record<string, ZodType | undefined>>>>>>;\r\n\r\nexport interface RunAllTestsInput {\r\n apiMethodMap: ApiMethodMap;\r\n apiMetaMap: ApiMetaMap;\r\n apiInputSchemas: ApiInputSchemas;\r\n baseUrl: string;\r\n /** Cookie name for the session token. Defaults to `luckystack_token`. */\r\n sessionCookieName?: string;\r\n /** Auth token applied as a `Cookie` header to sweep-layer requests that need a session. */\r\n authToken?: string;\r\n /** Endpoints to skip from the sweep layers, e.g. routes that need a file upload. */\r\n skip?: string[];\r\n /** Substring filter applied to `<page>/<name>/<version>`. */\r\n filter?: string;\r\n /** Disable individual layers (faster local iteration). */\r\n noSweep?: boolean;\r\n noFuzz?: boolean;\r\n noRateLimit?: boolean;\r\n noCustom?: boolean;\r\n}\r\n\r\nexport interface RunAllTestsSummary {\r\n contract?: RunContractSummary;\r\n auth?: RunContractSummary;\r\n rateLimit?: RunContractSummary;\r\n fuzz?: RunContractSummary;\r\n custom?: RunCustomTestsSummary;\r\n totalPassed: number;\r\n totalFailed: number;\r\n}\r\n\r\nconst inputForEndpoint = (apiInputSchemas: ApiInputSchemas) =>\r\n (endpoint: EndpointDescriptor): unknown => {\r\n const schema = apiInputSchemas[endpoint.page]?.[endpoint.name]?.[endpoint.version];\r\n return schema ? sampleSchemaInput(schema) : {};\r\n };\r\n\r\nconst matchesFilter = (endpoint: EndpointDescriptor, filter: string | undefined): boolean => {\r\n if (!filter) return true;\r\n return `${endpoint.page}/${endpoint.name}/${endpoint.version}`.includes(filter);\r\n};\r\n\r\nconst filterResults = (results: ContractCheckResult[], filter: string | undefined): ContractCheckResult[] =>\r\n filter ? results.filter(r => matchesFilter(r.endpoint, filter)) : results;\r\n\r\nconst cloneSummary = (summary: RunContractSummary, filter: string | undefined): RunContractSummary => {\r\n if (!filter) return summary;\r\n const results = filterResults(summary.results, filter);\r\n return {\r\n total: results.length,\r\n passed: results.filter(r => r.status === 'pass').length,\r\n failed: results.filter(r => r.status === 'fail').length,\r\n skipped: results.filter(r => r.status === 'skipped').length,\r\n results,\r\n };\r\n};\r\n\r\nexport const runAllTests = async (input: RunAllTestsInput): Promise<RunAllTestsSummary> => {\r\n const summary: RunAllTestsSummary = { totalPassed: 0, totalFailed: 0 };\r\n const headers: Record<string, string> = {};\r\n if (input.authToken) {\r\n const cookieName = input.sessionCookieName ?? getProjectConfig().http.sessionCookieName;\r\n headers.Cookie = `${cookieName}=${input.authToken}`;\r\n }\r\n const inputFor = inputForEndpoint(input.apiInputSchemas);\r\n\r\n if (!input.noSweep) {\r\n const contract = await runContractTests({\r\n apiMethodMap: input.apiMethodMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n headers,\r\n inputFor,\r\n });\r\n summary.contract = cloneSummary(contract, input.filter);\r\n\r\n const auth = await runAuthEnforcementTests({\r\n apiMethodMap: input.apiMethodMap,\r\n apiMetaMap: input.apiMetaMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n inputFor,\r\n });\r\n summary.auth = cloneSummary(auth, input.filter);\r\n\r\n if (!input.noRateLimit) {\r\n const rateLimit = await runRateLimitTests({\r\n apiMethodMap: input.apiMethodMap,\r\n apiMetaMap: input.apiMetaMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n headers,\r\n inputFor,\r\n });\r\n summary.rateLimit = cloneSummary(rateLimit, input.filter);\r\n }\r\n\r\n if (!input.noFuzz) {\r\n const fuzz = await runFuzzTests({\r\n apiMethodMap: input.apiMethodMap,\r\n baseUrl: input.baseUrl,\r\n skip: input.skip,\r\n headers,\r\n });\r\n summary.fuzz = cloneSummary(fuzz, input.filter);\r\n }\r\n }\r\n\r\n if (!input.noCustom) {\r\n //? The rate-limit layer drains per-route buckets; clear them before the\r\n //? custom layer so business-logic tests on low-limit routes (e.g.\r\n //? confirmReset, sendReset) aren't spuriously rejected with\r\n //? api.rateLimitExceeded. Runs in-process against the same Redis the server\r\n //? uses. Non-fatal if the active strategy has no clear().\r\n try {\r\n await clearAllRateLimits();\r\n } catch {\r\n //? swallow — a missing clear() is a test-quality degrade, not a crash.\r\n }\r\n const custom = await runCustomTests({\r\n baseUrl: input.baseUrl,\r\n sessionCookieName: input.sessionCookieName,\r\n filter: input.filter,\r\n //? Pass the generated method map so the harness invokes each API route\r\n //? with its declared HTTP method (e.g. logout is DELETE, not POST).\r\n apiMethodMap: input.apiMethodMap,\r\n });\r\n summary.custom = custom;\r\n }\r\n\r\n summary.totalPassed = (summary.contract?.passed ?? 0)\r\n + (summary.auth?.passed ?? 0)\r\n + (summary.rateLimit?.passed ?? 0)\r\n + (summary.fuzz?.passed ?? 0)\r\n + (summary.custom?.passed ?? 0);\r\n summary.totalFailed = (summary.contract?.failed ?? 0)\r\n + (summary.auth?.failed ?? 0)\r\n + (summary.rateLimit?.failed ?? 0)\r\n + (summary.fuzz?.failed ?? 0)\r\n + (summary.custom?.failed ?? 0);\r\n\r\n return summary;\r\n};\r\n\r\n//? ── Colored, list-based reporting ───────────────────────────────────────\r\n//? Honors NO_COLOR / FORCE_COLOR (https://no-color.org) and falls back to\r\n//? plain text when stdout is not a TTY (piped to a file / CI log).\r\nconst useColor = ((): boolean => {\r\n if (process.env.NO_COLOR) return false;\r\n if (process.env.FORCE_COLOR) return true;\r\n return process.stdout.isTTY;\r\n})();\r\nconst paint = (code: string, text: string): string => (useColor ? `\\u001B[${code}m${text}\\u001B[0m` : text);\r\nconst green = (t: string): string => paint('32', t);\r\nconst red = (t: string): string => paint('31', t);\r\nconst yellow = (t: string): string => paint('33', t);\r\nconst dim = (t: string): string => paint('2', t);\r\nconst bold = (t: string): string => paint('1', t);\r\n\r\ninterface ReportRow {\r\n label: string;\r\n reason?: string;\r\n}\r\n\r\nconst xfailOf = (s: RunContractSummary | RunCustomTestsSummary | undefined): number =>\r\n s && 'xfailed' in s ? s.xfailed : 0;\r\nconst skippedOf = (s: RunContractSummary | RunCustomTestsSummary | undefined): number =>\r\n s && 'skipped' in s ? s.skipped : 0;\r\n\r\n//? Per-layer headline: green \"X/Y passed\" when clean, red \"Z failed\" when not,\r\n//? plus dim xfail / skipped counts. This is the \"14/20 in groen, 6/20 in rood\"\r\n//? the report leads with.\r\nconst formatLayerLine = (label: string, s: RunContractSummary | RunCustomTestsSummary | undefined): string => {\r\n if (!s) return ` ${dim('–')} ${label.padEnd(18)} ${dim('(layer skipped)')}`;\r\n const clean = s.failed === 0;\r\n const mark = clean ? green('✓') : red('✗');\r\n const head = clean\r\n ? green(`${s.passed}/${s.total} passed`)\r\n : `${green(`${s.passed}/${s.total} passed`)} ${red(`${s.failed}/${s.total} failed`)}`;\r\n const extras: string[] = [];\r\n const xf = xfailOf(s);\r\n const sk = skippedOf(s);\r\n if (xf) extras.push(yellow(`${xf} expected-fail`));\r\n if (sk) extras.push(dim(`${sk} skipped`));\r\n const extra = extras.length > 0 ? ` ${dim('·')} ${extras.join(dim(', '))}` : '';\r\n return ` ${mark} ${label.padEnd(18)} ${head}${extra}`;\r\n};\r\n\r\nconst sweepFailRows = (s: RunContractSummary | undefined): ReportRow[] =>\r\n (s?.results ?? [])\r\n .filter((r) => r.status === 'fail')\r\n .map((r) => ({\r\n label: `${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\r\n reason: r.errorCode ? `${r.reason ?? 'failed'} [${r.errorCode}]` : r.reason,\r\n }));\r\n\r\nconst sweepSkipRows = (layer: string, s: RunContractSummary | undefined): ReportRow[] =>\r\n (s?.results ?? [])\r\n .filter((r) => r.status === 'skipped')\r\n .map((r) => ({\r\n label: `${layer}: ${r.endpoint.page}/${r.endpoint.name}/${r.endpoint.version}`,\r\n reason: r.reason,\r\n }));\r\n\r\nconst customRows = (s: RunCustomTestsSummary | undefined, status: CustomTestResult['status']): ReportRow[] =>\r\n (s?.results ?? [])\r\n .filter((r) => r.status === status)\r\n .map((r) => ({ label: `${r.routePath} :: ${r.caseName}`, reason: r.reason }));\r\n\r\nconst printSection = (title: (t: string) => string, heading: string, rows: ReportRow[], bullet: string): void => {\r\n if (rows.length === 0) return;\r\n console.log('');\r\n console.log(title(bold(`${heading} (${rows.length})`)));\r\n for (const row of rows) {\r\n console.log(` ${title(bullet)} ${row.label}`);\r\n if (row.reason) console.log(` ${dim(row.reason)}`);\r\n }\r\n};\r\n\r\nexport const logRunAllSummary = (summary: RunAllTestsSummary): void => {\r\n console.log('');\r\n console.log(formatLayerLine('contract', summary.contract));\r\n console.log(formatLayerLine('auth-enforcement', summary.auth));\r\n console.log(formatLayerLine('rate-limit', summary.rateLimit));\r\n console.log(formatLayerLine('fuzz', summary.fuzz));\r\n console.log(formatLayerLine('custom', summary.custom));\r\n\r\n //? Real failures across every layer — these MUST be fixed (a green test that\r\n //? went red, or a wrong test). Sweep failures + un-marked custom failures.\r\n const failed: ReportRow[] = [\r\n ...sweepFailRows(summary.contract),\r\n ...sweepFailRows(summary.auth),\r\n ...sweepFailRows(summary.rateLimit),\r\n ...sweepFailRows(summary.fuzz),\r\n ...customRows(summary.custom, 'fail'),\r\n ];\r\n printSection(red, 'Failed — must be fixed (real bugs / wrong tests)', failed, '✗');\r\n\r\n //? Known, accepted failures — red is OK here; they are tracked, not regressions.\r\n printSection(yellow, 'Expected failures — known issues, allowed to fail', customRows(summary.custom, 'xfail'), '⚠');\r\n\r\n //? Stale markers — these passed but are still flagged expectedToFail.\r\n printSection(yellow, 'Unexpectedly passed — remove the expectedToFail marker', customRows(summary.custom, 'xpass'), '?');\r\n\r\n //? Skipped with reason — not run in this mode (login-gated, high rate-limit, …).\r\n const skipped: ReportRow[] = [\r\n ...sweepSkipRows('contract', summary.contract),\r\n ...sweepSkipRows('auth', summary.auth),\r\n ...sweepSkipRows('rate-limit', summary.rateLimit),\r\n ...sweepSkipRows('fuzz', summary.fuzz),\r\n ];\r\n printSection(dim, 'Skipped — not run (with reason)', skipped, '–');\r\n\r\n const totalXfail = xfailOf(summary.contract) + xfailOf(summary.auth) + xfailOf(summary.rateLimit)\r\n + xfailOf(summary.fuzz) + xfailOf(summary.custom);\r\n const totalSkipped = skippedOf(summary.contract) + skippedOf(summary.auth) + skippedOf(summary.rateLimit)\r\n + skippedOf(summary.fuzz) + skippedOf(summary.custom);\r\n\r\n console.log('');\r\n const parts = [\r\n green(`${summary.totalPassed} passed`),\r\n summary.totalFailed > 0 ? red(`${summary.totalFailed} failed`) : dim('0 failed'),\r\n totalXfail > 0 ? yellow(`${totalXfail} expected-fail`) : dim('0 expected-fail'),\r\n totalSkipped > 0 ? yellow(`${totalSkipped} skipped`) : dim('0 skipped'),\r\n ];\r\n console.log(`Summary: ${parts.join(dim(' · '))}`);\r\n console.log(dim(' legend: ✗ red = must fix · ⚠ yellow = known/allowed · – skipped (not run)'));\r\n};\r\n","//? Extension registry for the test runner. Consumers plug in custom test\r\n//? layers, realistic-payload fixtures, and result reporters without\r\n//? forking the framework.\r\n//?\r\n//? Three slots:\r\n//?\r\n//? 1. Test layers — additional checks per endpoint (CORS enforcement,\r\n//? business rules, custom auth schemes, multi-tenant isolation).\r\n//? 2. Fixtures — realistic input payloads (valid + invalid) per typeKey\r\n//? so the fuzz layer can use them before falling back to schema-random.\r\n//? 3. Reporters — per-result and per-summary callbacks + optional\r\n//? webhook URL the runner POSTs the batch summary to.\r\n//?\r\n//? All slots are append-only at registration time; clearing requires\r\n//? `resetTestExtensionsForTests()` (kept out of the production surface\r\n//? to prevent accidental wipes from a misbehaving plugin).\r\n\r\nexport interface TestLayerInput {\r\n /** Resolved route name (`api/billing/getInvoice/v1`). */\r\n endpoint: string;\r\n /** Method when applicable, undefined for sync endpoints. */\r\n method?: string;\r\n /** Bearer token / session token to use for auth-aware checks. */\r\n authToken?: string;\r\n}\r\n\r\nexport interface TestLayerResult {\r\n passed: boolean;\r\n /** Short reason key — i18n-style errorCode when failed. */\r\n message?: string;\r\n /** Optional extras for the reporter (latency, response sample, ...). */\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TestLayer {\r\n name: string;\r\n run: (input: TestLayerInput) => Promise<TestLayerResult> | TestLayerResult;\r\n}\r\n\r\nexport interface TestFixture<TPayload = unknown> {\r\n valid: TPayload[];\r\n invalid: TPayload[];\r\n}\r\n\r\nexport interface TestResult {\r\n layer: string;\r\n endpoint: string;\r\n passed: boolean;\r\n message?: string;\r\n durationMs?: number;\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TestSummary {\r\n totalLayers: number;\r\n totalEndpoints: number;\r\n passed: number;\r\n failed: number;\r\n results: TestResult[];\r\n}\r\n\r\nexport interface TestReporter {\r\n /** Called once per individual TestResult as the runner progresses. */\r\n onResult?: (result: TestResult) => void | Promise<void>;\r\n /** Called once at the end with the aggregated summary. */\r\n onSummary?: (summary: TestSummary) => void | Promise<void>;\r\n /** Optional webhook the runner POSTs the summary to. */\r\n webhookUrl?: string;\r\n webhookAuth?: { type: 'bearer'; token: string };\r\n}\r\n\r\nconst layers = new Map<string, TestLayer>();\r\nconst fixtures = new Map<string, TestFixture>();\r\nlet reporter: TestReporter | null = null;\r\n\r\n/**\r\n * Register a custom test layer. Re-registering the same name replaces\r\n * the previous entry. Use for CORS enforcement, business-rule checks,\r\n * custom auth schemes, multi-tenant isolation, GDPR data-flow checks.\r\n */\r\nexport const registerTestLayer = (layer: TestLayer): void => {\r\n layers.set(layer.name, layer);\r\n};\r\n\r\nexport const listTestLayers = (): TestLayer[] => [...layers.values()];\r\n\r\n/**\r\n * Register a realistic payload fixture for a specific type key. The fuzz\r\n * layer prefers these over schema-random generated inputs when a fixture\r\n * exists for the endpoint's input type.\r\n *\r\n * `valid` payloads are used to verify happy-path behavior; `invalid`\r\n * payloads probe the validator's error responses.\r\n */\r\nexport const registerTestFixture = <TPayload = unknown>(typeKey: string, fixture: TestFixture<TPayload>): void => {\r\n fixtures.set(typeKey, fixture);\r\n};\r\n\r\nexport const getTestFixture = (typeKey: string): TestFixture | undefined => fixtures.get(typeKey);\r\n\r\n/**\r\n * Register a test reporter. Set `null` to unregister. The runner calls\r\n * `onResult` per individual result as the run progresses and `onSummary`\r\n * once at the end. When `webhookUrl` is set, the runner additionally\r\n * POSTs the JSON-serialised summary to that URL.\r\n */\r\nexport const registerTestReporter = (newReporter: TestReporter | null): void => {\r\n reporter = newReporter;\r\n};\r\n\r\nexport const getTestReporter = (): TestReporter | null => reporter;\r\n\r\nexport const resetTestExtensionsForTests = (): void => {\r\n layers.clear();\r\n fixtures.clear();\r\n reporter = null;\r\n};\r\n"],"mappings":";AAIO,IAAM,gBAAgB,CAAC,iBAAqD;AACjF,QAAM,YAAkC,CAAC;AACzC,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,YAAY,GAAG;AAC1D,QAAI,CAAC,QAAS;AACd,eAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,GAAG;AACxD,UAAI,CAAC,WAAY;AACjB,iBAAW,CAAC,SAAS,MAAM,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC1D,YAAI,WAAW,OAAW;AAC1B,kBAAU,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU,OAAO,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,QAC1C,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACvBA,SAAS,gBAAgB;AAGzB,IAAM,6BAA6B;AA8B5B,IAAM,mBAAmB,OAAO,UAA4D;AACjG,QAAM,EAAE,UAAU,QAAQ,IAAI;AAC9B,QAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,SAAS,QAAQ;AAC9D,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AAEzB,QAAM,mBAAmB,MAAM,oBAAoB;AACnD,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,gBAAgB,WAAW,MAAM;AAAE,eAAW,MAAM;AAAA,EAAG,GAAG,gBAAgB;AAEhF,QAAM,CAAC,YAAY,QAAQ,IAAI,MAAM,SAAS,MAAM,MAAM,KAAK;AAAA,IAC7D,QAAQ,SAAS;AAAA,IACjB,SAAS;AAAA,MACP,gBAAgB;AAAA;AAAA;AAAA;AAAA,MAIhB,UAAU,IAAI,IAAI,OAAO,EAAE;AAAA,MAC3B,GAAG,MAAM;AAAA,IACX;AAAA,IACA,MAAM,SAAS,WAAW,QAAQ,SAAY,KAAK,UAAU,IAAI;AAAA,IACjE,QAAQ,WAAW;AAAA,EACrB,CAAC,CAAC;AACF,eAAa,aAAa;AAE1B,MAAI,cAAc,CAAC,UAAU;AAC3B,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,YAAY,WAAW;AAAA,MAC/B,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,CAAC,YAAY,MAAM,IAAI,MAAM;AAAA,IACjC,YAAY,MAAM,SAAS,KAAK;AAAA,EAClC;AAEA,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,YAAY;AACd,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,QAAQ,sBAAsB,WAAW,OAAO;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,UAAW,OAAO,WAAW,aAAa,OAAO,WAAW,SAAU;AACzE,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,WAAW,CAAC,OAAO,WAAW;AAClD,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,YAAY,SAAS;AAAA,IACrB,gBAAgB,OAAO;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB;AAAA,EACF;AACF;;;AC9FA,IAAM,aAAa,CAAC,UAA8B,SAA4B;AAC5E,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,YAAY,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AACvE,QAAM,cAAc,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI;AACrD,SAAO,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,WAAW;AAC9D;AAEO,IAAM,mBAAmB,OAAO,UAA8D;AACnG,QAAM,YAAY,cAAc,MAAM,YAAY;AAClD,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,QAAI,WAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAIA,UAAM,SAAS,MAAM,iBAAiB;AAAA,MACpC;AAAA,MACA,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAAA,IACrD;AAAA,EACF;AACF;;;AChEA,SAAS,YAAAA,iBAAgB;AAGzB,IAAMC,8BAA6B;AAInC,IAAM,sBAAsB;AAerB,IAAM,0BAA0B,OACrC,UACiC;AACjC,QAAM,EAAE,UAAU,QAAQ,IAAI;AAC9B,QAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,SAAS,QAAQ;AAC9D,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,mBAAmB,MAAM,oBAAoBA;AAEnD,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,gBAAgB,WAAW,MAAM;AAAE,eAAW,MAAM;AAAA,EAAG,GAAG,gBAAgB;AAEhF,QAAM,CAAC,YAAY,QAAQ,IAAI,MAAMD,UAAS,MAAM,MAAM,KAAK;AAAA,IAC7D,QAAQ,SAAS;AAAA;AAAA;AAAA,IAGjB,SAAS,EAAE,gBAAgB,oBAAoB,UAAU,IAAI,IAAI,OAAO,EAAE,OAAO;AAAA,IACjF,MAAM,SAAS,WAAW,QAAQ,SAAY,KAAK,UAAU,IAAI;AAAA,IACjE,QAAQ,WAAW;AAAA,EACrB,CAAC,CAAC;AACF,eAAa,aAAa;AAE1B,MAAI,cAAc,CAAC,UAAU;AAC3B,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,YAAY,WAAW;AAAA,MAC/B,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,CAAC,YAAY,MAAM,IAAI,MAAMA;AAAA,IACjC,YAAY,MAAM,SAAS,KAAK;AAAA,EAClC;AACA,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,YAAY;AACd,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,QAAQ,sBAAsB,WAAW,OAAO;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,WAAW;AAChC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,SAAS;AAC9B,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,cAAc,qBAAqB;AAC5C,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,MACrB,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,QAAQ,uBAAuB,mBAAmB,cAAc,OAAO,aAAa,WAAW;AAAA,MAC/F;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,YAAY,SAAS;AAAA,IACrB,gBAAgB;AAAA,IAChB,WAAW,OAAO;AAAA,IAClB;AAAA,EACF;AACF;;;AC3FA,IAAME,cAAa,CAAC,UAA8B,SAA4B;AAC5E,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,YAAY,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AACvE,QAAM,cAAc,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI;AACrD,SAAO,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,WAAW;AAC9D;AAEA,IAAM,gBAAgB,CAAC,YAAwB,aAA0C;AACvF,QAAM,OAAO,WAAW,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAC1E,SAAO,MAAM,KAAK,SAAS;AAC7B;AAEO,IAAM,0BAA0B,OACrC,UACgC;AAChC,QAAM,YAAY,cAAc,MAAM,YAAY;AAClD,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,QAAI,CAAC,cAAc,MAAM,YAAY,QAAQ,GAAG;AAG9C;AAAA,IACF;AAEA,QAAIA,YAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,wBAAwB;AAAA,MAC3C;AAAA,MACA,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,IAClB,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAAA,IACrD;AAAA,EACF;AACF;;;AC1EA,SAAS,YAAAC,iBAAgB;AAGzB,IAAMC,8BAA6B;AACnC,IAAMC,uBAAsB;AAoBrB,IAAM,oBAAoB,OAAO,UAA6D;AACnG,QAAM,EAAE,UAAU,SAAS,UAAU,IAAI;AACzC,QAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,SAAS,QAAQ;AAC9D,QAAM,OAAO,MAAM,WAAW,MAAM,SAAS,QAAQ,IAAI,CAAC;AAC1D,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,mBAAmB,MAAM,oBAAoBD;AAEnD,QAAM,OAAO,YAAsC;AACjD,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,gBAAgB,WAAW,MAAM;AAAE,iBAAW,MAAM;AAAA,IAAG,GAAG,gBAAgB;AAChF,UAAM,CAAC,OAAO,QAAQ,IAAI,MAAMD,UAAS,MAAM,MAAM,KAAK;AAAA,MACxD,QAAQ,SAAS;AAAA,MACjB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,UAAU,IAAI,IAAI,OAAO,EAAE;AAAA,QAC3B,GAAG,MAAM;AAAA,MACX;AAAA,MACA,MAAM,SAAS,WAAW,QAAQ,SAAY,KAAK,UAAU,IAAI;AAAA,MACjE,QAAQ,WAAW;AAAA,IACrB,CAAC,CAAC;AACF,iBAAa,aAAa;AAC1B,QAAI,SAAS,CAAC,SAAU,QAAO;AAC/B,WAAO;AAAA,EACT;AAIA,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK,GAAG;AACrC,UAAM,KAAK;AAAA,EACb;AAEA,QAAM,QAAQ,MAAM,KAAK;AACzB,QAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,CAAC,EAAE,MAAM,IAAI,MAAMA;AAAA,IACvB,YAAY,MAAM,MAAM,KAAK;AAAA,EAC/B;AAEA,MAAI,QAAQ,WAAW,WAAW,OAAO,cAAcE,sBAAqB;AAC1E,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,MAAM;AAAA,MAClB,gBAAgB;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,YAAY,MAAM;AAAA,IAClB,gBAAgB,QAAQ,UAAU;AAAA,IAClC,WAAW,QAAQ;AAAA,IACnB,QAAQ,aAAaA,oBAAmB,gBAAgB,YAAY,CAAC,YAAY,QAAQ,UAAU,MAAM,IAAI,QAAQ,aAAa,QAAQ;AAAA,IAC1I;AAAA,EACF;AACF;;;AC3FA,SAAS,YAAAC,iBAAgB;AAgBlB,IAAM,mBAAmB,OAAO,EAAE,SAAS,MAAM,MAA+C;AACrG,QAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AACzC,QAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,MAAI,MAAO,SAAQ,oBAAoB,IAAI;AAE3C,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAMA,UAAS,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,QAAQ,CAAC,CAAC;AACtF,MAAI,SAAS,CAAC,SAAU,QAAO;AAC/B,SAAO,SAAS;AAClB;;;ACeA,IAAM,eAAe,CAAC,YAAwB,aAAgD;AAC5F,QAAM,OAAO,WAAW,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAC1E,MAAI,CAAC,QAAQ,KAAK,cAAc,SAAS,KAAK,cAAc,OAAW,QAAO;AAC9E,SAAO,KAAK;AACd;AAEA,IAAMC,iBAAgB,CAAC,YAAwB,aAA0C;AACvF,QAAM,OAAO,WAAW,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAC1E,SAAO,MAAM,KAAK,SAAS;AAC7B;AAEA,IAAMC,cAAa,CAAC,UAA8B,SAA4B;AAC5E,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,YAAY,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AACvE,QAAM,cAAc,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI;AACrD,SAAO,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,WAAW;AAC9D;AAEO,IAAM,oBAAoB,OAC/B,UACgC;AAChC,QAAM,YAAY,cAAc,MAAM,YAAY;AAClD,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,eAAe,MAAM,sBAAsB;AAMjD,QAAM,uBAAuB,QAAQ,MAAM,SAAS,MAAM;AAC1D,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,UAAM,YAAY,aAAa,MAAM,YAAY,QAAQ;AACzD,QAAI,cAAc,KAAM;AAExB,QAAI,CAAC,wBAAwBD,eAAc,MAAM,YAAY,QAAQ,GAAG;AACtE,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,YAAY,cAAc;AAC5B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ,aAAa,SAAS,+BAA+B,YAAY;AAAA,MAC3E;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAIC,YAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,QAAI,MAAM,uBAAuB;AAC/B,YAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,WAAW,CAAC;AAAA,IAC5E;AAEA,UAAM,SAAS,MAAM,kBAAkB;AAAA,MACrC;AAAA,MACA,SAAS,MAAM;AAAA,MACf;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAAA,IACrD;AAAA,EACF;AACF;;;ACrIA,SAAS,YAAAC,iBAAgB;AAGzB,IAAMC,8BAA6B;AAMnC,IAAM,gBAA2B;AAAA,EAC/B;AAAA,EACA,CAAC;AAAA,EACD,CAAC,GAAG,GAAG,CAAC;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,IAAI,OAAO,GAAM,EAAE,EAAE,EAAE,EAAE;AAAA,EAChE,EAAE,WAAW,EAAE,UAAU,KAAK,EAAE;AAAA,EAChC,EAAE,KAAK,MAAM,OAAO,QAAW,OAAO,OAAO,IAAI;AACnD;AAUA,IAAM,QAAQ,OACZ,UACA,SACA,MACA,SACA,qBACqG;AACrG,QAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,SAAS,QAAQ;AAC9D,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,gBAAgB,WAAW,MAAM;AAAE,eAAW,MAAM;AAAA,EAAG,GAAG,gBAAgB;AAChF,QAAM,CAAC,YAAY,QAAQ,IAAI,MAAMD,UAAS,MAAM,MAAM,KAAK;AAAA,IAC7D,QAAQ,SAAS;AAAA,IACjB,SAAS,EAAE,gBAAgB,oBAAoB,UAAU,IAAI,IAAI,OAAO,EAAE,QAAQ,GAAG,QAAQ;AAAA,IAC7F,MAAM,SAAS,WAAW,QAAQ,SAAY,KAAK,UAAU,IAAI;AAAA,IACjE,QAAQ,WAAW;AAAA,EACrB,CAAC,CAAC;AACF,eAAa,aAAa;AAC1B,MAAI,cAAc,CAAC,SAAU,QAAO;AAEpC,QAAM,CAAC,EAAE,MAAM,IAAI,MAAMA;AAAA,IACvB,YAAY,MAAM,SAAS,KAAK;AAAA,EAClC;AACA,SAAO,EAAE,YAAY,SAAS,QAAQ,QAAQ,UAAU,KAAK;AAC/D;AAEO,IAAM,eAAe,OAAO,UAAwD;AACzF,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,mBAAmB,MAAM,oBAAoBC;AAEnD,aAAW,WAAW,eAAe;AACnC,UAAM,SAAS,MAAM,MAAM,MAAM,UAAU,MAAM,SAAS,SAAS,MAAM,SAAS,gBAAgB;AAClG,UAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,QAAQ,kDAAkD,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAC9F;AAAA,MACF;AAAA,IACF;AAQA,QAAI,CAAC,OAAO,UAAW,OAAO,OAAO,WAAW,aAAa,OAAO,OAAO,WAAW,SAAU;AAC9F,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,YAAY,OAAO;AAAA,QACnB,QAAQ,gDAAgD,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAC5F;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,OAAO,cAAc,UAAU;AACnF,aAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,QAAQ;AAAA,QACR,YAAY,OAAO;AAAA,QACnB,QAAQ,sDAAsD,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,QAAQ;AAAA,IACR,YAAY,KAAK,IAAI,IAAI;AAAA,EAC3B;AACF;;;ACzFA,IAAMC,cAAa,CAAC,UAA8B,SAA4B;AAC5E,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,YAAY,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AACvE,QAAM,cAAc,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI;AACrD,SAAO,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,WAAW;AAC9D;AAEO,IAAM,eAAe,OAAO,UAA0D;AAC3F,QAAM,YAAY,cAAc,MAAM,YAAY;AAClD,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,UAAiC,CAAC;AAExC,aAAW,YAAY,WAAW;AAChC,QAAIA,YAAW,UAAU,IAAI,GAAG;AAC9B,YAAM,UAA+B;AAAA,QACnC;AAAA,QACA,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV;AACA,cAAQ,KAAK,OAAO;AACpB,YAAM,WAAW,OAAO;AACxB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,aAAa;AAAA,MAChC;AAAA,MACA,SAAS,MAAM;AAAA,MACf,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAAA,IACrD;AAAA,EACF;AACF;;;AC7CA,IAAM,eAAe,CAAC,QAA0B;AAG9C,QAAM,IAAI;AAEV,QAAM,OAAO,EAAE,YAAY,EAAE;AAE7B,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK,UAAU;AACb,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACb,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,WAAW;AACd,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,QAAQ;AACX,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,aAAa;AAChB,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,WAAW;AACd,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,WAAW;AAEd,aAAO,EAAE,SAAS,EAAE,SAAS,CAAC;AAAA,IAChC;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,YAAY;AAEf,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,SAAS;AACZ,aAAO,EAAE,WAAW,EAAE,QAAQ,SAAS,KAAK,EAAE,QAAQ,CAAC,IAAI,aAAa,EAAE,QAAQ,CAAC,EAAE,IAAI,IAAI;AAAA,IAC/F;AAAA,IACA,KAAK;AAAA,IACL,KAAK,SAAS;AACZ,aAAO,CAAC;AAAA,IACV;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACb,YAAM,MAA+B,CAAC;AAEtC,YAAM,QAAQ,OAAO,EAAE,UAAU,aAAa,EAAE,MAAM,IAAK,EAAE,SAAS,CAAC;AACvE,iBAAW,CAAC,KAAK,WAAW,KAAK,OAAO,QAAQ,KAAK,GAAG;AACtD,cAAM,SAAS,aAAa,YAAY,IAAI;AAC5C,YAAI,WAAW,OAAW,KAAI,GAAG,IAAI;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACb,aAAO,CAAC;AAAA,IACV;AAAA,IACA,KAAK;AAAA,IACL,KAAK,QAAQ;AACX,cAAO,oBAAI,KAAK,GAAE,YAAY;AAAA,IAChC;AAAA,IACA,SAAS;AACP,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,IAAM,oBAAoB,CAAC,WAA+B;AAE/D,SAAO,aAAa,OAAO,GAAG;AAChC;;;AC1FA,IAAM,aAAa,CAAC,WAAkD;AACpE,UAAQ,QAAQ;AAAA,IACd,KAAK,QAAQ;AAAE,aAAO;AAAA,IACtB;AAAA,IACA,KAAK,QAAQ;AAAE,aAAO;AAAA,IACtB;AAAA,IACA,KAAK,WAAW;AAAE,aAAO;AAAA,IACzB;AAAA,EACF;AACF;AAEO,IAAM,oBAAoB,CAAC,WAAsC;AACtE,QAAM,EAAE,UAAU,YAAY,YAAY,gBAAgB,WAAW,OAAO,IAAI;AAChF,QAAM,OAAO,WAAW,OAAO,MAAM;AACrC,QAAM,OAAO,GAAG,IAAI,IAAI,SAAS,MAAM,KAAK,SAAS,QAAQ,IAAI,UAAU;AAE3E,MAAI,OAAO,WAAW,QAAQ;AAC5B,UAAM,OAAO,mBAAmB,UAAU,YAAY,aAAa,SAAS,MAAM;AAClF,YAAQ,IAAI,GAAG,IAAI,SAAS,cAAc,GAAG,GAAG,IAAI,EAAE;AACtD;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,WAAW;AAC/B,YAAQ,IAAI,GAAG,IAAI,IAAI,UAAU,EAAE,EAAE;AACrC;AAAA,EACF;AAEA,UAAQ,IAAI,GAAG,IAAI,SAAS,cAAc,GAAG,WAAW,UAAU,QAAQ,EAAE;AAC9E;AAEO,IAAM,qBAAqB,CAAC,YAAsC;AACvE,QAAM,EAAE,OAAO,QAAQ,QAAQ,QAAQ,IAAI;AAC3C,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,mBAAmB,MAAM,IAAI,KAAK,YAAY,MAAM,YAAY,OAAO,UAAU;AAE7F,MAAI,SAAS,GAAG;AACd,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,WAAW;AACvB,eAAW,UAAU,QAAQ,SAAS;AACpC,UAAI,OAAO,WAAW,OAAQ;AAC9B,cAAQ,IAAI,QAAQ,OAAO,SAAS,QAAQ,KAAK,OAAO,UAAU,aAAa,EAAE;AAAA,IACnF;AAAA,EACF;AACF;;;ACxCA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,SAAS,eAAe,iBAAiB,kBAAkB,WAAW,YAAAC,iBAAgB;;;ACQtF,SAAS,MAAM,4BAAoC;AACnD;AAAA,EACE;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,OACK;AAyCP,IAAM,qBAAqB;AAC3B,IAAM,2BAA2B;AACjC,IAAM,qBAAqB;AAO3B,IAAM,qBAAqB,CAAC,UAC1B,OAAO,UAAU,YAAY,UAAU;AAEzC,IAAM,qBAAqB,CAAC,UAC1B,OAAO,UAAU,YAAY,UAAU;AAKzC,IAAI,uBAAuB;AAC3B,IAAM,oBAAoB,MAAc;AACtC,0BAAwB;AACxB,SAAO;AACT;AAEA,IAAM,iBAAiB,CAAC,WAAkC;AACxD,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,QAAI,OAAO,WAAW;AACpB,cAAQ;AACR;AAAA,IACF;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ;AACR,aAAO,IAAI,MAAM,gDAAgD,kBAAkB,IAAI,CAAC;AAAA,IAC1F,GAAG,kBAAkB;AACrB,UAAM,YAAY,MAAY;AAC5B,cAAQ;AACR,cAAQ;AAAA,IACV;AACA,UAAM,iBAAiB,CAAC,QAAqB;AAC3C,cAAQ;AACR,aAAO,IAAI,MAAM,qCAAgC,IAAI,OAAO,EAAE,CAAC;AAAA,IACjE;AACA,UAAM,UAAU,MAAY;AAC1B,mBAAa,KAAK;AAClB,aAAO,IAAI,WAAW,SAAS;AAC/B,aAAO,IAAI,iBAAiB,cAAc;AAAA,IAC5C;AACA,WAAO,GAAG,WAAW,SAAS;AAC9B,WAAO,GAAG,iBAAiB,cAAc;AAAA,EAC3C,CAAC;AACH;AAEA,IAAM,iBAAiB,CAAC,QAAgB,kBAAyC;AAC/E,QAAM,YAAY,+BAA+B,aAAa;AAC9D,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,IAAI,WAAW,KAAK;AAC3B,aAAO,IAAI,MAAM,2CAA2C,wBAAwB,IAAI,CAAC;AAAA,IAC3F,GAAG,wBAAwB;AAC3B,UAAM,QAAQ,CAAC,YAA2B;AACxC,mBAAa,KAAK;AAClB,aAAO,IAAI,WAAW,KAAK;AAC3B,UAAI,mBAAmB,OAAO,KAAK,QAAQ,WAAW,SAAS;AAC7D,eAAO,IAAI,MAAM,yCAAoC,QAAQ,aAAa,SAAS,EAAE,CAAC;AACtF;AAAA,MACF;AACA,cAAQ;AAAA,IACV;AACA,WAAO,GAAG,WAAW,KAAK;AAAA,EAC5B,CAAC;AACH;AAEO,IAAM,oBAAoB,OAC/B,UACmC;AACnC,QAAM,mBAAmB,MAAM,oBAAoB;AACnD,QAAM,SAAmB,CAAC;AAC1B,QAAM,UAA+E,CAAC;AAEtF,QAAM,SAAiB,qBAAqB,MAAM,SAAS;AAAA,IACzD,YAAY,CAAC,WAAW;AAAA,IACxB,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI;AAAA,EAC/C,CAAC;AAED,QAAM,cAAc,CAAC,QAAuB;AAC1C,QAAI,CAAC,mBAAmB,GAAG,EAAG;AAC9B,QAAI,IAAI,WAAW,SAAU;AAC7B,QAAI,IAAI,aAAa,MAAM,cAAe;AAK1C,UAAM,QAAQ;AACd,WAAO,KAAK,KAAK;AAGjB,aAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,UAAU,MAAM,GAAG;AAC5B,gBAAQ,OAAO,GAAG,CAAC;AACnB,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAG,iBAAiB,MAAM,WAAW;AAE5C,QAAM,eAAe,MAAM;AAE3B,QAAM,gBAAgB,kBAAkB;AACxC,SAAO,KAAK,iBAAiB,UAAU;AAAA,IACrC,OAAO,MAAM;AAAA,IACb;AAAA,EACF,CAAC;AACD,QAAM,CAAC,SAAS,IAAI,MAAMA,UAAS,MAAM,eAAe,QAAQ,aAAa,CAAC;AAC9E,MAAI,WAAW;AACb,WAAO,IAAI,iBAAiB,MAAM,WAAW;AAC7C,WAAO,WAAW;AAClB,UAAM;AAAA,EACR;AAEA,MAAI,SAAS;AACb,QAAM,QAAQ,MAAqB;AACjC,QAAI,OAAQ,QAAO,QAAQ,QAAQ;AACnC,aAAS;AACT,WAAO,IAAI,iBAAiB,MAAM,WAAW;AAE7C,eAAW,UAAU,QAAS,QAAO,QAAQ;AAC7C,YAAQ,SAAS;AACjB,WAAO,WAAW;AAClB,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAEA,QAAM,SAAS,CACb,WACA,YAAoB,qBACF;AAClB,QAAI,UAAU,MAAM,EAAG,QAAO,QAAQ,QAAQ;AAC9C,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAI,UAAU;AACd,YAAM,SAAS;AAAA,QACb;AAAA,QACA,SAAS,MAAM;AACb,cAAI,QAAS;AACb,oBAAU;AACV,uBAAa,KAAK;AAClB,kBAAQ;AAAA,QACV;AAAA,MACF;AACA,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,QAAS;AACb,kBAAU;AACV,cAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,YAAI,QAAQ,GAAI,SAAQ,OAAO,KAAK,CAAC;AACrC,eAAO,IAAI;AAAA,UACT,sDAAsD,SAAS,gBAAgB,OAAO,MAAM;AAAA,QAC9F,CAAC;AAAA,MACH,GAAG,SAAS;AACZ,cAAQ,KAAK,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,CAAC,GAAW,YAAoB,qBAAoC;AACvF,WAAO,OAAO,CAAC,YAAY,QAAQ,UAAU,GAAG,SAAS;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AD7NA,IAAM,wBAAwB;AAC9B,IAAM,gCAAgC;AAwE/B,IAAM,0BAA0B,CAAC,SAAiB,UAAU,MAA4B;AAC7F,MAAI,CAAC,GAAG,WAAW,MAAM,EAAG,QAAO,CAAC;AACpC,QAAM,QAA8B,CAAC;AACrC,QAAM,QAAkB,CAAC,MAAM;AAC/B,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,IAAI;AAC1B,QAAI,YAAY,OAAW;AAC3B,UAAM,UAAU,GAAG,YAAY,SAAS,EAAE,eAAe,KAAK,CAAC;AAC/D,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,KAAK,KAAK,SAAS,MAAM,IAAI;AAC9C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,QAAQ;AACnB;AAAA,MACF;AACA,UAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,WAAW,EAAG;AAC1D,YAAM,YAAY,KAAK,SAAS,OAAO;AACvC,UAAI,cAAc,UAAU,cAAc,QAAS;AACnD,YAAM,UAAU,cAAc,SAAS,wBAAwB;AAC/D,YAAM,eAAe,QAAQ,KAAK,MAAM,IAAI;AAC5C,UAAI,CAAC,aAAc;AACnB,YAAM,UAAU,IAAI,aAAa,CAAC,CAAC;AACnC,YAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,SAAS,sBAAsB,4BAA4B,EAAE;AAC/G,YAAM,UAAU,KAAK,QAAQ,OAAO;AAIpC,YAAM,eAAe,KAAK,SAAS,QAAQ,OAAO,EAAE,WAAW,MAAM,GAAG,KAAK;AAC7E,YAAM,KAAK;AAAA,QACT,UAAU;AAAA,QACV,MAAM,cAAc,SAAS,QAAQ;AAAA,QACrC,OAAO,EAAE,MAAM,cAAc,MAAM,UAAU,QAAQ;AAAA,MACvD,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,MAAM,SAAS,CAAC,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,QAAQ,CAAC;AACtE;AA0CA,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACjC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,cAAc,OAAmB;AAAA,EACrC,IAAI,CAAC,QAAQ,UAAU,YAAY;AACjC,UAAM,KAAK,OAAO,GAAG,QAAQ,QAAQ,KAC/B,OAAO,WAAW,YAAY,OAAO,aAAa,YAAY,KAAK,UAAU,MAAM,MAAM,KAAK,UAAU,QAAQ;AACtH,QAAI,CAAC,IAAI;AACP,YAAM,SAAS,UAAU,GAAG,OAAO,OAAO;AAC1C,YAAM,IAAI,eAAe,GAAG,MAAM,YAAY,KAAK,UAAU,QAAQ,CAAC,YAAY,KAAK,UAAU,MAAM,CAAC,EAAE;AAAA,IAC5G;AAAA,EACF;AAAA,EACA,IAAI,CAAC,OAAO,YAAY;AACtB,QAAI,CAAC,MAAO,OAAM,IAAI,eAAe,WAAW,8BAA8B,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,EACvG;AAAA,EACA,QAAQ,OAAO,IAAI,YAAY;AAC7B,UAAM,CAAC,GAAG,IAAI,MAAMC,UAAS,EAAE;AAC/B,QAAI,CAAC,IAAK,OAAM,IAAI,eAAe,WAAW,sCAAsC;AACpF,WAAO;AAAA,EACT;AAAA,EACA,SAAS,CAAC,OAAO,SAAS,YAAY;AACpC,QAAI,CAAC,QAAQ,KAAK,KAAK,GAAG;AACxB,YAAM,SAAS,UAAU,GAAG,OAAO,OAAO;AAC1C,YAAM,IAAI,eAAe,GAAG,MAAM,IAAI,KAAK,oBAAoB,OAAO,EAAE;AAAA,IAC1E;AAAA,EACF;AACF;AAiBA,IAAM,mBAAmB,CAAC,aAA0C;AAClE,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,cAAc,YAAY,EAAE,UAAU,SAAS,EAAG,QAAO,EAAE;AACxE,MAAI,OAAO,EAAE,WAAW,YAAY,EAAE,WAAW,UAAW,QAAO,EAAE;AACrE,SAAO;AACT;AAQA,IAAM,sBAAsB,CAAC,UAAoD;AAC/E,SAAO;AAAA,IACL,OAAO,OAAO,SAAS;AACrB,YAAM,EAAE,aAAa,WAAW,IAAI,MAAM,OAAO,mBAAmB;AACpE,YAAM,SAAS,MAAM,MAAM,aAAa,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC5F,YAAM,QAAQ,MAAM,SAAS,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AACzF,YAAM,OAAO,MAAM,QAAQ;AAC3B,YAAM,QAAQ,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3E,YAAM,cAAc;AAAA,QAClB,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MACZ;AACA,YAAM,YAAY,OAAO,aAAa,IAAI;AAC1C,YAAM,QAAQ;AACd,YAAM,SAAS;AAGf,YAAM,YAAY,MAAM,WAAW,KAAK;AACxC,YAAM,YAAY,WAAW,aAAa;AAC1C,aAAO,EAAE,OAAO,OAAO;AAAA,IACzB;AAAA,IACA,QAAQ,YAAY;AAClB,UAAI,CAAC,MAAM,MAAO;AAClB,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,mBAAmB;AAC1D,YAAM,cAAc,MAAM,KAAK;AAC/B,YAAM,QAAQ;AACd,YAAM,SAAS;AACf,YAAM,YAAY;AAAA,IACpB;AAAA,IACA,SAAS,OAAO,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO;AAAA,EAC7D;AACF;AAEA,IAAM,eAAe,CACnB,SACA,WACA,OACA,YAIA,WAC2B;AAC3B,SAAO,OAAO,UAAU;AACtB,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,QAAQ,SAAS;AAC1D,UAAM,UAAkC,EAAE,gBAAgB,oBAAoB,QAAQ,IAAI,IAAI,OAAO,EAAE,OAAO;AAC9G,QAAI,MAAM,MAAO,SAAQ,SAAS,GAAG,UAAU,IAAI,MAAM,KAAK;AAC9D,QAAI,MAAM,UAAW,SAAQ,cAAc,EAAE,UAAU,IAAI,MAAM;AACjE,UAAM,OAAoB,EAAE,QAAQ,QAAQ;AAG5C,QAAI,WAAW,SAAS,WAAW,OAAQ,MAAK,OAAO,KAAK,UAAU,SAAS,CAAC,CAAC;AACjF,UAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AACtC,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,eAAe;AACrB,WAAO;AAAA,EACT;AACF;AAEA,IAAM,gBAAgB,CACpB,SACA,WACA,OACA,eAC4B;AAC5B,SAAO,OAAO,OAAO,SAAS;AAC5B,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,SAAS,SAAS;AAC3D,UAAM,UAAkC,EAAE,gBAAgB,oBAAoB,QAAQ,IAAI,IAAI,OAAO,EAAE,OAAO;AAC9G,QAAI,MAAM,MAAO,SAAQ,SAAS,GAAG,UAAU,IAAI,MAAM,KAAK;AAC9D,QAAI,MAAM,UAAW,SAAQ,cAAc,EAAE,UAAU,IAAI,MAAM;AACjE,UAAM,OAAO,EAAE,MAAM,SAAS,CAAC,GAAG,UAAU,MAAM,SAAS;AAC3D,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,eAAe;AACrB,WAAO;AAAA,EACT;AACF;AAUA,IAAM,eAAe,CACnB,WACA,UACiB;AAIjB,QAAM,aAAa,MAAM,qBAAqB,iBAAiB,EAAE,KAAK;AACtE,QAAM,YAAY,GAAG,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO;AAC5F,QAAM,gBAAgB,QAAQ,SAAS;AAIvC,QAAM,YAAY,MAAM,eAAe,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO,KAAK;AACnH,QAAM,QAA0B,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,MAAM,cAAc,KAAK;AACjG,QAAM,SAAS,gBAAgB;AAI/B,QAAM,WAA4B,CAAC;AACnC,QAAM,cAAc,OAClB,aACmC;AACnC,UAAM,UAAU,MAAM,kBAA0B;AAAA,MAC9C,SAAS,MAAM;AAAA,MACf;AAAA,MACA,OAAO,MAAM;AAAA,MACb;AAAA,IACF,CAAC;AAGD,aAAS,KAAK,OAAO;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,mBAAmB,YAA2B;AAClD,WAAO,SAAS,SAAS,GAAG;AAC1B,YAAM,UAAU,SAAS,IAAI;AAC7B,UAAI,CAAC,QAAS;AACd,YAAM,CAAC,UAAU,IAAI,MAAMA,UAAS,MAAM,QAAQ,MAAM,CAAC;AACzD,UAAI,YAAY;AAAA,MAIhB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAmB;AAAA,IACvB,SAAS,aAAa,MAAM,SAAS,WAAW,OAAO,YAAY,SAAS;AAAA,IAC5E,UAAU,cAAc,MAAM,SAAS,WAAW,OAAO,UAAU;AAAA,IACnE;AAAA,IACA,SAAS,oBAAoB,KAAK;AAAA,IAClC;AAAA,IACA,QAAQ,YAAY;AAAA,EACtB;AAEA,SAAO,EAAE,KAAK,kBAAkB,MAAM;AACxC;AAEO,IAAM,iBAAiB,OAAO,UAA+D;AAClG,QAAM,QAAQ,wBAAwB;AACtC,QAAM,UAA8B,CAAC;AAErC,aAAW,aAAa,OAAO;AAC7B,UAAM,YAAY,GAAG,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO;AAC5F,QAAI,MAAM,UAAU,CAAC,UAAU,SAAS,MAAM,MAAM,EAAG;AAEvD,UAAM,CAAC,aAAa,GAAG,IAAI,MAAMA;AAAA,MAC/B,MAAM,OAAO,cAAc,UAAU,QAAQ,EAAE;AAAA,IACjD;AACA,QAAI,eAAe,CAAC,KAAK;AACvB,YAAM,IAAsB;AAAA,QAC1B;AAAA,QAAW,UAAU;AAAA,QAAY,QAAQ;AAAA,QAAQ,YAAY;AAAA,QAC7D,QAAQ,oBAAoB,UAAU,QAAQ,KAAK,aAAa,WAAW,SAAS;AAAA,MACtF;AACA,cAAQ,KAAK,CAAC;AACd,YAAM,WAAW,CAAC;AAClB;AAAA,IACF;AAEA,UAAM,WAAY,IAAkC;AACpD,QAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AAErD;AAAA,IACF;AAEA,eAAW,WAAW,UAAU;AAC9B,YAAM,IAAI;AACV,UAAI,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,QAAQ,WAAY;AAC/D,YAAM,iBAAiB,OAAO,EAAE,mBAAmB,WAAW,EAAE,iBAAiB;AACjF,YAAM,QAAQ,aAAa,WAAW,KAAK;AAC3C,YAAM,UAAU,KAAK,IAAI;AACzB,YAAM,CAAC,QAAQ,IAAI,MAAMA,UAAS,YAAY,EAAE,IAAI,MAAM,GAAG,CAAC;AAI9D,YAAMA,UAAS,MAAM,MAAM,iBAAiB,CAAC;AAC7C,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,UAAI;AACJ,UAAI,UAAU;AAGZ,cAAM,YAAY,iBAAiB,MAAM,MAAM,YAAY;AAC3D,cAAM,SAAS,YAAY,GAAG,SAAS,OAAO,aAAa,SAAS,MAAM,SAAS;AACnF,YAAI,iBACA,EAAE,WAAW,UAAU,EAAE,MAAM,QAAQ,SAAS,YAAY,QAAQ,eAAe,IACnF,EAAE,WAAW,UAAU,EAAE,MAAM,QAAQ,QAAQ,YAAY,OAAO;AAAA,MACxE,OAAO;AACL,YAAI,iBACA;AAAA,UACE;AAAA,UAAW,UAAU,EAAE;AAAA,UAAM,QAAQ;AAAA,UAAS;AAAA,UAC9C,QAAQ,2BAA2B,cAAc;AAAA,UACjD;AAAA,QACF,IACA,EAAE,WAAW,UAAU,EAAE,MAAM,QAAQ,QAAQ,WAAW;AAAA,MAChE;AACA,cAAQ,KAAK,CAAC;AACd,YAAM,WAAW,CAAC;AAAA,IACpB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,OAAO,EAAE;AAAA,IACnD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,OAAO,EAAE;AAAA,IACnD;AAAA,EACF;AACF;;;AE/bA,SAAS,oBAAoB,oBAAAC,yBAAwB;AAkDrD,IAAM,mBAAmB,CAAC,oBACxB,CAAC,aAA0C;AACzC,QAAM,SAAS,gBAAgB,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AACjF,SAAO,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAC/C;AAEF,IAAM,gBAAgB,CAAC,UAA8B,WAAwC;AAC3F,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO,GAAG,SAAS,MAAM;AAChF;AAEA,IAAM,gBAAgB,CAAC,SAAgC,WACrD,SAAS,QAAQ,OAAO,OAAK,cAAc,EAAE,UAAU,MAAM,CAAC,IAAI;AAEpE,IAAM,eAAe,CAAC,SAA6B,WAAmD;AACpG,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,cAAc,QAAQ,SAAS,MAAM;AACrD,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,QAAQ,QAAQ,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE;AAAA,IACjD,SAAS,QAAQ,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAAA,IACrD;AAAA,EACF;AACF;AAEO,IAAM,cAAc,OAAO,UAAyD;AACzF,QAAM,UAA8B,EAAE,aAAa,GAAG,aAAa,EAAE;AACrE,QAAM,UAAkC,CAAC;AACzC,MAAI,MAAM,WAAW;AACnB,UAAM,aAAa,MAAM,qBAAqBC,kBAAiB,EAAE,KAAK;AACtE,YAAQ,SAAS,GAAG,UAAU,IAAI,MAAM,SAAS;AAAA,EACnD;AACA,QAAM,WAAW,iBAAiB,MAAM,eAAe;AAEvD,MAAI,CAAC,MAAM,SAAS;AAClB,UAAM,WAAW,MAAM,iBAAiB;AAAA,MACtC,cAAc,MAAM;AAAA,MACpB,SAAS,MAAM;AAAA,MACf,MAAM,MAAM;AAAA,MACZ;AAAA,MACA;AAAA,IACF,CAAC;AACD,YAAQ,WAAW,aAAa,UAAU,MAAM,MAAM;AAEtD,UAAM,OAAO,MAAM,wBAAwB;AAAA,MACzC,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,MAAM,MAAM;AAAA,MACZ;AAAA,IACF,CAAC;AACD,YAAQ,OAAO,aAAa,MAAM,MAAM,MAAM;AAE9C,QAAI,CAAC,MAAM,aAAa;AACtB,YAAM,YAAY,MAAM,kBAAkB;AAAA,QACxC,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,QAClB,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ;AAAA,QACA;AAAA,MACF,CAAC;AACD,cAAQ,YAAY,aAAa,WAAW,MAAM,MAAM;AAAA,IAC1D;AAEA,QAAI,CAAC,MAAM,QAAQ;AACjB,YAAM,OAAO,MAAM,aAAa;AAAA,QAC9B,cAAc,MAAM;AAAA,QACpB,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ;AAAA,MACF,CAAC;AACD,cAAQ,OAAO,aAAa,MAAM,MAAM,MAAM;AAAA,IAChD;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,UAAU;AAMnB,QAAI;AACF,YAAM,mBAAmB;AAAA,IAC3B,QAAQ;AAAA,IAER;AACA,UAAM,SAAS,MAAM,eAAe;AAAA,MAClC,SAAS,MAAM;AAAA,MACf,mBAAmB,MAAM;AAAA,MACzB,QAAQ,MAAM;AAAA;AAAA;AAAA,MAGd,cAAc,MAAM;AAAA,IACtB,CAAC;AACD,YAAQ,SAAS;AAAA,EACnB;AAEA,UAAQ,eAAe,QAAQ,UAAU,UAAU,MAC9C,QAAQ,MAAM,UAAU,MACxB,QAAQ,WAAW,UAAU,MAC7B,QAAQ,MAAM,UAAU,MACxB,QAAQ,QAAQ,UAAU;AAC/B,UAAQ,eAAe,QAAQ,UAAU,UAAU,MAC9C,QAAQ,MAAM,UAAU,MACxB,QAAQ,WAAW,UAAU,MAC7B,QAAQ,MAAM,UAAU,MACxB,QAAQ,QAAQ,UAAU;AAE/B,SAAO;AACT;AAKA,IAAM,YAAY,MAAe;AAC/B,MAAI,QAAQ,IAAI,SAAU,QAAO;AACjC,MAAI,QAAQ,IAAI,YAAa,QAAO;AACpC,SAAO,QAAQ,OAAO;AACxB,GAAG;AACH,IAAM,QAAQ,CAAC,MAAc,SAA0B,WAAW,QAAU,IAAI,IAAI,IAAI,YAAc;AACtG,IAAM,QAAQ,CAAC,MAAsB,MAAM,MAAM,CAAC;AAClD,IAAM,MAAM,CAAC,MAAsB,MAAM,MAAM,CAAC;AAChD,IAAM,SAAS,CAAC,MAAsB,MAAM,MAAM,CAAC;AACnD,IAAM,MAAM,CAAC,MAAsB,MAAM,KAAK,CAAC;AAC/C,IAAM,OAAO,CAAC,MAAsB,MAAM,KAAK,CAAC;AAOhD,IAAM,UAAU,CAAC,MACf,KAAK,aAAa,IAAI,EAAE,UAAU;AACpC,IAAM,YAAY,CAAC,MACjB,KAAK,aAAa,IAAI,EAAE,UAAU;AAKpC,IAAM,kBAAkB,CAAC,OAAe,MAAsE;AAC5G,MAAI,CAAC,EAAG,QAAO,KAAK,IAAI,QAAG,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC,IAAI,IAAI,iBAAiB,CAAC;AAC1E,QAAM,QAAQ,EAAE,WAAW;AAC3B,QAAM,OAAO,QAAQ,MAAM,QAAG,IAAI,IAAI,QAAG;AACzC,QAAM,OAAO,QACT,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,SAAS,IACrC,GAAG,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,SAAS,CAAC,KAAK,IAAI,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,SAAS,CAAC;AACtF,QAAM,SAAmB,CAAC;AAC1B,QAAM,KAAK,QAAQ,CAAC;AACpB,QAAM,KAAK,UAAU,CAAC;AACtB,MAAI,GAAI,QAAO,KAAK,OAAO,GAAG,EAAE,gBAAgB,CAAC;AACjD,MAAI,GAAI,QAAO,KAAK,IAAI,GAAG,EAAE,UAAU,CAAC;AACxC,QAAM,QAAQ,OAAO,SAAS,IAAI,KAAK,IAAI,MAAG,CAAC,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK;AAC9E,SAAO,KAAK,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,IAAI,IAAI,GAAG,KAAK;AACtD;AAEA,IAAM,gBAAgB,CAAC,OACpB,GAAG,WAAW,CAAC,GACb,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,IAAI,CAAC,OAAO;AAAA,EACX,OAAO,GAAG,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,OAAO;AAAA,EAClE,QAAQ,EAAE,YAAY,GAAG,EAAE,UAAU,QAAQ,KAAK,EAAE,SAAS,MAAM,EAAE;AACvE,EAAE;AAEN,IAAM,gBAAgB,CAAC,OAAe,OACnC,GAAG,WAAW,CAAC,GACb,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EACpC,IAAI,CAAC,OAAO;AAAA,EACX,OAAO,GAAG,KAAK,KAAK,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,OAAO;AAAA,EAC5E,QAAQ,EAAE;AACZ,EAAE;AAEN,IAAM,aAAa,CAAC,GAAsC,YACvD,GAAG,WAAW,CAAC,GACb,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EACjC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,SAAS,OAAO,EAAE,QAAQ,IAAI,QAAQ,EAAE,OAAO,EAAE;AAEhF,IAAM,eAAe,CAAC,OAA8B,SAAiB,MAAmB,WAAyB;AAC/G,MAAI,KAAK,WAAW,EAAG;AACvB,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC;AACtD,aAAW,OAAO,MAAM;AACtB,YAAQ,IAAI,KAAK,MAAM,MAAM,CAAC,IAAI,IAAI,KAAK,EAAE;AAC7C,QAAI,IAAI,OAAQ,SAAQ,IAAI,SAAS,IAAI,IAAI,MAAM,CAAC,EAAE;AAAA,EACxD;AACF;AAEO,IAAM,mBAAmB,CAAC,YAAsC;AACrE,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,gBAAgB,YAAY,QAAQ,QAAQ,CAAC;AACzD,UAAQ,IAAI,gBAAgB,oBAAoB,QAAQ,IAAI,CAAC;AAC7D,UAAQ,IAAI,gBAAgB,cAAc,QAAQ,SAAS,CAAC;AAC5D,UAAQ,IAAI,gBAAgB,QAAQ,QAAQ,IAAI,CAAC;AACjD,UAAQ,IAAI,gBAAgB,UAAU,QAAQ,MAAM,CAAC;AAIrD,QAAM,SAAsB;AAAA,IAC1B,GAAG,cAAc,QAAQ,QAAQ;AAAA,IACjC,GAAG,cAAc,QAAQ,IAAI;AAAA,IAC7B,GAAG,cAAc,QAAQ,SAAS;AAAA,IAClC,GAAG,cAAc,QAAQ,IAAI;AAAA,IAC7B,GAAG,WAAW,QAAQ,QAAQ,MAAM;AAAA,EACtC;AACA,eAAa,KAAK,yDAAoD,QAAQ,QAAG;AAGjF,eAAa,QAAQ,0DAAqD,WAAW,QAAQ,QAAQ,OAAO,GAAG,QAAG;AAGlH,eAAa,QAAQ,+DAA0D,WAAW,QAAQ,QAAQ,OAAO,GAAG,GAAG;AAGvH,QAAM,UAAuB;AAAA,IAC3B,GAAG,cAAc,YAAY,QAAQ,QAAQ;AAAA,IAC7C,GAAG,cAAc,QAAQ,QAAQ,IAAI;AAAA,IACrC,GAAG,cAAc,cAAc,QAAQ,SAAS;AAAA,IAChD,GAAG,cAAc,QAAQ,QAAQ,IAAI;AAAA,EACvC;AACA,eAAa,KAAK,wCAAmC,SAAS,QAAG;AAEjE,QAAM,aAAa,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,QAAQ,IAAI,IAAI,QAAQ,QAAQ,SAAS,IAC5F,QAAQ,QAAQ,IAAI,IAAI,QAAQ,QAAQ,MAAM;AAClD,QAAM,eAAe,UAAU,QAAQ,QAAQ,IAAI,UAAU,QAAQ,IAAI,IAAI,UAAU,QAAQ,SAAS,IACpG,UAAU,QAAQ,IAAI,IAAI,UAAU,QAAQ,MAAM;AAEtD,UAAQ,IAAI,EAAE;AACd,QAAM,QAAQ;AAAA,IACZ,MAAM,GAAG,QAAQ,WAAW,SAAS;AAAA,IACrC,QAAQ,cAAc,IAAI,IAAI,GAAG,QAAQ,WAAW,SAAS,IAAI,IAAI,UAAU;AAAA,IAC/E,aAAa,IAAI,OAAO,GAAG,UAAU,gBAAgB,IAAI,IAAI,iBAAiB;AAAA,IAC9E,eAAe,IAAI,OAAO,GAAG,YAAY,UAAU,IAAI,IAAI,WAAW;AAAA,EACxE;AACA,UAAQ,IAAI,YAAY,MAAM,KAAK,IAAI,UAAO,CAAC,CAAC,EAAE;AAClD,UAAQ,IAAI,IAAI,kGAA6E,CAAC;AAChG;;;AC5NA,IAAM,SAAS,oBAAI,IAAuB;AAC1C,IAAM,WAAW,oBAAI,IAAyB;AAC9C,IAAI,WAAgC;AAO7B,IAAM,oBAAoB,CAAC,UAA2B;AAC3D,SAAO,IAAI,MAAM,MAAM,KAAK;AAC9B;AAEO,IAAM,iBAAiB,MAAmB,CAAC,GAAG,OAAO,OAAO,CAAC;AAU7D,IAAM,sBAAsB,CAAqB,SAAiB,YAAyC;AAChH,WAAS,IAAI,SAAS,OAAO;AAC/B;AAEO,IAAM,iBAAiB,CAAC,YAA6C,SAAS,IAAI,OAAO;AAQzF,IAAM,uBAAuB,CAAC,gBAA2C;AAC9E,aAAW;AACb;AAEO,IAAM,kBAAkB,MAA2B;AAEnD,IAAM,8BAA8B,MAAY;AACrD,SAAO,MAAM;AACb,WAAS,MAAM;AACf,aAAW;AACb;","names":["tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","shouldSkip","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","EXPECTED_ERROR_CODE","tryCatch","requiresLogin","shouldSkip","tryCatch","DEFAULT_REQUEST_TIMEOUT_MS","shouldSkip","tryCatch","tryCatch","tryCatch","getProjectConfig","getProjectConfig"]}
|