205 lines
5.2 KiB
TypeScript
205 lines
5.2 KiB
TypeScript
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<typeof import("@capakraken/application")>();
|
|
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<string, unknown>) {
|
|
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",
|
|
);
|
|
});
|
|
});
|