diff --git a/UAT_PLAYBOOK.md b/UAT_PLAYBOOK.md index c6e10f8..42a0b07 100644 --- a/UAT_PLAYBOOK.md +++ b/UAT_PLAYBOOK.md @@ -61,6 +61,14 @@ GroomBook API is a Hono-based REST service (TypeScript/Node.js) powering the pet | TC-API-3.5 | Delete pet | DELETE /api/pets/{id} | 200 OK, pet deleted | | TC-API-3.6 | Upload pet photo | POST /api/pets/{id}/photo/upload-url, then confirm | 200 OK, photo uploaded and key stored | | TC-API-3.7 | View pet photo | GET /api/pets/{id}/photo | 200 OK, presigned URL returned | +| TC-API-3.8 | Create pet with extended fields | POST /api/pets with coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts | 201 Created, all extended fields stored and returned | +| TC-API-3.9 | Update pet extended fields | PATCH /api/pets/{id} with coatType, temperamentScore, medicalAlerts | 200 OK, extended fields updated | +| TC-API-3.10 | Reject invalid coatType | POST /api/pets with coatType: "smooth" | 400 Bad Request, invalid coatType rejected | +| TC-API-3.11 | Reject out-of-range temperamentScore | POST /api/pets with temperamentScore: 0 or 6 | 400 Bad Request, score out of range rejected | +| TC-API-3.12 | Reject invalid medicalAlert severity | POST /api/pets with medicalAlerts severity: "critical" | 400 Bad Request, invalid severity rejected | +| TC-API-3.13 | Reject too many temperamentFlags | POST /api/pets with 21 temperamentFlags | 400 Bad Request, max 20 flags enforced | +| TC-API-3.14 | Reject too many preferredCuts | POST /api/pets with 21 preferredCuts | 400 Bad Request, max 20 cuts enforced | +| TC-API-3.15 | Reject too many medicalAlerts | POST /api/pets with 51 medicalAlerts | 400 Bad Request, max 50 alerts enforced | ### 4.4 Appointment Scheduling diff --git a/apps/api/src/__tests__/petsExtendedFields.test.ts b/apps/api/src/__tests__/petsExtendedFields.test.ts index 6c19818..a1c64a8 100644 --- a/apps/api/src/__tests__/petsExtendedFields.test.ts +++ b/apps/api/src/__tests__/petsExtendedFields.test.ts @@ -145,7 +145,8 @@ function makeDeleteChainable(): unknown { return chain; } -vi.mock("../db", () => { +vi.mock("../db", async (importOriginal) => { + const db = await importOriginal(); const pets = new Proxy({ _name: "pets" }, { get: (t, p) => p === "_name" ? "pets" : {} }); const appointments = new Proxy({ _name: "appointments" }, { get: (t, p) => p === "_name" ? "appointments" : {} }); return { @@ -163,7 +164,7 @@ vi.mock("../db", () => { }), pets, appointments, - and: (...conds: unknown[]) => conds, +and: (...conds: unknown[]) => conds, eq: (col: unknown, val: unknown) => ({ col, val }), exists: (q: unknown) => q, or: (...conds: unknown[]) => conds, @@ -322,11 +323,11 @@ describe("Extended pet profile fields — update", () => { const res = await app.request(`/pets/${PET_ID}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ coatType: "smooth" }), + body: JSON.stringify({ coatType: "double" }), }); expect(res.status).toBe(200); const body = await res.json(); - expect(body.coatType).toBe("smooth"); + expect(body.coatType).toBe("double"); }); it("updates temperamentScore", async () => { diff --git a/apps/api/src/db/factories.ts b/apps/api/src/db/factories.ts index 9f801e2..da36fe6 100644 --- a/apps/api/src/db/factories.ts +++ b/apps/api/src/db/factories.ts @@ -103,6 +103,11 @@ export function buildPet(overrides: Partial & { clientId: string }): Pet photoKey: null, photoUploadedAt: null, image: null, + coatType: null, + temperamentScore: null, + temperamentFlags: [], + medicalAlerts: [], + preferredCuts: [], createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), }; diff --git a/apps/api/src/routes/pets.ts b/apps/api/src/routes/pets.ts index 379d2be..2ac78fd 100644 --- a/apps/api/src/routes/pets.ts +++ b/apps/api/src/routes/pets.ts @@ -24,7 +24,7 @@ const createPetSchema = z.object({ shampooPreference: z.string().max(500).optional(), specialCareNotes: z.string().max(2000).optional(), customFields: z.record(z.string(), z.string()).optional(), - coatType: z.string().max(100).optional(), + coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(), temperamentScore: z.number().int().min(1).max(5).optional(), temperamentFlags: z.array(z.string().max(100)).max(20).optional(), medicalAlerts: z.array(z.object({