Initial Commit
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user