import { describe, it, expect, beforeAll, afterAll } from "vitest"; import prisma from "../config/database"; /** * Pins the 2026-06-12 DB hardening migration: * 1. CHECK (quantity >= 0) on sklad_batches + sklad_item_locations — the DB * itself rejects negative stock, so a future C2-class bug (cumulative * decrements driving a batch negative) becomes a loud error instead of * silently hidden corruption. * 2. audit_logs.created_at is DATETIME, not TIMESTAMP — no 2038 cap. */ const N = "db_constraints_"; let itemId: number; let locationId: number; let receiptId: number; let lineAId: number; let lineBId: number; async function cleanup() { await prisma.sklad_batches.deleteMany({ where: { item: { name: { contains: N } } }, }); await prisma.sklad_item_locations.deleteMany({ where: { item: { name: { contains: N } } }, }); await prisma.sklad_receipt_lines.deleteMany({ where: { item: { name: { contains: N } } }, }); await prisma.sklad_receipts.deleteMany({ where: { notes: { contains: N } } }); await prisma.sklad_items.deleteMany({ where: { name: { contains: N } } }); await prisma.sklad_locations.deleteMany({ where: { name: { contains: N } }, }); await prisma.audit_logs.deleteMany({ where: { description: { contains: N } }, }); } beforeAll(async () => { await cleanup(); const item = await prisma.sklad_items.create({ data: { name: `${N}item`, unit: "ks" }, }); itemId = item.id; const location = await prisma.sklad_locations.create({ data: { code: `DBC${Date.now() % 100000}`, name: `${N}loc` }, }); locationId = location.id; const receipt = await prisma.sklad_receipts.create({ data: { notes: `${N}receipt`, status: "CONFIRMED" }, }); receiptId = receipt.id; const lineA = await prisma.sklad_receipt_lines.create({ data: { receipt_id: receiptId, item_id: itemId, quantity: 5, unit_price: 1, }, }); lineAId = lineA.id; const lineB = await prisma.sklad_receipt_lines.create({ data: { receipt_id: receiptId, item_id: itemId, quantity: 5, unit_price: 1, }, }); lineBId = lineB.id; }); afterAll(async () => { await cleanup(); await prisma.$disconnect(); }); describe("stock CHECK constraints", () => { it("rejects inserting a negative batch quantity", async () => { await expect( prisma.sklad_batches.create({ data: { item_id: itemId, receipt_line_id: lineAId, quantity: -1, original_qty: 5, unit_price: 1, is_consumed: false, }, }), ).rejects.toThrow(); }); it("rejects updating a batch quantity below zero", async () => { const batch = await prisma.sklad_batches.create({ data: { item_id: itemId, receipt_line_id: lineBId, quantity: 5, original_qty: 5, unit_price: 1, is_consumed: false, }, }); await expect( prisma.$executeRaw`UPDATE sklad_batches SET quantity = -2 WHERE id = ${batch.id}`, ).rejects.toThrow(); const fresh = await prisma.sklad_batches.findUnique({ where: { id: batch.id }, }); expect(Number(fresh?.quantity)).toBe(5); }); it("rejects a negative item_location quantity", async () => { await expect( prisma.sklad_item_locations.create({ data: { item_id: itemId, location_id: locationId, quantity: -1 }, }), ).rejects.toThrow(); }); }); describe("audit_logs.created_at has no 2038 cap", () => { it("accepts a far-future timestamp (DATETIME, not TIMESTAMP)", async () => { const row = await prisma.audit_logs.create({ data: { action: "test", description: `${N}future`, created_at: new Date(Date.UTC(2098, 0, 15, 12, 0, 0)), }, }); expect(row.created_at?.getUTCFullYear()).toBe(2098); }); });