62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
NAVIGATION_ROUTES,
|
|
ROUTE_REGISTRY,
|
|
} from "../../src/features/installed-feature-contracts.ts";
|
|
import type { RouteDefinition } from "../../src/contracts/routes.ts";
|
|
import {
|
|
createRedirectLoopGuard,
|
|
decideRouteAccess,
|
|
} from "../../src/presentation/routes/navigation-policy.ts";
|
|
|
|
describe("installed route registry", () => {
|
|
it("derives visible navigation in explicit order", () => {
|
|
const expected = Object.values(ROUTE_REGISTRY)
|
|
.filter((route) => route.navigationOrder !== null)
|
|
.sort(
|
|
(left, right) =>
|
|
left.navigationOrder! - right.navigationOrder!,
|
|
)
|
|
.map((route) => route.routeId);
|
|
expect(NAVIGATION_ROUTES.map(({ routeId }) => routeId)).toEqual(expected);
|
|
});
|
|
|
|
it("allows public routes without consulting a product permission", () => {
|
|
expect(decideRouteAccess("TECH_LOG_HOME", "unauthenticated")).toEqual({
|
|
allowed: true,
|
|
action: "none",
|
|
});
|
|
});
|
|
|
|
it("treats every non-public route as explicitly session-required", () => {
|
|
const protectedRoutes = Object.values(
|
|
ROUTE_REGISTRY as Readonly<Record<string, RouteDefinition>>,
|
|
).filter((route) => route.access !== "public");
|
|
|
|
expect(
|
|
protectedRoutes.every((route) => route.access === "session-required"),
|
|
).toBe(true);
|
|
for (const route of protectedRoutes) {
|
|
expect(route.access).toBe("session-required");
|
|
expect(decideRouteAccess(route.routeId, "unauthenticated")).toEqual({
|
|
allowed: false,
|
|
action: "show-sign-in",
|
|
});
|
|
}
|
|
});
|
|
|
|
it("bounds automatic redirects by pair and maximum hops", () => {
|
|
const guard = createRedirectLoopGuard(2);
|
|
expect(guard.allow("/private", "/signin")).toBe(true);
|
|
expect(guard.allow("/private", "/signin")).toBe(false);
|
|
expect(guard.allow("/signin", "/signin")).toBe(false);
|
|
expect(guard.allow("/signin", "/continue")).toBe(true);
|
|
expect(guard.allow("/continue", "/final")).toBe(false);
|
|
expect(guard.hopCount).toBe(2);
|
|
|
|
guard.reset();
|
|
expect(guard.allow("/unrelated", "/canonical-unrelated")).toBe(true);
|
|
});
|
|
});
|