Owner Administration is complete. Summary of this final stretch of work:

Seed data extended with an inactive staff member, a pending invitation, a redeemed-reward fixture, a service with no eligible staff, and a future-appointment fixture for conflict testing — all idempotent, verified against the dev DB.

Testing brought to 179 unit / 86 integration / 27 E2E tests, all passing together on a freshly seeded, disposable Postgres:

Unit tests for every new pure validator (business hours, categories/services, booking rules, loyalty config, staff creation).
Integration tests covering opening-hours conflicts, last-owner protection, staff deactivation/eligibility conflicts, loyalty redemption's atomic race-guard, and owner-vs-barber/business-isolation authorization — all against a real Postgres.
12 new Playwright owner journeys, which surfaced and fixed two genuine issues along the way: a cookie-clearing race condition in the sign-out-mid-test flow (a real, reproducible timing bug, not just a test artifact), and confirmation that seed fixtures upserted by deterministic ID go stale once "today" has moved on — now handled defensively.
Desktop responsiveness: converted the single-column Business Details, Booking Rules, and Loyalty Configuration forms to a genuine two-column layout at the lg: breakpoint (verified via measured element positions, not just screenshots), confirmed mobile stacking is unaffected.

Final verification: clean tsc, eslint, and next build, plus the full three-suite run against one fresh database as the authoritative pass.
This commit is contained in:
Stefan Lazic committed 2026-08-03 12:56:55 +01:00
1 parent ffee9d6754
commit ec5583f972
73 files changed
+8400 -53

No files matched your search

@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import { validateBookingRuleInput } from "../booking-rule.service";
import type { BookingRuleInput } from "../booking-rule.service";
const base: BookingRuleInput = {
minNoticeMinutes: 60,
maxAdvanceDays: 30,
cancellationWindowHours: 24,
rescheduleWindowHours: 24,
slotIntervalMinutes: 15,
bufferBeforeMinutes: 0,
bufferAfterMinutes: 0,
maxAppointmentsPerDay: null,
maxAppointmentsPerStaffPerDay: null,
customerCanChooseStaff: true,
allowGuestBooking: true,
requireApproval: false,
};
describe("validateBookingRuleInput", () => {
it("accepts the default configuration", () => {
expect(validateBookingRuleInput(base).valid).toBe(true);
});
it("rejects a negative minimum notice", () => {
const result = validateBookingRuleInput({ ...base, minNoticeMinutes: -1 });
expect(result.valid).toBe(false);
expect(result.issues[0]?.code).toBe("INVALID_INPUT");
});
it("rejects a maximum advance window under 1 day", () => {
const result = validateBookingRuleInput({ ...base, maxAdvanceDays: 0 });
expect(result.valid).toBe(false);
});
it("rejects a negative cancellation window", () => {
expect(validateBookingRuleInput({ ...base, cancellationWindowHours: -1 }).valid).toBe(false);
});
it("rejects a negative reschedule window", () => {
expect(validateBookingRuleInput({ ...base, rescheduleWindowHours: -1 }).valid).toBe(false);
});
it.each([4, 121])("rejects a slot interval outside 5-120 minutes (%s)", (slotIntervalMinutes) => {
expect(validateBookingRuleInput({ ...base, slotIntervalMinutes }).valid).toBe(false);
});
it.each([5, 120])("accepts slot interval boundaries (%s)", (slotIntervalMinutes) => {
expect(validateBookingRuleInput({ ...base, slotIntervalMinutes }).valid).toBe(true);
});
it("rejects a negative buffer", () => {
expect(validateBookingRuleInput({ ...base, bufferBeforeMinutes: -5 }).valid).toBe(false);
});
it("accepts null max-appointments-per-day (unlimited)", () => {
expect(validateBookingRuleInput({ ...base, maxAppointmentsPerDay: null }).valid).toBe(true);
});
it("rejects a zero max-appointments-per-day", () => {
expect(validateBookingRuleInput({ ...base, maxAppointmentsPerDay: 0 }).valid).toBe(false);
});
it("rejects a zero max-appointments-per-staff-per-day", () => {
expect(validateBookingRuleInput({ ...base, maxAppointmentsPerStaffPerDay: 0 }).valid).toBe(false);
});
it("accepts a positive max-appointments-per-staff-per-day", () => {
expect(validateBookingRuleInput({ ...base, maxAppointmentsPerStaffPerDay: 5 }).valid).toBe(true);
});
});