import type { SecretsLike } from "@suaveplan/api-types/secrets"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { SECRETS_CODES, type SecretsCode } from "../error-codes/error-codes.js"; /** * Factory-supplied test fixture for the secrets-source conformance harness. * Implementations populate `seed` (initial name → value map), then return * the `SecretsLike` instance to exercise. */ export interface SecretsConformanceFixture { /** The `SecretsLike` under test. */ readonly secrets: SecretsLike; /** Optional teardown — called after every scenario. */ readonly teardown?: () => Promise | void; } /** * Construction options for the conformance harness. Sources declare * `caseSensitive` (most are; environment-variable sources may not be on * Windows) and `supportsListReadingAhead` (`list()` returns every seeded * name without resolving them first). */ export interface RunSecretsConformanceOptions { /** Human-readable label embedded in `describe` titles. */ readonly label: string; /** * Asynchronously build a fresh fixture for each scenario. The factory * receives the seed map for the upcoming case and MUST honour it. */ readonly factory: ( seed: Readonly> ) => Promise | SecretsConformanceFixture; /** * Predicate that recognises the source's representation of a * `SECRETS.SECRET_NOT_FOUND` failure thrown from `require`. Defaults to * matching `(err as { code?: string }).code === "SECRETS.SECRET_NOT_FOUND"`. */ readonly isNotFound?: (err: unknown) => boolean; } function defaultIsNotFound(err: unknown): boolean { const code: SecretsCode = SECRETS_CODES.SECRET_NOT_FOUND; return ( typeof err === "object" && err !== null && "code" in err && (err as { code: unknown }).code === code ); } /** * Reusable spec-conformance suite for every concrete `SecretsLike` * implementation. Covers the four documented behaviours: `get` returns a * present value, `get` returns undefined for missing names, `require` * resolves to the value, and `require` throws `SECRETS.SECRET_NOT_FOUND` * for missing names. Also exercises `list()` enumeration. * * Every Stage-4/Stage-8 source MUST call this harness from its own test * suite. Failing the harness means the source breaks the cross-package * contract. */ export function runSecretsConformance( options: RunSecretsConformanceOptions ): void { const { label, factory } = options; const isNotFound = options.isNotFound ?? defaultIsNotFound; describe(`${label} — SecretsLike conformance`, () => { it("get resolves to the seeded value", { timeout: 5000 }, async () => { const fx = await factory({ ALPHA: "one" }); try { await expect(fx.secrets.get("ALPHA")).resolves.toBe("one"); } finally { await fx.teardown?.(); } }); it("get resolves to undefined for missing names", { timeout: 5000, }, async () => { const fx = await factory({}); try { await expect( fx.secrets.get("MISSING") ).resolves.toBeUndefined(); } finally { await fx.teardown?.(); } }); it("require resolves to the seeded value", { timeout: 5000, }, async () => { const fx = await factory({ BETA: "two" }); try { await expect(fx.secrets.require("BETA")).resolves.toBe("two"); } finally { await fx.teardown?.(); } }); it("require throws SECRET_NOT_FOUND for missing names", { timeout: 5000, }, async () => { const fx = await factory({}); try { let caught: unknown; try { await fx.secrets.require("MISSING"); } catch (err) { caught = err; } expect(caught).toBeDefined(); expect(isNotFound(caught)).toBe(true); } finally { await fx.teardown?.(); } }); it("list enumerates seeded names", { timeout: 5000 }, async () => { const fx = await factory({ A: "1", B: "2" }); try { const names = [...fx.secrets.list()].sort(); expect(names).toEqual(["A", "B"]); } finally { await fx.teardown?.(); } }); }); }