import { SystemRole } 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(), }; }); vi.mock("../router/blueprint-validation.js", () => ({ assertBlueprintDynamicFields: vi.fn().mockResolvedValue(undefined), })); vi.mock("../lib/anonymization.js", () => ({ anonymizeResource: vi.fn((r: Record) => r), anonymizeResources: vi.fn((rs: unknown[]) => rs), anonymizeSearchMatches: vi.fn((rs: unknown[]) => rs), getAnonymizationDirectory: vi.fn().mockResolvedValue(null), resolveResourceIdsByDisplayedEids: vi.fn().mockResolvedValue(new Map()), })); import { resourceRouter } from "../router/resource.js"; import { createCallerFactory } from "../trpc.js"; const createCaller = createCallerFactory(resourceRouter); function createManagerCaller(db: Record) { return createCaller({ session: { user: { email: "manager@example.com", name: "Manager", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "mgr_1", systemRole: SystemRole.MANAGER, permissionOverrides: null, }, }); } function createProtectedCaller(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, }, }); } const sampleResource = { id: "res_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, blueprint: 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, resourceRoles: [], areaRole: null, countryId: null, metroCityId: null, orgUnitId: null, managementLevelGroupId: null, managementLevelId: null, resourceType: null, chgResponsibility: null, rolledOff: false, departed: false, enterpriseId: null, clientUnitId: null, fte: 1, }; describe("resource router CRUD", () => { beforeEach(() => { vi.clearAllMocks(); }); // ─── listStaff ──────────────────────────────────────────────────────────── describe("listStaff", () => { it("returns paginated results with total count for staff callers", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([sampleResource]), count: vi.fn().mockResolvedValue(1), }, }; const caller = createManagerCaller(db); const result = await caller.listStaff({ limit: 50 }); expect(result.resources).toHaveLength(1); expect(result.resources[0]?.displayName).toBe("Alice"); expect(db.resource.findMany).toHaveBeenCalled(); }); it("applies search filter for staff callers", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, }; const caller = createManagerCaller(db); await caller.listStaff({ search: "Alice", limit: 50 }); expect(db.resource.findMany).toHaveBeenCalled(); }); }); // ─── getById ────────────────────────────────────────────────────────────── describe("getById", () => { it("returns correct resource", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_1" }), findUnique: vi.fn().mockResolvedValue({ ...sampleResource, userId: "user_1" }), findMany: vi.fn().mockResolvedValue([]), }, systemSettings: { findUnique: vi.fn().mockResolvedValue(null), }, }; const caller = createProtectedCaller(db); const result = await caller.getById({ id: "res_1" }); expect(result.id).toBe("res_1"); expect(result.displayName).toBe("Alice"); }); it("throws NOT_FOUND when resource does not exist", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_1" }), findUnique: vi.fn().mockResolvedValue(null), findMany: vi.fn().mockResolvedValue([]), }, systemSettings: { findUnique: vi.fn().mockResolvedValue(null), }, }; const caller = createProtectedCaller(db); await expect(caller.getById({ id: "missing" })).rejects.toThrow( expect.objectContaining({ code: "NOT_FOUND" }), ); }); it("sets isOwnedByCurrentUser when userId matches", async () => { const ownedResource = { ...sampleResource, userId: "user_1" }; const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_1" }), findUnique: vi.fn().mockResolvedValue(ownedResource), findMany: vi.fn().mockResolvedValue([]), }, systemSettings: { findUnique: vi.fn().mockResolvedValue(null), }, }; const caller = createProtectedCaller(db); const result = await caller.getById({ id: "res_1" }); expect(result.isOwnedByCurrentUser).toBe(true); }); it("rejects foreign resources for regular users", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_own" }), findUnique: vi.fn().mockResolvedValue(sampleResource), findMany: vi.fn().mockResolvedValue([]), }, }; const caller = createProtectedCaller(db); await expect(caller.getById({ id: "res_1" })).rejects.toThrow( expect.objectContaining({ code: "FORBIDDEN" }), ); }); }); // ─── create ─────────────────────────────────────────────────────────────── describe("create", () => { it("creates a resource and returns it", async () => { const created = { ...sampleResource, id: "res_new", resourceRoles: [] }; const db = { resource: { findFirst: vi.fn().mockResolvedValue(null), create: vi.fn().mockResolvedValue(created), }, auditLog: { create: vi.fn().mockResolvedValue({}) }, }; const caller = createManagerCaller(db); const result = await caller.create({ eid: "E-NEW", displayName: "New Resource", email: "new@example.com", lcrCents: 4000, ucrCents: 8000, }); expect(result.id).toBe("res_new"); expect(db.resource.create).toHaveBeenCalled(); expect(db.auditLog.create).toHaveBeenCalled(); }); it("throws CONFLICT on duplicate eid or email", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue(sampleResource), create: vi.fn(), }, auditLog: { create: vi.fn() }, }; const caller = createManagerCaller(db); await expect( caller.create({ eid: "E-001", displayName: "Duplicate", email: "alice@example.com", lcrCents: 5000, ucrCents: 9000, }), ).rejects.toThrow( expect.objectContaining({ code: "CONFLICT" }), ); }); it("blocks USER role from creating resources", async () => { const db = { resource: { findFirst: vi.fn(), create: vi.fn() }, auditLog: { create: vi.fn() }, }; const caller = createProtectedCaller(db); await expect( caller.create({ eid: "E-002", displayName: "Blocked", email: "blocked@example.com", lcrCents: 4000, ucrCents: 8000, }), ).rejects.toThrow( expect.objectContaining({ code: "FORBIDDEN" }), ); }); }); // ─── update ─────────────────────────────────────────────────────────────── describe("update", () => { it("updates resource fields", async () => { const updated = { ...sampleResource, displayName: "Alice Updated" }; const db = { resource: { findUnique: vi.fn().mockResolvedValue(sampleResource), update: vi.fn().mockResolvedValue(updated), }, resourceRole: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) }, auditLog: { create: vi.fn().mockResolvedValue({}) }, }; const caller = createManagerCaller(db); const result = await caller.update({ id: "res_1", data: { displayName: "Alice Updated" }, }); expect(result.displayName).toBe("Alice Updated"); expect(db.resource.update).toHaveBeenCalledWith( expect.objectContaining({ where: { id: "res_1" } }), ); }); it("throws NOT_FOUND when resource does not exist", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValue(null), update: vi.fn(), }, auditLog: { create: vi.fn() }, }; const caller = createManagerCaller(db); await expect( caller.update({ id: "missing", data: { displayName: "X" } }), ).rejects.toThrow( expect.objectContaining({ code: "NOT_FOUND" }), ); }); }); // ─── deactivate ─────────────────────────────────────────────────────────── describe("deactivate", () => { it("sets isActive to false", async () => { const deactivated = { ...sampleResource, isActive: false }; const db = { resource: { update: vi.fn().mockResolvedValue(deactivated), }, auditLog: { create: vi.fn().mockResolvedValue({}) }, }; const caller = createManagerCaller(db); const result = await caller.deactivate({ id: "res_1" }); expect(result.isActive).toBe(false); expect(db.resource.update).toHaveBeenCalledWith( expect.objectContaining({ where: { id: "res_1" }, data: { isActive: false }, }), ); }); }); // ─── getHoverCard ───────────────────────────────────────────────────────── describe("getHoverCard", () => { it("returns expected shape with key fields", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_1" }), findUnique: vi.fn().mockResolvedValue({ id: "res_1", displayName: "Alice", eid: "E-001", email: "alice@example.com", chapter: "CGI", lcrCents: 5000, ucrCents: 9000, currency: "EUR", chargeabilityTarget: 80, skills: [], availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, isActive: true, areaRole: null, country: null, managementLevel: null, resourceType: null, }), }, systemSettings: { findUnique: vi.fn().mockResolvedValue(null), }, }; const caller = createProtectedCaller(db); const result = await caller.getHoverCard({ id: "res_1" }); expect(result).toMatchObject({ id: "res_1", displayName: "Alice", chapter: "CGI", lcrCents: 5000, isActive: true, }); }); it("throws NOT_FOUND for missing resource", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_1" }), findUnique: vi.fn().mockResolvedValue(null), }, systemSettings: { findUnique: vi.fn().mockResolvedValue(null) }, }; const caller = createProtectedCaller(db); await expect(caller.getHoverCard({ id: "missing" })).rejects.toThrow( expect.objectContaining({ code: "NOT_FOUND" }), ); }); it("rejects foreign hover-card access for regular users", async () => { const db = { resource: { findFirst: vi.fn().mockResolvedValue({ id: "res_own" }), findUnique: vi.fn().mockResolvedValue({ id: "res_1", displayName: "Alice", eid: "E-001", email: "alice@example.com", chapter: "CGI", lcrCents: 5000, ucrCents: 9000, currency: "EUR", chargeabilityTarget: 80, skills: [], availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, isActive: true, areaRole: null, country: null, managementLevel: null, resourceType: null, }), }, }; const caller = createProtectedCaller(db); await expect(caller.getHoverCard({ id: "res_1" })).rejects.toThrow( expect.objectContaining({ code: "FORBIDDEN" }), ); }); }); // ─── importSkillMatrix ──────────────────────────────────────────────────── describe("importSkillMatrix", () => { it("imports skills for the current user resource", async () => { const updatedResource = { ...sampleResource, skills: [{ skill: "Maya", proficiency: 4 }], skillMatrixUpdatedAt: new Date(), }; const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1", email: "user@example.com", resource: { id: "res_1" }, }), }, resource: { update: vi.fn().mockResolvedValue(updatedResource), }, }; const caller = createProtectedCaller(db); const result = await caller.importSkillMatrix({ skills: [{ skill: "Maya", proficiency: 4 }], }); expect(db.resource.update).toHaveBeenCalledWith( expect.objectContaining({ where: { id: "res_1" }, }), ); }); it("throws NOT_FOUND when user has no linked resource", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1", email: "user@example.com", resource: null, }), }, }; const caller = createProtectedCaller(db); await expect( caller.importSkillMatrix({ skills: [{ skill: "Nuke", proficiency: 3 }], }), ).rejects.toThrow("No resource linked to your account"); }); }); });