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,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { validateProgrammeConfig } from "../loyalty.service";
|
||||
import type { ProgrammeConfigInput } from "../loyalty.service";
|
||||
|
||||
const base: ProgrammeConfigInput = {
|
||||
isEnabled: true,
|
||||
stampsRequired: 10,
|
||||
rewardType: "PERCENTAGE",
|
||||
rewardValue: "50",
|
||||
autoApplyReward: true,
|
||||
requiresStaffApproval: false,
|
||||
earnOnCancelled: false,
|
||||
earnOnNoShow: false,
|
||||
};
|
||||
|
||||
describe("validateProgrammeConfig", () => {
|
||||
it("accepts a valid percentage-reward configuration", () => {
|
||||
expect(validateProgrammeConfig(base).valid).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a stamps-required of zero", () => {
|
||||
const result = validateProgrammeConfig({ ...base, stampsRequired: 0 });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_INPUT");
|
||||
});
|
||||
|
||||
it("allows an incomplete configuration while disabled", () => {
|
||||
const result = validateProgrammeConfig({ ...base, isEnabled: false, rewardValue: undefined });
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it.each([0, 101, -5])("rejects an out-of-range percentage when enabled (%s)", (pct) => {
|
||||
const result = validateProgrammeConfig({ ...base, rewardValue: String(pct) });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_PERCENTAGE");
|
||||
});
|
||||
|
||||
it("rejects a missing percentage value when enabled", () => {
|
||||
const result = validateProgrammeConfig({ ...base, rewardValue: undefined });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_PERCENTAGE");
|
||||
});
|
||||
|
||||
it("accepts a valid fixed-amount reward", () => {
|
||||
const result = validateProgrammeConfig({ ...base, rewardType: "FIXED_AMOUNT", rewardValue: "10.00" });
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a negative fixed-amount reward", () => {
|
||||
const result = validateProgrammeConfig({ ...base, rewardType: "FIXED_AMOUNT", rewardValue: "-1" });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("INVALID_VALUE");
|
||||
});
|
||||
|
||||
it("accepts a free-service reward with a service chosen", () => {
|
||||
const result = validateProgrammeConfig({ ...base, rewardType: "FREE_SERVICE", freeServiceId: "svc-1" });
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a free-service reward with no service chosen", () => {
|
||||
const result = validateProgrammeConfig({ ...base, rewardType: "FREE_SERVICE", freeServiceId: undefined });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.issues[0]?.code).toBe("FREE_SERVICE_REQUIRED");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user