import { describe, it, expect, beforeAll, afterAll } from "vitest"; import prisma from "../config/database"; import { deleteOffer } from "../services/offers.service"; import { applyPattern } from "../services/numbering.service"; /** * Pinning test for audit finding L2: an offer created in year Y but * finalized (numbered) in year Y+1 must release the Y+1 sequence on delete. * deleteOffer used to derive the release year from created_at, so the * reconstructed highest number ("2025/NA/...") never matched the actual * "2026/NA/..." and the counter kept a permanent gap. */ const CURRENT_YEAR = new Date().getFullYear(); let offerId: number; let originalLastNumber: number | null = null; // null = row absent before test let offerNumber: string; async function getSeq(): Promise { const rows = await prisma.$queryRaw>` SELECT last_number FROM number_sequences WHERE \`type\` = 'offer' AND \`year\` = ${CURRENT_YEAR} `; return rows.length > 0 ? Number(rows[0].last_number) : null; } beforeAll(async () => { const settings = await prisma.company_settings.findFirst(); const pattern = settings?.offer_number_pattern || "{YYYY}/{PREFIX}/{NNN}"; const prefix = settings?.quotation_prefix || "NA"; originalLastNumber = await getSeq(); const base = originalLastNumber ?? 0; const seq = base + 1; offerNumber = applyPattern(pattern, { year: CURRENT_YEAR, prefix, code: "", seq, }); // Consume the number in the sequence (what finalize would have done). if (originalLastNumber === null) { await prisma.$executeRaw` INSERT INTO number_sequences (\`type\`, \`year\`, \`last_number\`) VALUES ('offer', ${CURRENT_YEAR}, ${seq}) `; } else { await prisma.$executeRaw` UPDATE number_sequences SET \`last_number\` = ${seq} WHERE \`type\` = 'offer' AND \`year\` = ${CURRENT_YEAR} `; } // The offer itself was CREATED last year (draft), finalized this year — // its number carries the finalize year, created_at the draft year. const offer = await prisma.quotations.create({ data: { quotation_number: offerNumber, status: "active", currency: "CZK", language: "cs", created_at: new Date(CURRENT_YEAR - 1, 11, 30, 12, 0, 0), }, }); offerId = offer.id; }); afterAll(async () => { await prisma.quotations .deleteMany({ where: { id: offerId } }) .catch(() => {}); // Restore the sequence to its pre-test value regardless of outcome. if (originalLastNumber === null) { await prisma.$executeRaw` DELETE FROM number_sequences WHERE \`type\` = 'offer' AND \`year\` = ${CURRENT_YEAR} `; } else { await prisma.$executeRaw` UPDATE number_sequences SET \`last_number\` = ${originalLastNumber} WHERE \`type\` = 'offer' AND \`year\` = ${CURRENT_YEAR} `; } await prisma.$disconnect(); }); describe("offer sequence release across years (audit L2)", () => { it("releases the finalize-year sequence when created_at is in a previous year", async () => { const before = await getSeq(); expect(before).toBe((originalLastNumber ?? 0) + 1); const result = await deleteOffer(offerId); expect(result && "error" in result ? result.error : null).toBeNull(); // The highest number was just deleted → the counter must step back. expect(await getSeq()).toBe(originalLastNumber ?? 0); }); });