import './assert_dev_env.js';
/**
 * Assertion helpers for auth attack surface testing.
 *
 * Plain functions called inside explicit `test()` blocks to verify
 * surface snapshots, public routes, and middleware stacks.
 *
 * @module
 */
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { assert } from 'vitest';
import { is_public_auth } from '../http/auth_shape.js';
/**
 * Resolve an absolute path relative to the caller's module.
 *
 * @param import_meta_url - the caller's `import.meta.url`
 */
export const resolve_fixture_path = (filename, import_meta_url) => resolve(dirname(fileURLToPath(import_meta_url)), filename);
/**
 * Compare live surface against a committed snapshot JSON file.
 *
 * Failure message instructs the developer to run `gro gen` to update the
 * snapshot — every fuz_app consumer wires the snapshot through a `*.gen.ts`
 * file so regeneration goes through the same pipeline as the rest of the
 * generated artifacts.
 *
 * @param surface - the live surface to check
 * @param snapshot_path - absolute path to the committed JSON snapshot
 * @throws AssertionError if the live surface does not deep-equal the snapshot,
 *   or `Error` if the snapshot file is unreadable / malformed JSON.
 */
export const assert_surface_matches_snapshot = (surface, snapshot_path) => {
    const committed = JSON.parse(readFileSync(snapshot_path, 'utf-8'));
    assert.deepStrictEqual(surface, committed, 'Attack surface changed! Run `gro gen` to update the snapshot, then review the diff.');
};
/**
 * Verify surface generation is deterministic (build twice, compare).
 */
export const assert_surface_deterministic = (build_surface) => {
    assert.deepStrictEqual(build_surface(), build_surface());
};
/**
 * Bidirectional check: no unexpected public routes, no missing expected ones.
 *
 * @param surface - the app surface to check
 * @param expected_public - format: `['GET /health', 'POST /api/account/login']`
 * @throws AssertionError if the live surface has public routes not in
 *   `expected_public`, or if any entry in `expected_public` is missing from
 *   the live surface.
 */
export const assert_only_expected_public_routes = (surface, expected_public) => {
    const expected = new Set(expected_public);
    const actual_public = surface.routes
        .filter((r) => is_public_auth(r.auth))
        .map((r) => `${r.method} ${r.path}`);
    const unexpected = actual_public.filter((r) => !expected.has(r));
    const missing = expected_public.filter((r) => !actual_public.includes(r));
    assert.strictEqual(unexpected.length, 0, `Unexpected public routes: ${unexpected.join(', ')}`);
    assert.strictEqual(missing.length, 0, `Expected public routes missing: ${missing.join(', ')}`);
};
/**
 * Look up the merged error schema for a route+status from a pre-built schema lookup.
 *
 * @param lookup - map from `"METHOD /path"` to merged error schemas
 * @param route - the surface route to look up
 * @param status - HTTP status code
 * @returns the Zod schema for that route+status, or `undefined` when no error
 *   schema is declared for the status code
 */
export const get_route_error_schema = (lookup, route, status) => {
    const key = `${route.method} ${route.path}`;
    return lookup.get(key)?.[status];
};
/**
 * Assert that an error schema exists for a route+status and validate the body against it.
 *
 * Protected routes should always have auto-derived error schemas (401 for authenticated,
 * 403 for role-restricted). A missing schema indicates a gap in error schema derivation.
 *
 * @param lookup - map from `"METHOD /path"` to merged error schemas
 * @param route - the surface route to validate against
 * @param status - expected HTTP status code
 * @param body - the parsed response body to validate
 * @throws AssertionError if no schema is declared for the route+status pair.
 * @throws ZodError if the body does not satisfy the declared schema.
 */
export const assert_error_schema_valid = (lookup, route, status, body) => {
    const schema = get_route_error_schema(lookup, route, status);
    assert.ok(schema, `missing error schema for ${status} on ${route.method} ${route.path}`);
    schema.parse(body);
};
/**
 * Verify every route under a path prefix has the exact expected middleware stack.
 *
 * @param surface - the app surface to check
 * @param path_prefix - prefix to filter routes (e.g. `'/api/'`)
 * @param expected_middleware - the exact middleware names in order
 * @throws AssertionError if no routes match `path_prefix`, or if any matching
 *   route's `applicable_middleware` does not deep-equal `expected_middleware`.
 */
export const assert_full_middleware_stack = (surface, path_prefix, expected_middleware) => {
    const routes = surface.routes.filter((r) => r.path.startsWith(path_prefix));
    assert.ok(routes.length > 0, `No routes found under ${path_prefix}`);
    for (const route of routes) {
        assert.deepStrictEqual(route.applicable_middleware, expected_middleware, `${route.method} ${route.path} has wrong middleware stack`);
    }
};
