| 1 | import * as Test from "@/Test/Alchemy"; |
| 2 | import type { TestOptions } from "alchemy-test"; |
| 3 | import { makeApiGatewayTestLease } from "./TestLease.ts"; |
| 4 | |
| 5 | const apiGatewayOptions = (options: TestOptions | undefined): TestOptions => { |
| 6 | const normalized = |
| 7 | typeof options === "number" ? { timeout: options } : options; |
| 8 | return { |
| 9 | ...normalized, |
| 10 | // Once a file owns the service lease an individual lifecycle is fast — |
| 11 | // observed passes at suite concurrency 32 run 3–30s. The failures that |
| 12 | // motivated this budget were throttle storms at concurrency 64: the |
| 13 | // account-wide REST control-plane quota is shared with ApiGatewayV2 and |
| 14 | // the rest of the run, so distilled's TooManyRequests backoff can stack |
| 15 | // a 3s operation past 120s. 300s absorbs the storm while still failing |
| 16 | // a genuine provider hang fast relative to the full-run timeout. |
| 17 | timeout: normalized?.timeout ?? 300_000, |
| 18 | // API Gateway REST mutations share an account-wide throttle. A timed-out |
| 19 | // retry starts with a fresh scratch state and therefore cannot reclaim the |
| 20 | // physical API from the interrupted attempt, so these live tests run once. |
| 21 | // TestLease serializes only this service's files; do not use the runner's |
| 22 | // global `exclusive` lock here because that would also block unrelated AWS |
| 23 | // services while API Gateway waits on its slow control plane. |
| 24 | retry: 0, |
| 25 | }; |
| 26 | }; |
| 27 | |
| 28 | /** |
| 29 | * API Gateway REST test adapter. |
| 30 | * |
| 31 | * The old Vitest project ran this directory in a single fork with file |
| 32 | * concurrency disabled. A shared root-hook lease restores that scheduling |
| 33 | * contract without teaching the generic runner about service paths. |
| 34 | */ |
| 35 | export const make = <ROut = any>(options: Test.MakeOptions<ROut>) => { |
| 36 | const api = Test.make(options); |
| 37 | const testLease = makeApiGatewayTestLease(); |
| 38 | |
| 39 | // The first 12 files can enter the runner together. Waiting here is |
| 40 | // intentional service scheduling rather than a cloud operation, so allow |
| 41 | // the queue to span the full hard-walled AWS run. |
| 42 | api.beforeAll(testLease.acquire, { timeout: 3_600_000 }); |
| 43 | api.afterAll(testLease.release); |
| 44 | |
| 45 | const provider = api.test.provider; |
| 46 | |
| 47 | const providerWithOptions = ((name, fn, testOptions) => |
| 48 | provider(name, fn, apiGatewayOptions(testOptions))) as typeof provider; |
| 49 | providerWithOptions.skip = (name, fn, testOptions) => |
| 50 | provider.skip(name, fn, apiGatewayOptions(testOptions)); |
| 51 | providerWithOptions.skipIf = (condition) => (name, fn, testOptions) => |
| 52 | provider.skipIf(condition)(name, fn, apiGatewayOptions(testOptions)); |
| 53 | |
| 54 | api.test.provider = providerWithOptions; |
| 55 | return api; |
| 56 | }; |