first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
# `testing` — `runAuthSessionConformance` harness
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.