/** * Test factories — build typed in-memory entities for unit tests. * * Each factory returns a fully-populated object with valid defaults. * Pass an overrides object to customise specific fields. * * IDs are generated with a deterministic counter so tests produce stable, * readable values (e.g. "staff-1", "client-2") without needing crypto. * * Usage: * import { buildStaff, buildClient, buildPet } from "@groombook/db/factories"; * * const manager = buildStaff({ role: "manager" }); * const client = buildClient({ name: "Alice Smith" }); * const pet = buildPet({ clientId: client.id }); */ import type { staff, clients, pets, services, appointments } from "./schema.js"; // ── Counter-based ID factory ───────────────────────────────────────────────── const counters: Record = {}; function nextId(prefix: string): string { counters[prefix] = (counters[prefix] ?? 0) + 1; return `${prefix}-${counters[prefix]}`; } /** Reset all counters. Call in beforeEach() to keep tests independent. */ export function resetFactoryCounters(): void { for (const key of Object.keys(counters)) { delete counters[key]; } } // ── Type aliases ───────────────────────────────────────────────────────────── export type StaffRow = typeof staff.$inferSelect; export type ClientRow = typeof clients.$inferSelect; export type PetRow = typeof pets.$inferSelect; export type ServiceRow = typeof services.$inferSelect; export type AppointmentRow = typeof appointments.$inferSelect; // ── Factories ──────────────────────────────────────────────────────────────── export function buildStaff(overrides: Partial = {}): StaffRow { const id = nextId("staff"); return { id, name: `Staff Member ${id}`, email: `${id}@groombook.test`, oidcSub: `oidc-${id}`, userId: null, role: "groomer", isSuperUser: false, active: true, icalToken: null, createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), ...overrides, }; } export function buildClient(overrides: Partial = {}): ClientRow { const id = nextId("client"); return { id, name: `Client ${id}`, email: `${id}@example.com`, phone: "555-0100", address: "1 Main St, Springfield, CA 90000", notes: null, emailOptOut: false, smsOptIn: false, smsConsentDate: null, smsOptOutDate: null, smsConsentText: null, stripeCustomerId: null, status: "active", disabledAt: null, latitude: null, longitude: null, geocodedAt: null, createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), ...overrides, }; } export function buildPet(overrides: Partial & { clientId: string }): PetRow { const id = nextId("pet"); const defaults: PetRow = { id, clientId: overrides.clientId, name: `Pet ${id}`, species: "Dog", breed: "Mixed Breed", weightKg: "15.00", dateOfBirth: new Date("2020-06-15T00:00:00Z"), healthAlerts: null, groomingNotes: null, cutStyle: null, shampooPreference: null, specialCareNotes: null, coatType: null, petSizeCategory: null, customFields: {}, photoKey: null, photoUploadedAt: null, image: null, temperamentScore: null, temperamentFlags: [], medicalAlerts: [], preferredCuts: [], createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), }; return { ...defaults, ...overrides }; } export function buildService(overrides: Partial = {}): ServiceRow { const id = nextId("service"); return { id, name: `Service ${id}`, description: "A grooming service", basePriceCents: 6500, durationMinutes: 60, defaultBufferMinutes: 0, active: true, createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), ...overrides, }; } export function buildAppointment( overrides: Partial & { clientId: string; petId: string; serviceId: string; staffId: string } ): AppointmentRow { const id = nextId("appointment"); const startTime = new Date("2025-06-01T10:00:00Z"); const endTime = new Date("2025-06-01T11:00:00Z"); const defaults: AppointmentRow = { id, clientId: overrides.clientId, petId: overrides.petId, serviceId: overrides.serviceId, staffId: overrides.staffId, batherStaffId: null, seriesId: null, seriesIndex: null, groupId: null, status: "scheduled", startTime, endTime, notes: null, priceCents: null, confirmationStatus: "pending", confirmedAt: null, cancelledAt: null, confirmationToken: null, customerNotes: null, createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), }; return { ...defaults, ...overrides }; }