import { OrderType, PermissionKey, SystemRole } from "@capakraken/shared"; import { describe, expect, it, vi } from "vitest"; import { scenarioRouter } from "../router/scenario.js"; import { createCallerFactory } from "../trpc.js"; vi.mock("../lib/resource-capacity.js", () => ({ calculateEffectiveAvailableHours: vi.fn(), calculateEffectiveBookedHours: vi.fn(), loadResourceDailyAvailabilityContexts: vi.fn().mockResolvedValue(new Map()), })); const createCaller = createCallerFactory(scenarioRouter); function createAuthenticatedCaller(db: Record) { return createCaller({ session: { user: { email: "user@example.com", name: "User", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_1", systemRole: SystemRole.USER, permissionOverrides: null, }, }); } function createProtectedCallerWithOverrides( db: Record, overrides: { granted?: PermissionKey[]; denied?: PermissionKey[] } | null, ) { return createCaller({ session: { user: { email: "user@example.com", name: "User", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_1", systemRole: SystemRole.USER, permissionOverrides: overrides, }, }); } describe("scenario router authorization", () => { it("requires planning read access for getProjectBaseline", async () => { const caller = createAuthenticatedCaller({}); await expect(caller.getProjectBaseline({ projectId: "project_1", })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); }); it("does not treat viewCosts as a substitute for viewPlanning on getProjectBaseline", async () => { const caller = createProtectedCallerWithOverrides({}, { granted: [PermissionKey.VIEW_COSTS], }); await expect(caller.getProjectBaseline({ projectId: "project_1", })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); }); it("requires viewCosts for getProjectBaseline", async () => { const caller = createProtectedCallerWithOverrides({}, { granted: [PermissionKey.VIEW_PLANNING], }); await expect(caller.getProjectBaseline({ projectId: "project_1", })).rejects.toMatchObject({ code: "FORBIDDEN", message: `Permission required: ${PermissionKey.VIEW_COSTS}`, }); }); it("allows getProjectBaseline with both planning and cost permissions", async () => { const project = { id: "project_1", name: "Project One", shortCode: "PRJ-001", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-30T00:00:00.000Z"), budgetCents: 125_000_00, orderType: OrderType.CHARGEABLE, }; const db = { project: { findUnique: vi.fn().mockResolvedValue(project), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }; const caller = createProtectedCallerWithOverrides(db, { granted: [PermissionKey.VIEW_PLANNING, PermissionKey.VIEW_COSTS], }); const result = await caller.getProjectBaseline({ projectId: "project_1", }); expect(db.project.findUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, select: { id: true, name: true, shortCode: true, startDate: true, endDate: true, budgetCents: true, orderType: true, }, }); expect(result).toEqual({ project, assignments: [], demands: [], totalCostCents: 0, totalHours: 0, budgetCents: project.budgetCents, }); }); });