import { describe, it, expect, beforeAll, afterAll } from "vitest"; import prisma from "../config/database"; import { updateEntry } from "../services/plan.service"; /** * Pinning tests for audit finding M9: updateEntry must re-check the per-cell * cap (MAX_RECORDS_PER_CELL = 3) when the date range changes — expanding an * entry onto a day that already holds 3 active entries used to succeed, * producing a 4th that the grid (capped at 3) silently hides. * * The re-check must EXCLUDE the updated entry itself, so editing an at-cap * entry without moving it stays allowed. */ const NOTE = "plan_updcap_test"; let userId: number; let cappedEntryIds: number[] = []; let fourthId: number; const D = (day: number) => new Date(Date.UTC(2099, 6, day)); // July 2099, UTC async function cleanup() { await prisma.work_plan_entries.deleteMany({ where: { note: NOTE } }); } beforeAll(async () => { await cleanup(); const user = await prisma.users.findFirst({ select: { id: true } }); if (!user) throw new Error("Test setup: no users — seed the database"); userId = user.id; cappedEntryIds = []; for (let i = 0; i < 3; i++) { const entry = await prisma.work_plan_entries.create({ data: { user_id: userId, date_from: D(1), date_to: D(1), category: "work", note: NOTE, created_by: userId, }, }); cappedEntryIds.push(entry.id); } const fourth = await prisma.work_plan_entries.create({ data: { user_id: userId, date_from: D(2), date_to: D(2), category: "work", note: NOTE, created_by: userId, }, }); fourthId = fourth.id; }); afterAll(async () => { await cleanup(); await prisma.$disconnect(); }); describe("updateEntry per-cell cap (audit M9)", () => { it("rejects expanding an entry onto a day already at the cap", async () => { const result = await updateEntry( fourthId, { date_from: "2099-07-01", date_to: "2099-07-01" }, userId, false, ); expect("error" in result).toBe(true); if ("error" in result) expect(result.status).toBe(400); // The entry must not have moved. const fresh = await prisma.work_plan_entries.findUnique({ where: { id: fourthId }, }); expect(fresh?.date_from.toISOString().slice(0, 10)).toBe("2099-07-02"); }); it("still allows editing an at-cap entry without moving it (self-exclusion)", async () => { const result = await updateEntry( cappedEntryIds[0], { date_from: "2099-07-01", date_to: "2099-07-01", note: NOTE }, userId, false, ); expect("error" in result).toBe(false); }); it("still allows a note-only edit and a move to a free day", async () => { const noteOnly = await updateEntry(fourthId, { note: NOTE }, userId, false); expect("error" in noteOnly).toBe(false); const move = await updateEntry( fourthId, { date_from: "2099-07-03", date_to: "2099-07-03" }, userId, false, ); expect("error" in move).toBe(false); }); });