# `testing` — `runAuthSessionConformance` harness _(no submodules)_ ## Purpose A reusable Vitest `describe(...)` block every concrete `SessionResolver` must pass before publishing. The harness covers the three behaviours that define a correct resolver implementation independent of the underlying credential shape: returning `null` for unrecognised requests, returning the documented `Principal` for a recognised request, and an idempotent `dispose()`. Concrete adapters in Stages 4/6/8 (`api-auth-jwt`, `api-auth-anonymous`, `api-auth-bearer-static`, `api-auth-better-auth`, `api-auth-rolled`) call `runAuthSessionConformance({ … })` from one of their `*.test.ts` files. The single conformance suite means every adapter ships against the same observable contract; new adapters drop in without re-deriving the test plan. ## Features - Pure function — wires `describe` / `it` from `@suaveplan/testing/runner` so the suite runs identically under Vitest (default) and `bun:test` (under the `bun` test condition). - Factory pattern — adapters re-construct their resolver inside each `it` so parallel runners do not share resolver state across tests. - Every `it` carries `{ timeout: 5000 }` per the project's hard rule. ## Usage: Basic ```ts import { runAuthSessionConformance } from "@suaveplan/api-auth/testing"; import type { PrincipalId, Scope } from "@suaveplan/api-types/principal"; import { buildAnonymousResolver } from "../src/anonymous.js"; runAuthSessionConformance({ label: "anonymous", factory: () => buildAnonymousResolver(), seedNull: () => new Request("https://example.test/api"), seedPrincipal: () => ({ request: new Request("https://example.test/api", { headers: { "x-test-principal": "1" }, }), expected: { id: "anon" as PrincipalId, scopes: [] as readonly Scope[], claims: {}, }, }), }); ``` ## Usage: Advanced Compose the harness with adapter-specific extra coverage (per-adapter codes, JWK rotation, etc.): ```ts import { describe, it, expect } from "@suaveplan/testing/runner"; import { runAuthSessionConformance } from "@suaveplan/api-auth/testing"; import { buildJwtResolver, JWT_CODES } from "../src/index.js"; runAuthSessionConformance({ label: "jwt-hs256", factory: async () => buildJwtResolver({ jwks: testJwks }), seedNull: () => new Request("https://example.test/api"), seedPrincipal: () => ({ request: new Request("https://example.test/api", { headers: { authorization: `Bearer ${testToken}` }, }), expected: testPrincipal, }), }); describe("jwt-hs256 specific", () => { it("emits JWT.EXPIRED on stale token", { timeout: 5000 }, async () => { /* … */ }); }); ``` ## API Reference | Symbol | Kind | Description | |-----------------------------------|-------------|----------------------------------------------------------------------| | `runAuthSessionConformance` | function | Adds a `describe(...)` block; runs three baseline scenarios. | | `AuthSessionConformanceFactory` | interface | `{ factory, label, seedNull, seedPrincipal }`. | ## Implementation Notes - The harness asserts `id` + `scopes` equality on the principal, not deep equality of `claims` — `claims` is intentionally free-form and adapters may add provider-specific fields that callers must not pin in conformance. - `dispose()` is asserted to resolve to `undefined`, not just "not throw" — some legacy resolvers were returning the wrong shape from the second invocation; this assertion caught it. - `expect` and `describe`/`it` come from `@suaveplan/testing/runner` per project rule 11.