Files
CapaKraken/packages/api/src/__tests__/assistant-tools-estimate-creation-races.test.ts
T

230 lines
6.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey, SystemRole } from "@capakraken/shared";
vi.mock("@capakraken/application", async (importOriginal) => {
const actual = await importOriginal<typeof import("@capakraken/application")>();
return {
...actual,
approveEstimateVersion: vi.fn(),
cloneEstimate: vi.fn(),
createEstimateExport: vi.fn(),
createEstimatePlanningHandoff: vi.fn(),
createEstimateRevision: vi.fn(),
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
getDashboardPeakTimes: vi.fn().mockResolvedValue([]),
getEstimateById: vi.fn(),
listAssignmentBookings: vi.fn().mockResolvedValue([]),
submitEstimateVersion: vi.fn(),
updateEstimateDraft: vi.fn(),
};
});
import { executeTool } from "../router/assistant-tools.js";
import {
createToolContext,
resetEstimateToolMocks,
} from "./assistant-tools-estimate-test-helpers.js";
describe("assistant estimate creation race tools", () => {
beforeEach(() => {
resetEstimateToolMocks();
});
it("returns stable assistant errors for estimate creation races", async () => {
const missingProjectCtx = createToolContext(
{
project: {
findUnique: vi.fn()
.mockResolvedValueOnce({ id: "project_1" })
.mockResolvedValueOnce({
id: "project_1",
shortCode: "PROJ-1",
name: "Project One",
status: "DRAFT",
startDate: new Date("2026-01-01T00:00:00.000Z"),
endDate: new Date("2026-12-31T00:00:00.000Z"),
orderType: "TIME_MATERIAL",
allocationType: "INT",
winProbability: 100,
budgetCents: 100000,
responsiblePerson: "Peter Parker",
}),
},
estimate: {
create: vi.fn().mockRejectedValue({
code: "P2003",
message: "Foreign key constraint failed",
meta: { field_name: "Estimate_projectId_fkey" },
}),
},
},
{
userRole: SystemRole.MANAGER,
permissions: [PermissionKey.MANAGE_PROJECTS],
},
);
const missingProjectResult = await executeTool(
"create_estimate",
JSON.stringify({ name: "Delivery Estimate", projectId: "project_1" }),
missingProjectCtx,
);
expect(JSON.parse(missingProjectResult.content)).toEqual({
error: "Project not found with the given criteria.",
});
const missingRoleCtx = createToolContext(
{
estimate: {
create: vi.fn().mockRejectedValue({
code: "P2003",
message: "Foreign key constraint failed",
meta: { field_name: "EstimateDemandLine_roleId_fkey" },
}),
},
rateCardLine: {
findMany: vi.fn().mockResolvedValue([]),
},
},
{
userRole: SystemRole.MANAGER,
permissions: [PermissionKey.MANAGE_PROJECTS],
},
);
const missingRoleResult = await executeTool(
"create_estimate",
JSON.stringify({
name: "Delivery Estimate",
demandLines: [
{
roleId: "role_1",
lineType: "LABOR",
name: "Design",
hours: 40,
costRateCents: 0,
billRateCents: 0,
currency: "EUR",
costTotalCents: 0,
priceTotalCents: 0,
monthlySpread: {},
staffingAttributes: {},
metadata: {},
},
],
}),
missingRoleCtx,
);
expect(JSON.parse(missingRoleResult.content)).toEqual({
error: "Role not found with the given criteria.",
});
const cases = [
{
name: "missing resource reference",
payload: {
name: "Delivery Estimate",
demandLines: [
{
resourceId: "resource_1",
lineType: "LABOR",
name: "Animation",
hours: 40,
costRateCents: 0,
billRateCents: 0,
currency: "EUR",
costTotalCents: 0,
priceTotalCents: 0,
monthlySpread: {},
staffingAttributes: {},
metadata: {},
},
],
},
rejection: {
code: "P2003",
message: "Foreign key constraint failed",
meta: { field_name: "EstimateDemandLine_resourceId_fkey" },
},
expected: "Resource not found with the given criteria.",
},
{
name: "missing scope item reference",
payload: {
name: "Delivery Estimate",
scopeItems: [
{
sequenceNo: 1,
scopeType: "SHOT",
name: "Shot 010",
technicalSpec: {},
metadata: {},
},
],
demandLines: [
{
scopeItemId: "scope_item_missing",
lineType: "LABOR",
name: "Lighting",
hours: 24,
costRateCents: 0,
billRateCents: 0,
currency: "EUR",
costTotalCents: 0,
priceTotalCents: 0,
monthlySpread: {},
staffingAttributes: {},
metadata: {},
},
],
},
rejection: {
code: "P2003",
message: "Foreign key constraint failed",
meta: { field_name: "EstimateDemandLine_scopeItemId_fkey" },
},
expected: "Estimate scope item not found with the given criteria.",
},
{
name: "generic referenced record race",
payload: { name: "Delivery Estimate" },
rejection: {
code: "P2025",
message: "Record to create no longer references a valid row",
meta: { cause: "Dependent record disappeared during nested estimate create" },
},
expected: "One of the referenced project, role, resource, or scope items no longer exists.",
},
] as const;
for (const testCase of cases) {
const ctx = createToolContext(
{
estimate: {
create: vi.fn().mockRejectedValue(testCase.rejection),
},
rateCardLine: {
findMany: vi.fn().mockResolvedValue([]),
},
},
{
userRole: SystemRole.MANAGER,
permissions: [PermissionKey.MANAGE_PROJECTS],
},
);
const result = await executeTool(
"create_estimate",
JSON.stringify(testCase.payload),
ctx,
);
expect(JSON.parse(result.content)).toEqual({
error: testCase.expected,
});
}
});
});