import { describe, it, expect, beforeAll, afterAll } from "vitest"; import prisma from "../config/database"; import { createLeave, deleteAttendance } from "../services/attendance.service"; import { localDateStr } from "../utils/date"; /** * Pinning tests for audit finding C1: deleting a vacation/sick attendance * record must restore the hours it charged to leave_balances. Without the * restore, cancelling booked leave by deleting its rows permanently inflates * vacation_used/sick_used. */ const N = "att_delbal_"; let userId: number; /** First Monday of March in the given year — always before the earliest * possible Easter holiday and clear of all fixed Czech holidays, so a * Mon-Fri range books exactly 5 days. (Far-future year per fixture hygiene.) */ function firstMondayOfMarch(year: number): Date { const d = new Date(year, 2, 1, 12, 0, 0); while (d.getDay() !== 1) d.setDate(d.getDate() + 1); return d; } async function cleanup() { const users = await prisma.users.findMany({ where: { username: { startsWith: N } }, select: { id: true }, }); const ids = users.map((u) => u.id); if (ids.length > 0) { await prisma.attendance.deleteMany({ where: { user_id: { in: ids } } }); await prisma.leave_balances.deleteMany({ where: { user_id: { in: ids } } }); await prisma.users.deleteMany({ where: { id: { in: ids } } }); } } beforeAll(async () => { await cleanup(); const adminRole = await prisma.roles.findFirst({ where: { name: "admin" } }); if (!adminRole) throw new Error("Admin role not found — seed the database first"); const user = await prisma.users.create({ data: { username: `${N}user`, email: `${N}user@test.local`, password_hash: "$2a$12$LJ3m4ys3Lg4oLBFnYP2amuPBzJnJBbGzCl5Y6X9Y8r0q5.s3L6OyO", first_name: "Delete", last_name: "Balance", is_active: true, role_id: adminRole.id, }, }); userId = user.id; }); afterAll(async () => { await cleanup(); await prisma.$disconnect(); }); async function balance(year: number) { return prisma.leave_balances.findFirst({ where: { user_id: userId, year } }); } describe("deleteAttendance leave-balance restore (audit C1)", () => { it("restores vacation_used when booked vacation rows are deleted", async () => { const monday = firstMondayOfMarch(2098); const friday = new Date(monday); friday.setDate(friday.getDate() + 4); const created = await createLeave( { user_id: userId, date_from: localDateStr(monday), date_to: localDateStr(friday), leave_type: "vacation", leave_hours: 8, }, userId, ); expect("created" in created && created.created).toBe(5); expect(Number((await balance(2098))?.vacation_used)).toBe(40); const rows = await prisma.attendance.findMany({ where: { user_id: userId, leave_type: "vacation" }, }); expect(rows).toHaveLength(5); for (const row of rows) { const res = await deleteAttendance(row.id); expect("success" in res && res.success).toBe(true); } expect(Number((await balance(2098))?.vacation_used)).toBe(0); }); it("restores sick_used when a booked sick day is deleted", async () => { const monday = firstMondayOfMarch(2099); await createLeave( { user_id: userId, date_from: localDateStr(monday), leave_type: "sick", leave_hours: 8, }, userId, ); expect(Number((await balance(2099))?.sick_used)).toBe(8); const row = await prisma.attendance.findFirst({ where: { user_id: userId, leave_type: "sick" }, }); expect(row).not.toBeNull(); await deleteAttendance(row!.id); expect(Number((await balance(2099))?.sick_used)).toBe(0); }); it("does not touch balances when deleting a plain work shift", async () => { const wednesday = firstMondayOfMarch(2098); wednesday.setDate(wednesday.getDate() + 9); // a weekday a week later const shift = await prisma.attendance.create({ data: { user_id: userId, shift_date: wednesday, leave_type: "work", }, }); const before = await balance(2098); await deleteAttendance(shift.id); const after = await balance(2098); expect(Number(after?.vacation_used)).toBe(Number(before?.vacation_used)); expect(Number(after?.sick_used)).toBe(Number(before?.sick_used)); }); });