import { PermissionKey, SystemRole } from "@capakraken/shared"; import { ResourceType } from "@capakraken/shared"; import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@capakraken/application", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, isChargeabilityActualBooking: actual.isChargeabilityActualBooking, isChargeabilityRelevantProject: actual.isChargeabilityRelevantProject, listAssignmentBookings: vi.fn(), recomputeResourceValueScores: vi.fn(), }; }); import { listAssignmentBookings } from "@capakraken/application"; import { resourceRouter } from "../router/resource.js"; import { createCallerFactory } from "../trpc.js"; const createCaller = createCallerFactory(resourceRouter); function createControllerCaller(db: Record) { return createCaller({ session: { user: { email: "controller@example.com", name: "Controller", image: null, role: "CONTROLLER" }, expires: "2026-03-14T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_controller", systemRole: SystemRole.CONTROLLER, permissionOverrides: null, }, }); } function createProtectedCaller(db: Record) { return createCaller({ session: { user: { email: "user@example.com", name: "User", image: null, role: "USER" }, expires: "2026-03-14T00: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, role: "USER" }, expires: "2026-03-14T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_1", systemRole: SystemRole.USER, permissionOverrides: overrides, }, }); } describe("resource router", () => { beforeEach(() => { vi.clearAllMocks(); }); it("filters proposed utilization rows unless explicitly requested", async () => { const resource = { id: "resource_1", eid: "E-001", displayName: "Alice", email: "alice@example.com", chapter: "CGI", lcrCents: 5000, ucrCents: 9000, currency: "EUR", chargeabilityTarget: 80, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, skills: [], dynamicFields: {}, blueprintId: null, isActive: true, createdAt: new Date("2026-03-01"), updatedAt: new Date("2026-03-01"), roleId: null, portfolioUrl: null, postalCode: null, federalState: null, valueScore: null, valueScoreBreakdown: null, valueScoreUpdatedAt: null, userId: null, countryId: "country_de", metroCityId: null, country: { code: "DE" }, metroCity: null, }; const db = { resource: { findMany: vi.fn().mockResolvedValue([resource]), }, }; vi.mocked(listAssignmentBookings).mockResolvedValue([ { id: "assignment_confirmed", projectId: "project_1", resourceId: "resource_1", startDate: new Date("2026-03-02T00:00:00.000Z"), endDate: new Date("2026-03-06T00:00:00.000Z"), hoursPerDay: 4, dailyCostCents: 0, status: "CONFIRMED", project: { id: "project_1", name: "Project 1", shortCode: "P1", status: "ACTIVE", orderType: "CLIENT", dynamicFields: null, }, resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" }, }, { id: "assignment_proposed", projectId: "project_2", resourceId: "resource_1", startDate: new Date("2026-03-02T00:00:00.000Z"), endDate: new Date("2026-03-06T00:00:00.000Z"), hoursPerDay: 4, dailyCostCents: 0, status: "PROPOSED", project: { id: "project_2", name: "Project 2", shortCode: "P2", status: "ACTIVE", orderType: "CLIENT", dynamicFields: null, }, resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" }, }, ]); const caller = createControllerCaller(db); const strict = await caller.listWithUtilization({ startDate: "2026-03-02T00:00:00.000Z", endDate: "2026-03-08T00:00:00.000Z", }); const withProposed = await caller.listWithUtilization({ startDate: "2026-03-02T00:00:00.000Z", endDate: "2026-03-08T00:00:00.000Z", includeProposed: true, }); expect(strict[0]).toMatchObject({ bookingCount: 1, bookedHours: 20, utilizationPercent: 50, }); expect(withProposed[0]).toMatchObject({ bookingCount: 2, bookedHours: 40, utilizationPercent: 100, }); }); it("calculates utilization with regional holidays removed from available hours", async () => { const resource = { id: "resource_1", eid: "E-001", displayName: "Alice", email: "alice@example.com", chapter: "CGI", lcrCents: 5000, ucrCents: 9000, currency: "EUR", chargeabilityTarget: 80, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }, skills: [], dynamicFields: {}, blueprintId: null, isActive: true, createdAt: new Date("2026-03-01"), updatedAt: new Date("2026-03-01"), roleId: null, portfolioUrl: null, postalCode: null, federalState: "BY", countryId: "country_de", metroCityId: null, valueScore: null, valueScoreBreakdown: null, valueScoreUpdatedAt: null, userId: null, country: { code: "DE" }, metroCity: null, }; const db = { resource: { findMany: vi.fn().mockResolvedValue([resource]), }, }; vi.mocked(listAssignmentBookings).mockResolvedValue([ { id: "assignment_confirmed", projectId: "project_1", resourceId: "resource_1", startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-06T00:00:00.000Z"), hoursPerDay: 8, dailyCostCents: 0, status: "CONFIRMED", project: { id: "project_1", name: "Project 1", shortCode: "P1", status: "ACTIVE", orderType: "CLIENT", dynamicFields: null, }, resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" }, }, ]); const caller = createControllerCaller(db); const result = await caller.listWithUtilization({ startDate: "2026-01-05T00:00:00.000Z", endDate: "2026-01-06T00:00:00.000Z", }); expect(result[0]).toMatchObject({ bookingCount: 1, bookedHours: 8, availableHours: 8, utilizationPercent: 100, }); }); it("shifts marketplace availability when a local holiday blocks today", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-01-06T10:00:00.000Z")); try { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "resource_by", displayName: "Bavaria Artist", eid: "E-BY", chapter: "CGI", skills: [{ skill: "Houdini", proficiency: 5 }], availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }, chargeabilityTarget: 80, countryId: "country_de", federalState: "BY", metroCityId: null, country: { code: "DE" }, metroCity: null, }, { id: "resource_hh", displayName: "Hamburg Artist", eid: "E-HH", chapter: "CGI", skills: [{ skill: "Houdini", proficiency: 5 }], availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }, chargeabilityTarget: 80, countryId: "country_de", federalState: "HH", metroCityId: null, country: { code: "DE" }, metroCity: null, }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }; const caller = createControllerCaller(db); const result = await caller.getSkillMarketplace({ searchSkill: "houdini", availableOnly: true, }); const bavaria = result.searchResults.find((resource) => resource.id === "resource_by"); const hamburg = result.searchResults.find((resource) => resource.id === "resource_hh"); expect(bavaria?.availableFrom).toBe("2026-01-07T00:00:00.000Z"); expect(hamburg?.availableFrom).toBe("2026-01-06T00:00:00.000Z"); } finally { vi.useRealTimers(); } }); it("uses a composite displayName/id cursor for stable pagination", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "2", displayName: "Alex", eid: "E-002", email: "alex2@example.com" }, { id: "3", displayName: "Bea", eid: "E-003", email: "bea@example.com" }, ]), count: vi.fn().mockResolvedValue(3), }, }; const caller = createControllerCaller(db); const result = await caller.listStaff({ limit: 1, cursor: JSON.stringify({ displayName: "Alex", id: "1" }), }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ AND: expect.arrayContaining([ { OR: [ { displayName: { gt: "Alex" } }, { displayName: "Alex", id: { gt: "1" } }, ], }, ]), }), orderBy: [{ displayName: "asc" }, { id: "asc" }], take: 2, }), ); expect(result.nextCursor).toBe(JSON.stringify({ displayName: "Alex", id: "2" })); }); it("resolves resource ownership server-side without exposing linked user email", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "resource_1" }), findUnique: vi.fn().mockResolvedValue({ id: "resource_1", eid: "E-001", displayName: "Alice", email: "alice@example.com", chapter: "CGI", lcrCents: 5000, ucrCents: 9000, currency: "EUR", chargeabilityTarget: 80, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, skills: [], dynamicFields: {}, blueprint: null, blueprintId: null, isActive: true, createdAt: new Date("2026-03-01"), updatedAt: new Date("2026-03-01"), resourceRoles: [], areaRole: null, portfolioUrl: null, roleId: null, aiSummary: null, aiSummaryUpdatedAt: null, skillMatrixUpdatedAt: null, valueScore: null, valueScoreBreakdown: null, valueScoreUpdatedAt: null, userId: "user_1", }), findMany: vi.fn().mockResolvedValue([]), }, systemSettings: { findUnique: vi.fn().mockResolvedValue(null), }, }; const caller = createProtectedCaller(db); const result = await caller.getById({ id: "resource_1" }); expect(result).toMatchObject({ id: "resource_1", displayName: "Alice", eid: "E-001", email: "alice@example.com", isOwnedByCurrentUser: true, }); expect(result).not.toHaveProperty("user"); expect(db.resource.findUnique).toHaveBeenCalledWith( expect.objectContaining({ include: expect.not.objectContaining({ user: expect.anything(), }), }), ); }); it("counts imported TBD draft projects in chargeability stats only when proposed work is enabled", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "resource_1", eid: "E-001", displayName: "Alice", chapter: "CGI", chargeabilityTarget: 80, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, }, ]), }, }; vi.mocked(listAssignmentBookings).mockResolvedValue([ { id: "assignment_tbd", projectId: "project_tbd", resourceId: "resource_1", startDate: new Date("2026-03-02T00:00:00.000Z"), endDate: new Date("2026-03-06T00:00:00.000Z"), hoursPerDay: 4, dailyCostCents: 0, status: "PROPOSED", project: { id: "project_tbd", name: "TBD Project", shortCode: "TBD-P1", status: "DRAFT", orderType: "CLIENT", dynamicFields: { dispoImport: { isTbd: true } }, }, resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" }, }, ]); const caller = createControllerCaller(db); const strict = await caller.getChargeabilityStats({}); const withProposed = await caller.getChargeabilityStats({ includeProposed: true }); expect(strict[0]?.actualChargeability).toBe(0); expect(strict[0]?.expectedChargeability).toBeGreaterThan(0); expect(withProposed[0]?.actualChargeability).toBeGreaterThan(strict[0]?.actualChargeability ?? 0); expect(withProposed[0]?.expectedChargeability).toBe(strict[0]?.expectedChargeability); }); it("excludes regional public holidays from chargeability stats", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "resource_by", eid: "E-BY", displayName: "Bavaria", chapter: "CGI", chargeabilityTarget: 80, countryId: "country_de", federalState: "BY", metroCityId: "city_munich", country: { code: "DE" }, metroCity: { name: "Munich" }, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, }, ]), }, }; vi.mocked(listAssignmentBookings).mockResolvedValue([ { id: "assignment_holiday", projectId: "project_1", resourceId: "resource_by", startDate: new Date("2026-01-06T00:00:00.000Z"), endDate: new Date("2026-01-06T00:00:00.000Z"), hoursPerDay: 8, dailyCostCents: 0, status: "CONFIRMED", project: { id: "project_1", name: "Project 1", shortCode: "P1", status: "ACTIVE", orderType: "CLIENT", dynamicFields: null, }, resource: { id: "resource_by", displayName: "Bavaria", chapter: "CGI" }, }, ]); const RealDate = Date; class MockDate extends Date { constructor(...args: ConstructorParameters) { if (args.length === 0) { super("2026-01-15T00:00:00.000Z"); return; } super(...args); } static now() { return new RealDate("2026-01-15T00:00:00.000Z").getTime(); } } vi.stubGlobal("Date", MockDate); try { const caller = createControllerCaller(db); const result = await caller.getChargeabilityStats({}); expect(result[0]).toMatchObject({ actualChargeability: 0, expectedChargeability: 0, availableHours: 168, }); } finally { vi.unstubAllGlobals(); } }); it("returns a holiday-aware chargeability summary readmodel", async () => { const resourceRecord = { id: "res_1", displayName: "Bruce Banner", eid: "bruce.banner", fte: 1, lcrCents: 5000, chargeabilityTarget: 80, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0 }, countryId: "country_de", federalState: "BY", metroCityId: null, country: { id: "country_de", code: "DE", name: "Deutschland", dailyWorkingHours: 8, scheduleRules: null }, metroCity: null, managementLevelGroup: null, }; const db = { resource: { findUniqueOrThrow: vi.fn().mockResolvedValue(resourceRecord), }, assignment: { findMany: vi.fn().mockResolvedValue([ { id: "assign_1", hoursPerDay: 8, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-06T00:00:00.000Z"), dailyCostCents: 40000, status: "CONFIRMED", project: { id: "project_gamma", name: "Gamma", shortCode: "GAM", orderType: "CLIENT", utilizationCategory: { code: "Chg" }, }, }, ]), }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, holidayCalendar: { findMany: vi.fn().mockResolvedValue([]), }, calculationRule: { findMany: vi.fn().mockResolvedValue([]), }, }; const caller = createControllerCaller(db); const result = await caller.getChargeabilitySummary({ resourceId: "res_1", month: "2026-01" }); expect(result.bookedHours).toBe(8); expect(result.allocations).toEqual([expect.objectContaining({ hours: 8, project: "Gamma", code: "GAM" })]); expect(result.baseWorkingDays).toBe(22); expect(result.baseAvailableHours).toBe(176); expect(result.availableHours).toBe(160); expect(result.workingDays).toBe(20); expect(result.targetHours).toBe(128); expect(result.unassignedHours).toBe(152); expect(result.locationContext.federalState).toBe("BY"); expect(result.holidaySummary).toEqual( expect.objectContaining({ count: 2, workdayCount: 2, hoursDeduction: 16, }), ); expect(result.capacityBreakdown).toEqual( expect.objectContaining({ formula: "baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours", holidayHoursDeduction: 16, absenceHoursDeduction: 0, }), ); }); it("allows regular users to read their own chargeability summary", async () => { const resourceRecord = { id: "res_own", displayName: "Bruce Banner", eid: "bruce.banner", fte: 1, lcrCents: 5000, chargeabilityTarget: 80, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0 }, countryId: "country_de", federalState: "BY", metroCityId: null, country: { id: "country_de", code: "DE", name: "Deutschland", dailyWorkingHours: 8, scheduleRules: null }, metroCity: null, managementLevelGroup: null, }; const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_own" }), findUniqueOrThrow: vi.fn().mockResolvedValue(resourceRecord), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, holidayCalendar: { findMany: vi.fn().mockResolvedValue([]), }, calculationRule: { findMany: vi.fn().mockResolvedValue([]), }, }; const caller = createProtectedCaller(db); const result = await caller.getChargeabilitySummary({ resourceId: "res_own", month: "2026-01" }); expect(result.resource).toBe("Bruce Banner"); expect(db.resource.findFirst).toHaveBeenCalledWith({ where: { userId: "user_1" }, select: { id: true }, }); }); it("rejects chargeability summary access for foreign resources", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_own" }), findUniqueOrThrow: vi.fn(), }, }; const caller = createProtectedCaller(db); await expect( caller.getChargeabilitySummary({ resourceId: "res_other", month: "2026-01" }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "You can only view chargeability details for your own resource unless you have staff access", }); expect(db.resource.findUniqueOrThrow).not.toHaveBeenCalled(); }); it("requires explicit score permission for value scores", async () => { const db = { resource: { findMany: vi.fn(), }, }; const caller = createProtectedCaller(db); await expect(caller.getValueScores({})).rejects.toMatchObject({ code: "FORBIDDEN", message: "Permission required: viewScores", }); expect(db.resource.findMany).not.toHaveBeenCalled(); }); it("returns value scores when the caller has explicit score permission", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", eid: "E-001", displayName: "Alice", chapter: "CGI", lcrCents: 5000, valueScore: 93, valueScoreBreakdown: { delivery: 50, scarcity: 43 }, valueScoreUpdatedAt: new Date("2026-03-01T00:00:00.000Z"), }, ]), }, }; const caller = createProtectedCallerWithOverrides(db, { granted: [PermissionKey.VIEW_SCORES] }); const result = await caller.getValueScores({ limit: 25 }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ id: "res_1", displayName: "Alice", valueScore: 93, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { isActive: true }, take: 25, }), ); }); it("rejects resource summary searches for regular users", async () => { const db = { resource: { findMany: vi.fn(), }, }; const caller = createProtectedCaller(db); await expect( caller.listSummaries({ search: "Alice", limit: 10 }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); expect(db.resource.findMany).not.toHaveBeenCalled(); }); it("allows resource summary searches with broad resource read permission", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", eid: "E-001", displayName: "Alice", chapter: "CGI", isActive: true, areaRole: { name: "Developer" }, country: { code: "DE", name: "Germany" }, metroCity: { name: "Munich" }, orgUnit: { name: "Studio A" }, }, ]), }, }; const caller = createProtectedCallerWithOverrides(db, { granted: [PermissionKey.VIEW_ALL_RESOURCES] }); const result = await caller.listSummaries({ search: "Ali", limit: 10 }); expect(result).toEqual([ { id: "res_1", eid: "E-001", name: "Alice", chapter: "CGI", role: "Developer", country: "Germany", countryCode: "DE", metroCity: "Munich", orgUnit: "Studio A", active: true, }, ]); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ OR: expect.arrayContaining([ { displayName: { contains: "Ali", mode: "insensitive" } }, { eid: { contains: "Ali", mode: "insensitive" } }, ]), }), select: { id: true, eid: true, displayName: true, chapter: true, isActive: true, areaRole: { select: { name: true } }, country: { select: { code: true, name: true } }, metroCity: { select: { name: true } }, orgUnit: { select: { name: true } }, }, take: 10, }), ); }); it("returns assistant-facing resource summary details from the canonical router", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", eid: "E-001", displayName: "Alice", chapter: "CGI", fte: 1, lcrCents: 5000, chargeabilityTarget: 80, isActive: true, areaRole: { name: "Developer" }, country: { code: "DE", name: "Germany" }, metroCity: { name: "Munich" }, orgUnit: { name: "Studio A" }, }, ]), }, }; const caller = createProtectedCallerWithOverrides(db, { granted: [PermissionKey.VIEW_ALL_RESOURCES] }); const result = await caller.listSummariesDetail({ search: "Ali", limit: 10 }); expect(result).toEqual([ { id: "res_1", eid: "E-001", name: "Alice", chapter: "CGI", role: "Developer", country: "Germany", countryCode: "DE", metroCity: "Munich", orgUnit: "Studio A", fte: 1, lcr: "50,00 EUR", chargeabilityTarget: "80%", active: true, }, ]); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ select: expect.objectContaining({ fte: true, lcrCents: true, chargeabilityTarget: true, }), }), ); }); it("returns only safe directory fields for generic resource lookups", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", eid: "E-001", displayName: "Alice", chapter: "CGI", isActive: true, }, ]), count: vi.fn().mockResolvedValue(1), }, }; const caller = createProtectedCaller(db); const result = await caller.directory({ limit: 10 }); expect(result.resources).toHaveLength(1); expect(result.resources[0]).toEqual({ id: "res_1", eid: "E-001", displayName: "Alice", chapter: "CGI", isActive: true, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ select: { id: true, eid: true, displayName: true, chapter: true, isActive: true, }, }), ); }); it("does not include email search in the safe resource directory", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createProtectedCaller(db); await caller.directory({ search: "alice@example.com", limit: 25 }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { OR: [ { displayName: { contains: "alice@example.com", mode: "insensitive" } }, { eid: { contains: "alice@example.com", mode: "insensitive" } }, ], }, ]), }, }), ); }); it("rejects staff resource lists for regular users", async () => { const db = { resource: { findMany: vi.fn(), count: vi.fn(), }, }; const caller = createProtectedCaller(db); await expect( caller.listStaff({ limit: 10 }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); expect(db.resource.findMany).not.toHaveBeenCalled(); expect(db.resource.count).not.toHaveBeenCalled(); }); it("includes email search in explicit staff resource lists", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ search: "alice@example.com", limit: 25 }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { OR: [ { displayName: { contains: "alice@example.com", mode: "insensitive" } }, { eid: { contains: "alice@example.com", mode: "insensitive" } }, { email: { contains: "alice@example.com", mode: "insensitive" } }, ], }, ]), }, }), ); }); it("returns sensitive fields and optional roles in explicit staff resource lists", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_other", eid: "E-OTHER", displayName: "Bob", email: "bob@example.com", chapter: "CGI", lcrCents: 7000, ucrCents: 12000, currency: "EUR", roleId: "role_artist", federalState: "BY", dynamicFields: { level: "senior" }, resourceRoles: [ { roleId: "role_artist", isPrimary: true, role: { id: "role_artist", name: "Artist", color: "#123456" }, }, ], }, ]), count: vi.fn().mockResolvedValue(1), }, }; const caller = createControllerCaller(db); const result = await caller.listStaff({ limit: 10, includeRoles: true }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ include: expect.objectContaining({ resourceRoles: expect.objectContaining({ include: expect.any(Object), }), }), }), ); expect(result.resources[0]).toMatchObject({ id: "res_other", email: "bob@example.com", lcrCents: 7000, ucrCents: 12000, currency: "EUR", roleId: "role_artist", federalState: "BY", dynamicFields: { level: "senior" }, resourceRoles: [ expect.objectContaining({ roleId: "role_artist", isPrimary: true, role: expect.objectContaining({ id: "role_artist", name: "Artist" }), }), ], }); }); it("allows exact self lookup via getByIdentifier without broad search", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(null), findFirst: vi.fn() .mockResolvedValueOnce({ id: "res_own", eid: "E-OWN", displayName: "Alice Example", chapter: "CGI", isActive: true, }) .mockResolvedValueOnce({ id: "res_own" }), findMany: vi.fn(), }, }; const caller = createProtectedCaller(db); const result = await caller.getByIdentifier({ identifier: "Alice Example" }); expect(result).toEqual({ id: "res_own", eid: "E-OWN", displayName: "Alice Example", chapter: "CGI", isActive: true, }); expect(db.resource.findMany).not.toHaveBeenCalled(); }); it("returns assistant-facing resource details from the canonical router", async () => { const db = { resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "res_own", eid: "E-OWN", displayName: "Alice Example", chapter: "CGI", isActive: true, }) .mockResolvedValueOnce({ id: "res_own", eid: "E-OWN", displayName: "Alice Example", email: "alice@example.com", chapter: "CGI", fte: 1, lcrCents: 5000, ucrCents: 9000, chargeabilityTarget: 80, isActive: true, availability: {}, skills: [{ name: "Houdini", level: 5 }], postalCode: "80331", federalState: "BY", areaRole: { name: "Developer", color: "#123456" }, country: { code: "DE", name: "Germany", dailyWorkingHours: 8 }, metroCity: { name: "Munich" }, managementLevelGroup: { name: "Senior", targetPercentage: 75 }, orgUnit: { name: "Studio A", level: 5 }, _count: { assignments: 4, vacations: 2 }, }), findFirst: vi.fn().mockResolvedValueOnce({ id: "res_own" }), }, }; const caller = createProtectedCaller(db); const result = await caller.getByIdentifierDetail({ identifier: "E-OWN" }); expect(result).toEqual({ id: "res_own", eid: "E-OWN", name: "Alice Example", email: "alice@example.com", chapter: "CGI", role: "Developer", country: "Germany", countryCode: "DE", countryHours: 8, metroCity: "Munich", fte: 1, lcr: "50,00 EUR", ucr: "90,00 EUR", chargeabilityTarget: "80%", managementLevel: "Senior", orgUnit: "Studio A", postalCode: "80331", federalState: "BY", active: true, totalAssignments: 4, totalVacations: 2, skillCount: 1, topSkills: ["Houdini (5)"], }); }); it("rejects foreign identifier lookups for regular users", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(null), findFirst: vi.fn() .mockResolvedValueOnce({ id: "res_other", eid: "E-OTHER", displayName: "Bob Other", chapter: "CGI", isActive: true, }) .mockResolvedValueOnce({ id: "res_own" }), }, }; const caller = createProtectedCaller(db); await expect( caller.getByIdentifier({ identifier: "Bob Other" }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "You can only view your own resource unless you have staff access", }); }); it("does not return fuzzy identifier suggestions to regular users", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(null), findFirst: vi.fn().mockResolvedValueOnce(null), findMany: vi.fn(), }, }; const caller = createProtectedCaller(db); const result = await caller.getByIdentifier({ identifier: "Ali" }); expect(result).toEqual({ error: "Resource not found: Ali" }); expect(db.resource.findMany).not.toHaveBeenCalled(); }); it("rejects foreign EID lookups for regular users", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_own" }), findUnique: vi.fn().mockResolvedValue({ id: "res_other", eid: "E-OTHER", displayName: "Bob Other", }), }, }; const caller = createProtectedCaller(db); await expect( caller.getByEid({ eid: "E-OTHER" }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "You can only view your own resource unless you have staff access", }); }); it("rejects foreign resolveByIdentifier lookups for regular users", async () => { const db = { resource: { findFirst: vi.fn() .mockResolvedValueOnce({ id: "res_other", eid: "E-OTHER", displayName: "Bob Other", chapter: "CGI", isActive: true, }) .mockResolvedValueOnce({ id: "res_own" }), findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(null), }, }; const caller = createProtectedCaller(db); await expect( caller.resolveByIdentifier({ identifier: "Bob Other" }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "You can only resolve your own resource unless you have staff access", }); }); it("returns only identity-safe fields from resolveByIdentifier", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({ id: "res_own", eid: "E-OWN", displayName: "Alice Example", chapter: "CGI", isActive: true, }), findFirst: vi.fn().mockResolvedValueOnce({ id: "res_own" }), findMany: vi.fn(), }, }; const caller = createProtectedCaller(db); const result = await caller.resolveByIdentifier({ identifier: "E-OWN" }); expect(result).toEqual({ id: "res_own", eid: "E-OWN", displayName: "Alice Example", chapter: "CGI", isActive: true, }); expect(db.resource.findMany).not.toHaveBeenCalled(); }); it("resolves responsible person names through the canonical resource search boundary", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValueOnce(null), findMany: vi.fn().mockResolvedValue([ { displayName: "Peter Parker", eid: "EMP-001" }, ]), }, }; const caller = createProtectedCallerWithOverrides(db, { granted: [PermissionKey.VIEW_ALL_RESOURCES] }); const result = await caller.resolveResponsiblePersonName({ name: "Peter" }); expect(result).toEqual({ status: "resolved", displayName: "Peter Parker", }); }); it("rejects responsible-person resolution for regular users without resource overview access", async () => { const db = { resource: { findFirst: vi.fn(), findMany: vi.fn(), }, }; const caller = createProtectedCaller(db); await expect( caller.resolveResponsiblePersonName({ name: "Peter" }), ).rejects.toMatchObject({ code: "FORBIDDEN", message: "Resource overview access required", }); expect(db.resource.findFirst).not.toHaveBeenCalled(); expect(db.resource.findMany).not.toHaveBeenCalled(); }); it("applies country filters on the staff list including explicit no-country toggle", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ countryIds: ["country_de", "country_us"], includeWithoutCountry: false, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { countryId: { in: ["country_de", "country_us"] } }, ]), }, }), ); }); it("excludes disabled countries on the staff list while leaving all others visible", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ excludedCountryIds: ["country_fr"], includeWithoutCountry: true, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { NOT: { countryId: { in: ["country_fr"] } } }, ]), }, }), ); }); it("applies resource type filters on the staff list while keeping unspecified rows when requested", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ resourceTypes: [ResourceType.EMPLOYEE, ResourceType.INTERN], includeWithoutResourceType: true, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { OR: [ { resourceType: { in: [ResourceType.EMPLOYEE, ResourceType.INTERN] } }, { resourceType: null }, ], }, ]), }, }), ); }); it("excludes disabled resource types on the staff list while leaving all others visible", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ excludedResourceTypes: [ResourceType.FREELANCER], includeWithoutResourceType: true, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { NOT: { resourceType: { in: [ResourceType.FREELANCER] } } }, ]), }, }), ); }); it("applies rolled-off and departed filters on the staff list", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ rolledOff: true, departed: false, }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { rolledOff: true }, { departed: false }, ]), }, }), ); }); it("applies multi-select chapter filters on the staff list", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ chapters: ["Art Direction", "Project Management"], }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { chapter: { in: ["Art Direction", "Project Management"] } }, ]), }, }), ); }); it("supports stable anonymized identities and alias-based filtering on the staff list", async () => { const resource = { id: "resource_anon_1", eid: "h.noerenberg", displayName: "Hartmut Noerenberg", email: "h.noerenberg@accenture.com", chapter: "Art Direction", lcrCents: 15000, isActive: true, createdAt: new Date("2026-03-01"), updatedAt: new Date("2026-03-01"), }; const db = { systemSettings: { findUnique: vi.fn().mockResolvedValue({ anonymizationEnabled: true, anonymizationDomain: "superhartmut.de", anonymizationSeed: null, anonymizationMode: "global", }), }, resource: { findMany: vi.fn().mockResolvedValue([resource]), count: vi.fn(), }, }; const caller = createControllerCaller(db); const first = await caller.listStaff({ limit: 10 }); const alias = first.resources[0]; expect(alias).toBeDefined(); expect(alias?.displayName).not.toBe(resource.displayName); expect(alias?.eid).not.toBe(resource.eid); expect(alias?.email).toBe(`${alias?.eid}@superhartmut.de`); const byAlias = await caller.listStaff({ eids: [alias!.eid], limit: 10 }); const byAliasSearch = await caller.listStaff({ search: alias!.displayName.slice(0, 4), limit: 10 }); expect(byAlias.resources).toHaveLength(1); expect(byAlias.resources[0]?.id).toBe(resource.id); expect(byAliasSearch.resources).toHaveLength(1); expect(byAliasSearch.resources[0]?.id).toBe(resource.id); }); it("includes email search for staff resource list lookups", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ search: "alice@example.com", limit: 25 }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: expect.arrayContaining([ { isActive: true }, { OR: [ { displayName: { contains: "alice@example.com", mode: "insensitive" } }, { eid: { contains: "alice@example.com", mode: "insensitive" } }, { email: { contains: "alice@example.com", mode: "insensitive" } }, ], }, ]), }, }), ); }); it("keeps contact and cost fields intact in staff resource lists", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_other", eid: "E-OTHER", displayName: "Bob", email: "bob@example.com", lcrCents: 7000, ucrCents: 12000, chargeabilityTarget: 85, valueScore: 88, valueScoreBreakdown: { scarcity: 40 }, valueScoreUpdatedAt: new Date("2026-03-02T00:00:00.000Z"), }, ]), count: vi.fn().mockResolvedValue(1), }, }; const caller = createControllerCaller(db); const result = await caller.listStaff({ limit: 10 }); expect(result.resources[0]).toMatchObject({ id: "res_other", email: "bob@example.com", lcrCents: 7000, ucrCents: 12000, chargeabilityTarget: 85, valueScore: 88, valueScoreBreakdown: { scarcity: 40 }, }); expect(result.resources[0]?.valueScoreUpdatedAt).toBeInstanceOf(Date); }); it("keeps includeRoles available for the staff route", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createControllerCaller(db); await caller.listStaff({ limit: 10, includeRoles: true }); expect(db.resource.findMany).toHaveBeenCalledWith( expect.objectContaining({ include: expect.objectContaining({ resourceRoles: expect.objectContaining({ include: expect.any(Object), }), }), }), ); }); });