Phase 6
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:
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user