import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { Weekday } from "@/generated/prisma/enums"; import { createBreak, createTimeOff, deleteBreak, deleteTimeOff } from "@/services/staff/staff.service"; import { cleanupTestBusiness, createTestBusiness, createTestStaff, prisma, } from "@/services/scheduling/__tests__/fixtures"; import type { TestBusiness } from "@/services/scheduling/__tests__/fixtures"; /** * Owner-on-behalf-of-staff breaks and time off (Phase 7, closing the Phase * 6-documented gap — see docs/phase-7-summary.md, "Owner-managed * availability"). The owner Server Actions in * src/features/owner/actions/breaks.ts and time-off.ts are thin wrappers: * `assertOwnerPermission` (already covered by * src/features/owner/__tests__/authorization.integration.test.ts) plus a * business-scoped staffId lookup, then these exact StaffService calls with * an explicit target staffId instead of the caller's own. Importing the * "use server" action files directly here isn't possible — they import * `@/auth`, which pulls in next-auth's NextAuth() factory and fails * outside Next's own bundler/runtime (`Cannot find module * "next/server"`), so what these tests verify is the StaffService plumbing * an owner action delegates to, exactly as every other StaffService * integration test in this suite does. */ describe("Owner-managed breaks and time off (integration)", () => { let business: TestBusiness; beforeEach(async () => { business = await createTestBusiness(); }); afterEach(async () => { await cleanupTestBusiness(business); }); it("creates and deletes a break against an explicit target staffId, not the caller's own", async () => { const { staffId } = await createTestStaff(business); const created = await createBreak(prisma, { staffId, businessTimezone: business.timezone, input: { label: "Owner-added break", startMinutes: 12 * 60, endMinutes: 12 * 60 + 30, days: [Weekday.MONDAY] }, }); expect(created.success).toBe(true); if (!created.success) return; const stored = await prisma.break.findUnique({ where: { id: created.break.id } }); expect(stored?.staffId).toBe(staffId); expect(stored?.label).toBe("Owner-added break"); const removed = await deleteBreak(prisma, { breakId: created.break.id, staffId }); expect(removed.success).toBe(true); const gone = await prisma.break.findUnique({ where: { id: created.break.id } }); expect(gone).toBeNull(); }); it("creates and deletes whole-day time off against an explicit target staffId", async () => { const { staffId } = await createTestStaff(business); const created = await createTimeOff(prisma, { staffId, businessTimezone: business.timezone, input: { startDate: "2027-02-01", endDate: "2027-02-03", reason: "Owner-added leave" }, }); expect(created.success).toBe(true); if (!created.success) return; const stored = await prisma.timeOff.findUnique({ where: { id: created.timeOff.id } }); expect(stored?.staffId).toBe(staffId); expect(stored?.reason).toBe("Owner-added leave"); const removed = await deleteTimeOff(prisma, { timeOffId: created.timeOff.id, staffId }); expect(removed.success).toBe(true); const gone = await prisma.timeOff.findUnique({ where: { id: created.timeOff.id } }); expect(gone).toBeNull(); }); it("a break created for staff A is never visible when querying by staff B's id (no accidental cross-staff writes)", async () => { const { staffId: staffA } = await createTestStaff(business); const { staffId: staffB } = await createTestStaff(business); await createBreak(prisma, { staffId: staffA, businessTimezone: business.timezone, input: { label: "A's break", startMinutes: 9 * 60, endMinutes: 9 * 60 + 15, days: [Weekday.TUESDAY] }, }); const staffBBreaks = await prisma.break.findMany({ where: { staffId: staffB } }); expect(staffBBreaks).toHaveLength(0); }); });