Files
suaveplan/packages/api-stack/api/api-auth-provider/src/testing/conformance.ts
2026-05-27 23:29:53 +01:00

168 lines
5.9 KiB
TypeScript

/**
* Reusable conformance assertions every concrete `AuthProviderAdapter` MUST
* pass. Implemented as a set of pure functions so the runner-agnostic
* assertions in this package can be driven from `@suaveplan/testing/runner`
* without forcing concrete providers onto a specific runner.
*
* `runAuthProviderConformance` returns a per-category list of scenario
* results; concrete providers wrap it in `describe`/`it` from their own test
* file (`import { describe, it, expect } from "@suaveplan/testing/runner";`).
*/
import { detectAuthPathCollisions } from "../collision-detector/collision-detector.js";
import { AUTH_PROVIDER_CODES } from "../error-codes/error-codes.js";
import { PathOwnership } from "../path-ownership/path-ownership.js";
import { isAuthPathPrefix } from "../provider/provider.js";
import type { AuthProviderAdapter } from "../provider/provider.types.js";
import { listProviderRouteNames } from "../route-surface/route-surface.js";
import type {
AuthProviderConformanceCategory,
AuthProviderConformanceOptions,
ConformanceScenarioResult,
} from "./conformance.types.js";
/**
* Returns `true` when re-registering the same provider on an existing
* registry does NOT throw (the documented idempotency invariant).
*/
export function isRegisterIdempotent(
registry: PathOwnership,
provider: AuthProviderAdapter
): boolean {
try {
registry.register(provider);
return true;
} catch {
return false;
}
}
/**
* Returns `true` when attempting to register `incoming` against a registry
* already owned by another provider throws
* `AUTH_PROVIDER.MULTIPLE_PROVIDERS_REGISTERED`. Any other thrown shape — or
* no throw at all — returns `false`.
*/
export function rejectsSecondProvider(
registry: PathOwnership,
incoming: AuthProviderAdapter
): boolean {
try {
registry.register(incoming);
return false;
} catch (err) {
const code =
err instanceof Error && "code" in err
? (err as { readonly code: unknown }).code
: undefined;
return code === AUTH_PROVIDER_CODES.MULTIPLE_PROVIDERS_REGISTERED;
}
}
/**
* Drive the harness against the supplied factory. Returns one
* `ConformanceScenarioResult` per scenario, grouped per category. Concrete
* providers iterate the result list and turn each entry into an `it()`
* assertion.
*/
export function runAuthProviderConformance(
options: AuthProviderConformanceOptions
): ReadonlyArray<ConformanceScenarioResult> {
const results: ConformanceScenarioResult[] = [];
// --- path-ownership ----------------------------------------------------
const pathOwnership: AuthProviderConformanceCategory = "path-ownership";
{
const provider = options.factory();
const registry = new PathOwnership();
registry.register(provider);
results.push({
category: pathOwnership,
name: "registers provider on first call",
passed: registry.hasProvider(),
});
results.push({
category: pathOwnership,
name: "re-registering same provider is idempotent",
passed: isRegisterIdempotent(registry, provider),
});
const second = options.factory();
const distinct: typeof second = { ...second, id: `${second.id}.alt` };
results.push({
category: pathOwnership,
name: "second provider rejected with MULTIPLE_PROVIDERS_REGISTERED",
passed: rejectsSecondProvider(registry, distinct),
});
}
// --- route-surface -----------------------------------------------------
const routeSurface: AuthProviderConformanceCategory = "route-surface";
{
const provider = options.factory();
results.push({
category: routeSurface,
name: "pathPrefix is a legal prefix",
passed: isAuthPathPrefix(provider.pathPrefix),
});
const names = listProviderRouteNames();
results.push({
category: routeSurface,
name: "canonical surface enumerates every documented name",
passed: names.length >= 9,
detail: `canonical names: ${names.join(", ")}`,
});
}
// --- lifecycle ---------------------------------------------------------
const lifecycle: AuthProviderConformanceCategory = "lifecycle";
{
const provider = options.factory();
const lc = provider.lifecycle;
results.push({
category: lifecycle,
name: "lifecycle quartet present",
passed:
typeof lc.install === "function" &&
typeof lc.start === "function" &&
typeof lc.stop === "function" &&
typeof lc.uninstall === "function",
});
}
// --- collision ---------------------------------------------------------
const collision: AuthProviderConformanceCategory = "collision";
{
const provider = options.factory();
const innerReport = detectAuthPathCollisions({
provider,
plugins: [
{
name: "plugin-x",
routes: [{ path: `${provider.pathPrefix}/custom` }],
},
],
});
results.push({
category: collision,
name: "plugin route inside auth prefix is reported",
passed: innerReport.hasCollisions,
});
const outerReport = detectAuthPathCollisions({
provider,
plugins: [
{
name: "plugin-y",
routes: [{ path: "/billing/invoices" }],
},
],
});
results.push({
category: collision,
name: "plugin route outside auth prefix is not reported",
passed: !outerReport.hasCollisions,
});
}
return Object.freeze(results);
}