# `runTenantProviderConformance` — reusable conformance harness _(no submodules)_ ## Purpose Every concrete `TenantProvider` — the in-memory reference impl (`api-tenant-provider-memory`, ), the host-DB-backed adapter, any directory-service adapter — MUST pass the same behavioural suite. Putting the suite here, importable as `runTenantProviderConformance`, guarantees that adding a new provider source is a one-line job in the adapter's test file and that every adapter agrees on the contract's edge cases (active-only default, includeSuspended, lookup-returns-null, cursor invalidation, dispose idempotency). The harness uses `describe` / `it` / `expect` from `@suaveplan/testing/runner` so it works under both the Vitest default runner and the Bun-conditional runner (rule 11). ## Features - Eight tests covering `list` default filter, `includeSuspended`, `count`, `lookup`-returns-record, `lookup`-returns-null, pagination round-trip, invalid-cursor, and dispose idempotency. - `factory(seed)` callback constructs a fresh provider per test — no shared singleton, no leaked state. - Compatible with both Vitest and Bun via `@suaveplan/testing/runner`. - Every `it(...)` carries `{ timeout: 5000 }` per CLAUDE.md. ## Usage: Basic ```ts // In api-tenant-provider-memory/src/memory.test.ts (Stage 4): import { runTenantProviderConformance } from "@suaveplan/api-tenant-provider/testing"; import { MemoryTenantProvider } from "./memory.js"; runTenantProviderConformance({ label: "in-memory", factory: (seed) => new MemoryTenantProvider(seed), }); ``` ## Usage: Advanced ```ts import { runTenantProviderConformance } from "@suaveplan/api-tenant-provider/testing"; import { SurrealTenantProvider } from "./surreal.js"; import { startSurrealdb } from "@suaveplan/testing-integration-surrealdb"; // Spin up a real SurrealDB container per provider instance — the // factory can be async, so heavyweight fixtures are first-class. runTenantProviderConformance({ label: "surrealdb", factory: async (seed) => { const db = await startSurrealdb(); const provider = new SurrealTenantProvider(db); for (const record of seed) await provider.put(record); // Wrap dispose so the container also stops when the test ends. const originalDispose = provider.dispose.bind(provider); provider.dispose = async () => { await originalDispose(); await db.stop(); }; return provider; }, }); ``` ## API Reference - `interface TenantProviderConformanceOptions { readonly label: string; readonly factory: (seed: readonly TenantRecord[]) => Promise | TenantProvider }`. - `function runTenantProviderConformance(options: TenantProviderConformanceOptions): void` — registers a `describe` block with the documented tests. ## Implementation Notes The harness is intentionally minimal: it does not test telemetry, metrics, or implementation-specific properties — those belong in the adapter's own test file. It only pins the cross-adapter contract that every consumer of `TenantProvider` relies on. The `factory` callback receives a `readonly TenantRecord[]` seed and MUST return a provider in which `list({ includeSuspended: true })` yields exactly the active and suspended subset of `seed` (deterministic order is not required — assertions normalise via sort). `dispose` idempotency is tested by calling `dispose()` twice and asserting the second call resolves. Adapters whose `dispose` reasonably fails on double-call must reset internal state on the first call to make the second a no-op.