This commit is contained in:
Stefan Lazic committed 2026-08-03 21:15:24 +01:00
1 parent ec5583f972
commit 546613e677
189 files changed
+9873 -655

No files matched your search

@@ -360,6 +360,50 @@ describe("booking engine (integration)", () => {
});
expect(laterValidation.valid).toBe(true);
});
// Regression test for a real bug found via a MySQL E2E run (see
// docs/phase-7-summary.md): with an asymmetric buffer
// (bufferBeforeMinutes 0, bufferAfterMinutes > 0 — the seeded
// production default), assertNoConflict in scheduling.service.ts was
// applying the wrong side's buffer to the candidate appointment,
// wrongly rejecting a slot placed immediately BEFORE an existing
// appointment using that appointment's *after* buffer instead of its
// (zero) *before* buffer. The test above only exercises
// validateBookingSlot (AvailabilityService) for a candidate placed
// AFTER an existing appointment; createBooking's actual DB-level
// conflict check (assertNoConflict) was never exercised for the
// "before" direction until now.
it("does not apply a later appointment's after-buffer to a candidate slot placed immediately before it", async () => {
const { staffId } = await createTestStaff(business);
const { customerId } = await createTestCustomer(business);
await prisma.bookingRule.update({
where: { businessId: business.businessId },
data: { bufferBeforeMinutes: 0, bufferAfterMinutes: 5 },
});
// Book the later appointment first: 10:30–11:00.
const later = await createBooking(prisma, {
businessId: business.businessId,
customerId,
staffId,
serviceIds: [business.haircutServiceId], // 30 min
startAt: wallClockToInstant(monday, 10 * 60 + 30, business.timezone),
});
expect(later.success).toBe(true);
// A candidate ending exactly when the later appointment starts (10:00–10:30) needs no gap —
// only the LATER appointment's own trailing (after) buffer applies once IT finishes, not
// before it starts, since bufferBeforeMinutes is 0.
const earlier = await createBooking(prisma, {
businessId: business.businessId,
customerId,
staffId,
serviceIds: [business.haircutServiceId],
startAt: wallClockToInstant(monday, 10 * 60, business.timezone),
});
expect(earlier.success).toBe(true);
});
});
describe("booking validation", () => {