test(api): lock report template completeness
This commit is contained in:
@@ -79,7 +79,7 @@ describe("report template procedure support", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
expect.objectContaining({
|
||||||
id: "tpl_1",
|
id: "tpl_1",
|
||||||
name: "Owned resource month",
|
name: "Owned resource month",
|
||||||
description: "Monthly default",
|
description: "Monthly default",
|
||||||
@@ -93,9 +93,28 @@ describe("report template procedure support", () => {
|
|||||||
},
|
},
|
||||||
isShared: false,
|
isShared: false,
|
||||||
isOwner: true,
|
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,
|
updatedAt,
|
||||||
},
|
}),
|
||||||
{
|
expect.objectContaining({
|
||||||
id: "tpl_2",
|
id: "tpl_2",
|
||||||
name: "Shared project",
|
name: "Shared project",
|
||||||
description: null,
|
description: null,
|
||||||
@@ -108,9 +127,63 @@ describe("report template procedure support", () => {
|
|||||||
},
|
},
|
||||||
isShared: true,
|
isShared: true,
|
||||||
isOwner: false,
|
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,
|
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 () => {
|
it("upserts new templates under the current owner with converted entity values", async () => {
|
||||||
@@ -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 () => {
|
it("deletes only owned templates", async () => {
|
||||||
const findUnique = vi.fn()
|
const findUnique = vi.fn()
|
||||||
.mockResolvedValueOnce({ ownerId: "user_controller" })
|
.mockResolvedValueOnce({ ownerId: "user_controller" })
|
||||||
|
|||||||
Reference in New Issue
Block a user