import { SystemRole } from "@capakraken/shared"; import { describe, expect, it, vi } from "vitest"; import { entitlementRouter } from "../router/entitlement.js"; import { createCallerFactory } from "../trpc.js"; vi.mock("@capakraken/db", () => ({ VacationType: { ANNUAL: "ANNUAL", SICK: "SICK", OTHER: "OTHER", PUBLIC_HOLIDAY: "PUBLIC_HOLIDAY" }, VacationStatus: { APPROVED: "APPROVED", PENDING: "PENDING", REJECTED: "REJECTED" }, })); const createCaller = createCallerFactory(entitlementRouter); function createContext( db: Record, options: { role?: SystemRole; session?: boolean; } = {}, ) { const { role = SystemRole.USER, session = true } = options; return { session: session ? { user: { email: "user@example.com", name: "User", image: null }, expires: "2099-01-01T00:00:00.000Z", } : null, db: db as never, dbUser: session ? { id: role === SystemRole.ADMIN ? "user_admin" : role === SystemRole.MANAGER ? "user_mgr" : role === SystemRole.CONTROLLER ? "user_ctrl" : "user_1", systemRole: role, permissionOverrides: null, } : null, }; } function sampleEntitlement(overrides: Record = {}) { return { id: "ent_1", resourceId: "res_2", year: 2026, entitledDays: 30, carryoverDays: 2, usedDays: 5, pendingDays: 3, createdAt: new Date("2026-01-01T00:00:00.000Z"), updatedAt: new Date("2026-01-02T00:00:00.000Z"), ...overrides, }; } describe("entitlement router authorization", () => { it("requires authentication for vacation balance reads", async () => { const findUnique = vi.fn(); const caller = createCaller(createContext({ resource: { findUnique, }, }, { session: false })); await expect(caller.getBalance({ resourceId: "res_1", year: 2026 })).rejects.toMatchObject({ code: "UNAUTHORIZED", message: "Authentication required", }); expect(findUnique).not.toHaveBeenCalled(); }); it("forbids regular users from reading another resource's balance", async () => { const systemSettingsFindUnique = vi.fn(); const vacationEntitlementFindUnique = vi.fn(); const caller = createCaller(createContext({ resource: { findUnique: vi.fn().mockResolvedValue({ userId: "user_2" }), }, systemSettings: { findUnique: systemSettingsFindUnique, }, vacationEntitlement: { findUnique: vacationEntitlementFindUnique, }, })); await expect(caller.getBalance({ resourceId: "res_2", year: 2026 })).rejects.toMatchObject({ code: "FORBIDDEN", message: "You can only view your own vacation balance", }); expect(systemSettingsFindUnique).not.toHaveBeenCalled(); expect(vacationEntitlementFindUnique).not.toHaveBeenCalled(); }); it("allows controllers to read broader balance detail without self-service ownership", async () => { const entitlement = sampleEntitlement({ pendingDays: 0 }); const resourceFindUnique = vi.fn().mockImplementation(async ({ select }: { select?: Record } = {}) => { if (select?.userId) { throw new Error("ownership check should not run for controller reads"); } if (select?.displayName || select?.eid) { return { ...(select?.displayName ? { displayName: "Alice Example" } : {}), ...(select?.eid ? { eid: "EMP-002" } : {}), }; } return { federalState: "BY", country: { code: "DE" }, metroCity: null, }; }); const caller = createCaller(createContext({ resource: { findUnique: resourceFindUnique, }, systemSettings: { findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }), }, vacationEntitlement: { findUnique: vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { year: number } } }) => ( where.resourceId_year.year === 2026 ? entitlement : null )), update: vi.fn().mockResolvedValue(entitlement), }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, }, { role: SystemRole.CONTROLLER })); const result = await caller.getBalanceDetail({ resourceId: "res_2", year: 2026 }); expect(result).toEqual({ resource: "Alice Example", eid: "EMP-002", year: 2026, entitlement: 30, carryOver: 2, taken: 5, pending: 0, remaining: 25, sickDays: 0, }); expect(resourceFindUnique).toHaveBeenCalledWith({ where: { id: "res_2" }, select: { displayName: true, eid: true }, }); }); it("forbids regular users from reading entitlement year summaries", async () => { const findMany = vi.fn(); const caller = createCaller(createContext({ resource: { findMany, }, })); await expect(caller.getYearSummary({ year: 2026 })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Manager or Admin role required", }); expect(findMany).not.toHaveBeenCalled(); }); it("forbids managers from bulk-setting entitlements", async () => { const findMany = vi.fn(); const caller = createCaller(createContext({ resource: { findMany, }, vacationEntitlement: { upsert: vi.fn(), }, }, { role: SystemRole.MANAGER })); await expect(caller.bulkSet({ year: 2026, entitledDays: 30 })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Admin role required", }); expect(findMany).not.toHaveBeenCalled(); }); });