test(api): lock report template completeness
This commit is contained in:
@@ -79,7 +79,7 @@ describe("report template procedure support", () => {
|
||||
},
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{
|
||||
expect.objectContaining({
|
||||
id: "tpl_1",
|
||||
name: "Owned resource month",
|
||||
description: "Monthly default",
|
||||
@@ -93,9 +93,28 @@ describe("report template procedure support", () => {
|
||||
},
|
||||
isShared: false,
|
||||
isOwner: true,
|
||||
completeness: expect.objectContaining({
|
||||
scope: "resource_month",
|
||||
isAuditReady: false,
|
||||
isRecommendedComplete: false,
|
||||
recommendedColumnCount: 26,
|
||||
selectedRecommendedColumnCount: 2,
|
||||
minimumAuditColumnCount: 13,
|
||||
selectedMinimumAuditColumnCount: 2,
|
||||
missingRecommendedColumns: expect.arrayContaining([
|
||||
"monthKey",
|
||||
"monthlyTargetHours",
|
||||
"monthlyUnassignedHours",
|
||||
]),
|
||||
missingMinimumAuditColumns: expect.arrayContaining([
|
||||
"monthKey",
|
||||
"countryName",
|
||||
"monthlyTargetHours",
|
||||
]),
|
||||
}),
|
||||
updatedAt,
|
||||
},
|
||||
{
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "tpl_2",
|
||||
name: "Shared project",
|
||||
description: null,
|
||||
@@ -108,11 +127,65 @@ describe("report template procedure support", () => {
|
||||
},
|
||||
isShared: true,
|
||||
isOwner: false,
|
||||
completeness: null,
|
||||
updatedAt,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("marks resource month templates audit-ready once the minimum audit basis is present", async () => {
|
||||
const updatedAt = new Date("2026-03-31T10:30:00.000Z");
|
||||
const ctx = createContext({
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: "tpl_audit",
|
||||
name: "Audit-ready resource month",
|
||||
description: null,
|
||||
entity: "RESOURCE_MONTH",
|
||||
config: {
|
||||
entity: "resource_month",
|
||||
columns: [
|
||||
"monthKey",
|
||||
"displayName",
|
||||
"countryName",
|
||||
"federalState",
|
||||
"metroCityName",
|
||||
"monthlyPublicHolidayCount",
|
||||
"monthlyPublicHolidayHoursDeduction",
|
||||
"monthlyAbsenceDayEquivalent",
|
||||
"monthlyAbsenceHoursDeduction",
|
||||
"monthlySahHours",
|
||||
"monthlyTargetHours",
|
||||
"monthlyActualBookedHours",
|
||||
"monthlyUnassignedHours",
|
||||
],
|
||||
filters: [],
|
||||
periodMonth: "2026-03",
|
||||
},
|
||||
isShared: false,
|
||||
ownerId: "user_controller",
|
||||
updatedAt,
|
||||
},
|
||||
]),
|
||||
});
|
||||
|
||||
const [result] = await listReportTemplates(ctx);
|
||||
|
||||
expect(result?.completeness).toMatchObject({
|
||||
scope: "resource_month",
|
||||
isAuditReady: true,
|
||||
isRecommendedComplete: false,
|
||||
minimumAuditColumnCount: 13,
|
||||
selectedMinimumAuditColumnCount: 13,
|
||||
missingMinimumAuditColumns: [],
|
||||
missingRecommendedColumns: expect.arrayContaining([
|
||||
"eid",
|
||||
"chapter",
|
||||
"monthlyExpectedBookedHours",
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it("upserts new templates under the current owner with converted entity values", async () => {
|
||||
const upsert = vi.fn().mockResolvedValue({
|
||||
id: "tpl_new",
|
||||
@@ -235,6 +308,130 @@ describe("report template procedure support", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("clears an existing description when the template is saved with a blank description", async () => {
|
||||
const findUnique = vi.fn().mockResolvedValue({ ownerId: "user_controller" });
|
||||
const update = vi.fn().mockResolvedValue({
|
||||
id: "tpl_1",
|
||||
updatedAt: new Date("2026-03-31T13:00:00.000Z"),
|
||||
});
|
||||
const ctx = createContext({
|
||||
findUnique,
|
||||
update,
|
||||
});
|
||||
|
||||
const result = await saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
|
||||
id: "tpl_1",
|
||||
name: "Owned template",
|
||||
description: " ",
|
||||
config: {
|
||||
entity: "project",
|
||||
columns: ["name"],
|
||||
filters: [],
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "tpl_1",
|
||||
updatedAt: new Date("2026-03-31T13:00:00.000Z"),
|
||||
});
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
where: { id: "tpl_1" },
|
||||
data: {
|
||||
name: "Owned template",
|
||||
description: null,
|
||||
entity: "PROJECT",
|
||||
config: {
|
||||
entity: "project",
|
||||
columns: ["name"],
|
||||
filters: [],
|
||||
sortDir: "asc",
|
||||
},
|
||||
isShared: false,
|
||||
},
|
||||
select: { id: true, updatedAt: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("stores blank descriptions as null for new templates", async () => {
|
||||
const upsert = vi.fn().mockResolvedValue({
|
||||
id: "tpl_new",
|
||||
updatedAt: new Date("2026-03-31T14:00:00.000Z"),
|
||||
});
|
||||
const ctx = createContext({
|
||||
upsert,
|
||||
});
|
||||
|
||||
await saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
|
||||
name: "Monthly default",
|
||||
description: " ",
|
||||
isShared: true,
|
||||
config: {
|
||||
entity: "resource_month",
|
||||
columns: ["displayName", "monthlyTargetHours"],
|
||||
filters: [],
|
||||
periodMonth: "2026-03",
|
||||
},
|
||||
}));
|
||||
|
||||
expect(upsert).toHaveBeenCalledWith({
|
||||
where: {
|
||||
ownerId_name: {
|
||||
ownerId: "user_controller",
|
||||
name: "Monthly default",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
description: null,
|
||||
entity: "RESOURCE_MONTH",
|
||||
config: {
|
||||
entity: "resource_month",
|
||||
columns: ["displayName", "monthlyTargetHours"],
|
||||
filters: [],
|
||||
periodMonth: "2026-03",
|
||||
sortDir: "asc",
|
||||
},
|
||||
isShared: true,
|
||||
},
|
||||
create: {
|
||||
ownerId: "user_controller",
|
||||
name: "Monthly default",
|
||||
description: null,
|
||||
entity: "RESOURCE_MONTH",
|
||||
config: {
|
||||
entity: "resource_month",
|
||||
columns: ["displayName", "monthlyTargetHours"],
|
||||
filters: [],
|
||||
periodMonth: "2026-03",
|
||||
sortDir: "asc",
|
||||
},
|
||||
isShared: true,
|
||||
},
|
||||
select: { id: true, updatedAt: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("maps duplicate template names to a conflict error instead of leaking a raw database error", async () => {
|
||||
const update = vi.fn().mockRejectedValue({ code: "P2002" });
|
||||
const findUnique = vi.fn().mockResolvedValue({ ownerId: "user_controller" });
|
||||
const ctx = createContext({
|
||||
findUnique,
|
||||
update,
|
||||
});
|
||||
|
||||
await expect(saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
|
||||
id: "tpl_1",
|
||||
name: "Monthly default",
|
||||
config: {
|
||||
entity: "project",
|
||||
columns: ["name"],
|
||||
filters: [],
|
||||
},
|
||||
}))).rejects.toMatchObject({
|
||||
code: "CONFLICT",
|
||||
message: 'A report template named "Monthly default" already exists',
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes only owned templates", async () => {
|
||||
const findUnique = vi.fn()
|
||||
.mockResolvedValueOnce({ ownerId: "user_controller" })
|
||||
|
||||
Reference in New Issue
Block a user