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,73 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { Weekday } from "@/generated/prisma/enums";
|
||||
import { validateBusinessProfileInput, validateOpeningHoursInput } from "../business.service";
|
||||
|
||||
describe("validateBusinessProfileInput", () => {
|
||||
const base = {
|
||||
name: "Blake Heritage Barbershop",
|
||||
timezone: "Europe/London",
|
||||
currency: "GBP",
|
||||
locale: "en-GB",
|
||||
};
|
||||
|
||||
it("accepts a complete profile", () => {
|
||||
expect(validateBusinessProfileInput(base).valid).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects an empty name", () => {
|
||||
const result = validateBusinessProfileInput({ ...base, name: " " });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_INPUT");
|
||||
});
|
||||
|
||||
it("rejects a missing timezone/currency/locale", () => {
|
||||
const result = validateBusinessProfileInput({ ...base, timezone: "" });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_INPUT");
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateOpeningHoursInput", () => {
|
||||
it("accepts a day marked closed with no times", () => {
|
||||
const result = validateOpeningHoursInput([{ dayOfWeek: Weekday.SUNDAY, isClosed: true }]);
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it("requires open/close times for an open day", () => {
|
||||
const result = validateOpeningHoursInput([{ dayOfWeek: Weekday.MONDAY, isClosed: false }]);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_INPUT");
|
||||
});
|
||||
|
||||
it("rejects opening time at or after closing time", () => {
|
||||
const result = validateOpeningHoursInput([
|
||||
{ dayOfWeek: Weekday.TUESDAY, isClosed: false, opensAtMinutes: 600, closesAtMinutes: 600 },
|
||||
]);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_TIME_RANGE");
|
||||
});
|
||||
|
||||
it("rejects times outside a single day", () => {
|
||||
const result = validateOpeningHoursInput([
|
||||
{ dayOfWeek: Weekday.WEDNESDAY, isClosed: false, opensAtMinutes: 0, closesAtMinutes: 25 * 60 },
|
||||
]);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_TIME_RANGE");
|
||||
});
|
||||
|
||||
it("rejects a negative opening time", () => {
|
||||
const result = validateOpeningHoursInput([
|
||||
{ dayOfWeek: Weekday.THURSDAY, isClosed: false, opensAtMinutes: -30, closesAtMinutes: 600 },
|
||||
]);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_TIME_RANGE");
|
||||
});
|
||||
|
||||
it("accepts a valid full week mixing open and closed days", () => {
|
||||
const result = validateOpeningHoursInput([
|
||||
{ dayOfWeek: Weekday.MONDAY, isClosed: false, opensAtMinutes: 10 * 60, closesAtMinutes: 19 * 60 },
|
||||
{ dayOfWeek: Weekday.SUNDAY, isClosed: true },
|
||||
]);
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user