3.8 KiB
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/itfrom@suaveplan/testing/runnerso the suite runs identically under Vitest (default) andbun:test(under thebuntest condition). - Factory pattern — adapters re-construct their resolver inside each
itso parallel runners do not share resolver state across tests. - Every
itcarries{ timeout: 5000 }per the project's hard rule.
Usage: Basic
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.):
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+scopesequality on the principal, not deep equality ofclaims—claimsis intentionally free-form and adapters may add provider-specific fields that callers must not pin in conformance. dispose()is asserted to resolve toundefined, not just "not throw" — some legacy resolvers were returning the wrong shape from the second invocation; this assertion caught it.expectanddescribe/itcome from@suaveplan/testing/runnerper project rule 11.