import { FieldType, ProjectStatus } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { beforeEach, describe, expect, it, vi } from "vitest"; const { countPlanningEntries, loadProjectPlanningReadModel, getProjectShoringRatio, } = vi.hoisted(() => ({ countPlanningEntries: vi.fn(), loadProjectPlanningReadModel: vi.fn(), getProjectShoringRatio: vi.fn(), })); vi.mock("@capakraken/application", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, countPlanningEntries, }; }); vi.mock("../router/project-planning-read-model.js", () => ({ loadProjectPlanningReadModel, })); vi.mock("../router/project-shoring-ratio.js", () => ({ getProjectShoringRatio, })); import { getProjectById, getProjectShoringRatioData, listProjects, } from "../router/project-procedure-support.js"; function createContext(db: Record) { return { db: db as never }; } describe("project-procedure-support", () => { beforeEach(() => { vi.clearAllMocks(); }); it("lists projects with planning counts and dynamic field filters", async () => { const findMany = vi.fn().mockResolvedValue([ { id: "project_1", name: "Platform Refresh", shortCode: "PRJ-1", status: ProjectStatus.ACTIVE, startDate: new Date("2026-01-01T00:00:00.000Z"), }, ]); const count = vi.fn().mockResolvedValue(1); countPlanningEntries.mockResolvedValue({ countsByProjectId: new Map([["project_1", 3]]), }); const result = await listProjects( createContext({ project: { findMany, count, }, }), { limit: 50, page: 1, status: ProjectStatus.ACTIVE, search: "Platform", customFieldFilters: [ { key: "market", value: "de", type: FieldType.TEXT }, ], }, ); expect(result.total).toBe(1); expect(result.projects).toEqual([ expect.objectContaining({ id: "project_1", _count: { allocations: 3 }, }), ]); expect(findMany).toHaveBeenCalledWith({ where: { status: ProjectStatus.ACTIVE, OR: [ { name: { contains: "Platform", mode: "insensitive" } }, { shortCode: { contains: "Platform", mode: "insensitive" } }, ], AND: [ { dynamicFields: { path: ["market"], string_contains: "de", }, }, ], }, skip: 0, take: 51, orderBy: [{ startDate: "asc" }, { id: "asc" }], }); expect(count).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ status: ProjectStatus.ACTIVE, }), })); expect(countPlanningEntries).toHaveBeenCalledWith( expect.objectContaining({ project: expect.objectContaining({ findMany, count, }), }), { projectIds: ["project_1"] }, ); }); it("returns a project detail enriched with planning read model data", async () => { const findUnique = vi.fn().mockResolvedValue({ id: "project_1", name: "Platform Refresh", blueprint: null, }); loadProjectPlanningReadModel.mockResolvedValue({ readModel: { assignments: [{ id: "assignment_1" }], demands: [{ id: "demand_1" }], }, }); const result = await getProjectById( createContext({ project: { findUnique }, }), { id: "project_1" }, ); expect(result).toEqual({ id: "project_1", name: "Platform Refresh", blueprint: null, allocations: [{ id: "assignment_1" }], demands: [{ id: "demand_1" }], assignments: [{ id: "assignment_1" }], }); expect(findUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, include: { blueprint: true }, }); expect(loadProjectPlanningReadModel).toHaveBeenCalledWith( expect.objectContaining({ project: expect.objectContaining({ findUnique }), }), { projectId: "project_1" }, ); }); it("throws not found when the requested project is missing", async () => { loadProjectPlanningReadModel.mockResolvedValue({ readModel: { assignments: [], demands: [] }, }); await expect( getProjectById( createContext({ project: { findUnique: vi.fn().mockResolvedValue(null) }, }), { id: "missing" }, ), ).rejects.toThrowError(new TRPCError({ code: "NOT_FOUND", message: "Project not found", })); }); it("delegates shoring ratio reads to the dedicated shoring helper", async () => { getProjectShoringRatio.mockResolvedValue({ totalHours: 40, onshoreRatio: 60, offshoreRatio: 40, }); const result = await getProjectShoringRatioData( createContext({ project: { findUnique: vi.fn() }, }), { projectId: "project_1" }, ); expect(result).toEqual({ totalHours: 40, onshoreRatio: 60, offshoreRatio: 40, }); expect(getProjectShoringRatio).toHaveBeenCalledWith( expect.any(Object), "project_1", ); }); });