import { PermissionKey, SystemRole } from "@capakraken/shared"; import { describe, expect, it, vi } from "vitest"; import { clientRouter } from "../router/client.js"; import { countryRouter } from "../router/country.js"; import { managementLevelRouter } from "../router/management-level.js"; import { orgUnitRouter } from "../router/org-unit.js"; import { utilizationCategoryRouter } from "../router/utilization-category.js"; import { createCallerFactory } from "../trpc.js"; function createProtectedContext( db: Record, overrides: { granted?: PermissionKey[]; denied?: PermissionKey[] } | null = null, ) { return { 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("master-data router authorization", () => { it("keeps country lists available to authenticated users as safe lookup data", async () => { const findMany = vi.fn().mockResolvedValue([ { id: "country_de", code: "DE", name: "Germany", isActive: true, dailyWorkingHours: 8, metroCities: [{ id: "city_ber", name: "Berlin", countryId: "country_de" }], }, ]); const caller = createCallerFactory(countryRouter)(createProtectedContext({ country: { findMany, }, })); const result = await caller.list({ isActive: true }); expect(result).toHaveLength(1); expect(findMany).toHaveBeenCalledWith(expect.objectContaining({ where: { isActive: true }, include: { metroCities: { orderBy: { name: "asc" } } }, orderBy: { name: "asc" }, })); }); it("keeps minimal country lookups available to authenticated users", async () => { const findFirst = vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany", isActive: true, dailyWorkingHours: 8, }); const caller = createCallerFactory(countryRouter)(createProtectedContext({ country: { findUnique: vi.fn().mockResolvedValue(null), findFirst, }, })); const result = await caller.resolveByIdentifier({ identifier: "de" }); expect(result).toEqual({ id: "country_de", code: "DE", name: "Germany", isActive: true, dailyWorkingHours: 8, }); expect(findFirst).toHaveBeenCalled(); }); it("keeps metro-city lookups available to authenticated users", async () => { const findUnique = vi.fn().mockResolvedValue({ id: "city_muc", name: "Munich", countryId: "country_de", }); const caller = createCallerFactory(countryRouter)(createProtectedContext({ metroCity: { findUnique, }, })); const result = await caller.getCityById({ id: "city_muc" }); expect(result).toEqual({ id: "city_muc", name: "Munich", countryId: "country_de", }); expect(findUnique).toHaveBeenCalledWith({ where: { id: "city_muc" }, select: { id: true, name: true, countryId: true }, }); }); it("requires resource overview access for detailed country reads with resource counts", async () => { const caller = createCallerFactory(countryRouter)(createProtectedContext({ country: { findFirst: vi.fn(), findUnique: vi.fn(), }, })); await expect(caller.getByIdentifier({ identifier: "DE" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); await expect(caller.getById({ id: "country_de" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); }); it("allows detailed country reads for users with resource overview access", async () => { const findFirst = vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany", isActive: true, dailyWorkingHours: 8, scheduleRules: null, metroCities: [{ id: "city_muc", name: "Munich", countryId: "country_de" }], _count: { resources: 12 }, }); const caller = createCallerFactory(countryRouter)(createProtectedContext({ country: { findUnique: vi.fn().mockResolvedValue(null), findFirst, }, }, { granted: [PermissionKey.VIEW_ALL_RESOURCES], })); const result = await caller.getByIdentifier({ identifier: "DE" }); expect(result._count.resources).toBe(12); expect(findFirst).toHaveBeenCalledWith(expect.objectContaining({ include: expect.objectContaining({ metroCities: expect.any(Object), _count: expect.any(Object), }), })); }); it("allows detailed country reads for users with manage-resources access", async () => { const findUnique = vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany", isActive: true, dailyWorkingHours: 8, scheduleRules: null, metroCities: [], _count: { resources: 4 }, }); const caller = createCallerFactory(countryRouter)(createProtectedContext({ country: { findUnique, }, }, { granted: [PermissionKey.MANAGE_RESOURCES], })); const result = await caller.getById({ id: "country_de" }); expect(result._count.resources).toBe(4); expect(findUnique).toHaveBeenCalled(); }); it("keeps minimal org-unit lookups available to authenticated users", async () => { const findFirst = vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, isActive: true, }); const caller = createCallerFactory(orgUnitRouter)(createProtectedContext({ orgUnit: { findUnique: vi.fn().mockResolvedValue(null), findFirst, }, })); const result = await caller.resolveByIdentifier({ identifier: "DEL" }); expect(result).toEqual({ id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, isActive: true, }); }); it("requires resource overview access for org-unit list and tree reads", async () => { const findMany = vi.fn(); const caller = createCallerFactory(orgUnitRouter)(createProtectedContext({ orgUnit: { findMany, }, })); await expect(caller.list({ level: 5, isActive: true })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); await expect(caller.getTree({ isActive: true })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); expect(findMany).not.toHaveBeenCalled(); }); it("requires resource overview access for detailed org-unit reads with staffing counts", async () => { const caller = createCallerFactory(orgUnitRouter)(createProtectedContext({ orgUnit: { findFirst: vi.fn(), findUnique: vi.fn(), }, })); await expect(caller.getByIdentifier({ identifier: "Delivery" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); await expect(caller.getById({ id: "ou_1" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); }); it("allows detailed org-unit reads for users with resource overview access", async () => { const findFirst = vi.fn().mockResolvedValue({ id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 10, isActive: true, _count: { resources: 7 }, }); const caller = createCallerFactory(orgUnitRouter)(createProtectedContext({ orgUnit: { findUnique: vi.fn().mockResolvedValue(null), findFirst, }, }, { granted: [PermissionKey.VIEW_ALL_RESOURCES], })); const result = await caller.getByIdentifier({ identifier: "Delivery" }); expect(result._count.resources).toBe(7); expect(findFirst).toHaveBeenCalledWith(expect.objectContaining({ include: { _count: { select: { resources: true } } }, })); }); it("allows org-unit list and tree reads for users with resource overview access", async () => { const listFindMany = vi.fn().mockResolvedValue([ { id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 10, isActive: true, createdAt: new Date("2026-03-01T00:00:00.000Z"), updatedAt: new Date("2026-03-01T00:00:00.000Z"), }, ]); const treeFindMany = vi.fn().mockResolvedValue([ { id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 10, isActive: true, createdAt: new Date("2026-03-01T00:00:00.000Z"), updatedAt: new Date("2026-03-01T00:00:00.000Z"), }, ]); const caller = createCallerFactory(orgUnitRouter)(createProtectedContext({ orgUnit: { findMany: vi.fn() .mockImplementationOnce(listFindMany) .mockImplementationOnce(treeFindMany), }, }, { granted: [PermissionKey.MANAGE_RESOURCES], })); const listResult = await caller.list({ level: 5, isActive: true }); const treeResult = await caller.getTree({ isActive: true }); expect(listResult).toHaveLength(1); expect(treeResult).toEqual([ expect.objectContaining({ id: "ou_1", name: "Delivery", children: [], }), ]); expect(listFindMany).toHaveBeenCalledWith({ where: { level: 5, isActive: true }, orderBy: [{ level: "asc" }, { sortOrder: "asc" }, { name: "asc" }], }); expect(treeFindMany).toHaveBeenCalledWith({ where: { isActive: true }, orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }); }); it("keeps minimal client lookups available to authenticated users", async () => { const findUnique = vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "client_1", name: "Acme Studios", code: "ACME", parentId: null, isActive: true, }); const caller = createCallerFactory(clientRouter)(createProtectedContext({ client: { findUnique, findFirst: vi.fn(), }, })); const result = await caller.resolveByIdentifier({ identifier: "ACME" }); expect(result).toEqual({ id: "client_1", name: "Acme Studios", code: "ACME", parentId: null, isActive: true, }); }); it("requires planning read access for client list and tree reads", async () => { const findMany = vi.fn(); const caller = createCallerFactory(clientRouter)(createProtectedContext({ client: { findMany, }, })); await expect(caller.list({ isActive: true })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); await expect(caller.getTree({ isActive: true })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); expect(findMany).not.toHaveBeenCalled(); }); it("requires planning read access for detailed client reads with project counts", async () => { const caller = createCallerFactory(clientRouter)(createProtectedContext({ client: { findFirst: vi.fn(), findUnique: vi.fn(), }, })); await expect(caller.getByIdentifier({ identifier: "Acme" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); await expect(caller.getById({ id: "client_1" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); }); it("allows client list, tree, and detail reads for users with planning access", async () => { const listFindMany = vi.fn().mockResolvedValue([ { id: "client_1", name: "Acme Studios", code: "ACME", parentId: null, isActive: true, sortOrder: 10, tags: ["enterprise"], createdAt: new Date("2026-03-01T00:00:00.000Z"), updatedAt: new Date("2026-03-01T00:00:00.000Z"), _count: { children: 1, projects: 4 }, }, ]); const treeFindMany = vi.fn().mockResolvedValue([ { id: "client_1", name: "Acme Studios", code: "ACME", parentId: null, isActive: true, sortOrder: 10, tags: ["enterprise"], createdAt: new Date("2026-03-01T00:00:00.000Z"), updatedAt: new Date("2026-03-01T00:00:00.000Z"), }, ]); const getByIdFindUnique = vi.fn() .mockResolvedValueOnce({ id: "client_1", name: "Acme Studios", code: "ACME", parentId: null, isActive: true, sortOrder: 10, tags: ["enterprise"], createdAt: new Date("2026-03-01T00:00:00.000Z"), updatedAt: new Date("2026-03-01T00:00:00.000Z"), parent: null, children: [], _count: { children: 1, projects: 4 }, }) .mockResolvedValueOnce(null); const getByIdentifierFindFirst = vi.fn().mockResolvedValue({ id: "client_1", name: "Acme Studios", code: "ACME", parentId: null, isActive: true, sortOrder: 10, tags: ["enterprise"], createdAt: new Date("2026-03-01T00:00:00.000Z"), updatedAt: new Date("2026-03-01T00:00:00.000Z"), _count: { children: 1, projects: 4 }, }); const caller = createCallerFactory(clientRouter)(createProtectedContext({ client: { findMany: vi.fn() .mockImplementationOnce(listFindMany) .mockImplementationOnce(treeFindMany), findUnique: getByIdFindUnique, findFirst: getByIdentifierFindFirst, }, }, { granted: [PermissionKey.VIEW_PLANNING], })); const listResult = await caller.list({ isActive: true, search: "Acme" }); const treeResult = await caller.getTree({ isActive: true }); const byIdResult = await caller.getById({ id: "client_1" }); const byIdentifierResult = await caller.getByIdentifier({ identifier: "Acme Studios" }); expect(listResult).toHaveLength(1); expect(treeResult).toEqual([ expect.objectContaining({ id: "client_1", name: "Acme Studios", children: [], }), ]); expect(byIdResult._count.projects).toBe(4); expect(byIdentifierResult._count.projects).toBe(4); expect(listFindMany).toHaveBeenCalledWith({ where: { isActive: true, OR: [ { name: { contains: "Acme", mode: "insensitive" } }, { code: { contains: "Acme", mode: "insensitive" } }, ], }, include: { _count: { select: { children: true, projects: true } } }, orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }); expect(treeFindMany).toHaveBeenCalledWith({ where: { isActive: true }, orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }); expect(getByIdFindUnique).toHaveBeenCalledWith({ where: { id: "client_1" }, include: { parent: true, children: { orderBy: { sortOrder: "asc" } }, _count: { select: { projects: true, children: true } }, }, }); expect(getByIdentifierFindFirst).toHaveBeenCalledWith(expect.objectContaining({ where: { name: { equals: "Acme Studios", mode: "insensitive" } }, include: { _count: { select: { projects: true, children: true } } }, })); }); it("requires planning read access for utilization-category reads with project counts", async () => { const listFindMany = vi.fn(); const getByIdFindUnique = vi.fn(); const caller = createCallerFactory(utilizationCategoryRouter)(createProtectedContext({ utilizationCategory: { findMany: listFindMany, findUnique: getByIdFindUnique, }, })); await expect(caller.list({ isActive: true })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); await expect(caller.getById({ id: "util_chargeable" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); expect(listFindMany).not.toHaveBeenCalled(); expect(getByIdFindUnique).not.toHaveBeenCalled(); }); it("allows utilization-category reads for users with planning access", async () => { const listFindMany = vi.fn().mockResolvedValue([ { id: "util_chargeable", code: "CHARGEABLE", name: "Chargeable", description: "Revenue-generating project work", sortOrder: 1, isDefault: true, isActive: true, }, ]); const getByIdFindUnique = vi.fn().mockResolvedValue({ id: "util_chargeable", code: "CHARGEABLE", name: "Chargeable", description: "Revenue-generating project work", sortOrder: 1, isDefault: true, isActive: true, _count: { projects: 12 }, }); const caller = createCallerFactory(utilizationCategoryRouter)(createProtectedContext({ utilizationCategory: { findMany: listFindMany, findUnique: getByIdFindUnique, }, }, { granted: [PermissionKey.VIEW_PLANNING], })); const listResult = await caller.list({ isActive: true }); const byIdResult = await caller.getById({ id: "util_chargeable" }); expect(listResult).toHaveLength(1); expect(byIdResult._count.projects).toBe(12); expect(listFindMany).toHaveBeenCalledWith({ where: { isActive: true }, orderBy: { sortOrder: "asc" }, }); expect(getByIdFindUnique).toHaveBeenCalledWith({ where: { id: "util_chargeable" }, include: { _count: { select: { projects: true } } }, }); }); it("requires planning read access for management-level reads", async () => { const listFindMany = vi.fn(); const getByIdFindUnique = vi.fn(); const caller = createCallerFactory(managementLevelRouter)(createProtectedContext({ managementLevelGroup: { findMany: listFindMany, findUnique: getByIdFindUnique, }, })); await expect(caller.listGroups()).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); await expect(caller.getGroupById({ id: "mgmt_group_1" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Planning read access required", }); expect(listFindMany).not.toHaveBeenCalled(); expect(getByIdFindUnique).not.toHaveBeenCalled(); }); it("allows management-level reads for users with planning access", async () => { const listFindMany = vi.fn().mockResolvedValue([ { id: "mgmt_group_1", name: "Team Leads", targetPercentage: 0.72, sortOrder: 10, levels: [{ id: "mgmt_level_1", name: "Senior Team Lead" }], }, ]); const getByIdFindUnique = vi.fn().mockResolvedValue({ id: "mgmt_group_1", name: "Team Leads", targetPercentage: 0.72, sortOrder: 10, levels: [{ id: "mgmt_level_1", name: "Senior Team Lead" }], _count: { resources: 6 }, }); const caller = createCallerFactory(managementLevelRouter)(createProtectedContext({ managementLevelGroup: { findMany: listFindMany, findUnique: getByIdFindUnique, }, }, { granted: [PermissionKey.VIEW_PLANNING], })); const listResult = await caller.listGroups(); const detailResult = await caller.getGroupById({ id: "mgmt_group_1" }); expect(listResult).toHaveLength(1); expect(detailResult._count.resources).toBe(6); expect(listFindMany).toHaveBeenCalledWith({ include: { levels: { orderBy: { name: "asc" } } }, orderBy: { sortOrder: "asc" }, }); expect(getByIdFindUnique).toHaveBeenCalledWith({ where: { id: "mgmt_group_1" }, include: { levels: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }); }); });