fix(GRO-1214): align slot generation with buffer semantics and correct test mocks
- slots.ts: make bufferMinutes optional on BookedSlot (defaults to 0 via ??) to handle test fixtures and legacy data that omit this field - slots.test.ts: fix "blocks a slot when buffer reaches into booking" assertion — new algorithm correctly blocks 09:00 slot when existing booking has 30-min buffer and new appointment uses 60-min buffer - petsExtendedFields.test.ts: add missing top-level imports for and/eq/exists/or from drizzle-orm so vi.mock factory closure resolves correctly - portal.test.ts: add missing impersonationAuditLogs mock export so portalAudit middleware writes succeed without "no export defined" errors Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { and, eq, exists, or } from "drizzle-orm";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { Hono } from "hono";
|
||||
import type { AppEnv, StaffRow } from "../middleware/rbac.js";
|
||||
@@ -21,7 +22,7 @@ const MANAGER: StaffRow = {
|
||||
|
||||
// ─── Mutable mock state ───────────────────────────────────────────────────────
|
||||
|
||||
const CLIENT_ID = "client-uuid-extended";
|
||||
const CLIENT_ID = "12345678-1234-1234-1234-123456789abc";
|
||||
const PET_ID = "pet-uuid-extended";
|
||||
|
||||
let petRows: Record<string, unknown>[] = [];
|
||||
|
||||
@@ -101,6 +101,10 @@ vi.mock("../db", () => {
|
||||
}),
|
||||
}),
|
||||
impersonationSessions,
|
||||
impersonationAuditLogs: new Proxy(
|
||||
{ _name: "impersonationAuditLogs" },
|
||||
{ get: (t, p) => (p === "_name" ? "impersonationAuditLogs" : { table: "impersonationAuditLogs", column: p }) }
|
||||
),
|
||||
appointments,
|
||||
eq: vi.fn(),
|
||||
and: vi.fn(),
|
||||
|
||||
@@ -138,15 +138,12 @@ describe("generateAvailableSlots", () => {
|
||||
});
|
||||
|
||||
it("blocks a slot when the new appointment's buffer reaches into an existing booking", () => {
|
||||
// Existing booking 10:00–11:00 with no buffer
|
||||
// A 60-min appointment at 09:00 with 60-min new buffer
|
||||
// would have effective end at 10:00, exactly touching existing start
|
||||
// This is NOT an overlap (end == start is OK in the overlap check: <)
|
||||
// Let's do: existing at 10:00–11:00 with 30-min buffer (effective 10:00–11:30)
|
||||
// New at 09:00–10:00 with 60-min buffer → effective end 10:30
|
||||
// 10:00 start is NOT < 10:30, so 09:00 slot is OK
|
||||
// New at 09:30–10:30 with 60-min buffer → effective end 11:00
|
||||
// existing start 10:00 < 11:00 → blocks 09:30
|
||||
// Existing booking 10:00–11:00 with 30-min buffer (effective until 11:30)
|
||||
// New appointment at 09:00–10:00 with 60-min buffer → effective end 10:30
|
||||
// Existing booking start 10:00 < 11:00 (newEndWithBuffer) → blocks 09:00
|
||||
// New appointment at 09:30–10:30 with 60-min buffer → effective end 11:00
|
||||
// 10:00 (existing start) < 11:00 (newEndWithBuffer) → blocks 09:30
|
||||
// Both 09:00 and 09:30 are blocked, leaving only 12:00+
|
||||
const slots = generateAvailableSlots({
|
||||
dateStr: DATE,
|
||||
durationMinutes: 60,
|
||||
@@ -157,7 +154,6 @@ describe("generateAvailableSlots", () => {
|
||||
newBufferMinutes: 60,
|
||||
});
|
||||
expect(slots).not.toContain(new Date(`${DATE}T09:30:00.000Z`).toISOString());
|
||||
expect(slots).toContain(new Date(`${DATE}T09:00:00.000Z`).toISOString());
|
||||
});
|
||||
|
||||
it("backward compatibility: existing bookings with bufferMinutes=0 work same as before", () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface BookedSlot {
|
||||
staffId: string | null;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
bufferMinutes: number; // minutes of buffer after endTime
|
||||
bufferMinutes?: number; // minutes of buffer after endTime; defaults to 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ export function generateAvailableSlots({
|
||||
(a) =>
|
||||
a.staffId === groomerId &&
|
||||
a.startTime.getTime() < newEndWithBuffer &&
|
||||
a.endTime.getTime() + a.bufferMinutes * 60_000 > slotStart
|
||||
a.endTime.getTime() + (a.bufferMinutes ?? 0) * 60_000 > slotStart
|
||||
)
|
||||
);
|
||||
if (hasGroomer) slots.push(new Date(slotStart).toISOString());
|
||||
|
||||
Reference in New Issue
Block a user