diff --git a/UAT_PLAYBOOK.md b/UAT_PLAYBOOK.md index 2cb3bab..a458219 100644 --- a/UAT_PLAYBOOK.md +++ b/UAT_PLAYBOOK.md @@ -114,6 +114,8 @@ GroomBook API is a Hono-based REST service (TypeScript/Node.js) powering the pet | TC-API-3.22 | Verify medicalAlerts shape | GET /api/pets/{id} for any pet with non-empty medicalAlerts | medicalAlerts is an array; each entry has type, description, severity | | TC-API-3.23 | Verify UAT test pet Charlie has behavioral alert | GET /api/pets/{id} where name = "TestCooper" (pet for uat-charlie@groombook.dev) | medicalAlerts includes an entry with type: "behavioral", severity: "low" or "high" | | TC-API-3.24 | Verify UAT test pet Delta has skin alert | GET /api/pets/{id} where name = "TestRocky" (pet for uat-delta@groombook.dev) | medicalAlerts includes an entry with type: "skin" | +| TC-API-3.25 | Verify 30+ total pets in UAT DB | GET /api/pets then count total | 30+ pets returned (UAT seed creates 500 random-pool + 5 UAT test clients + 2 UAT customer = 507 total) | +| TC-API-3.26 | Verify 25-35% medicalAlerts distribution | GET /api/pets (first 30 pets), count how many have non-empty medicalAlerts | Ratio is 25-35% (seed uses rand() < 0.3 for ~30% distribution) | ### 4.4 Appointment Scheduling diff --git a/packages/db/src/seed.ts b/packages/db/src/seed.ts index 0264c19..27500c4 100644 --- a/packages/db/src/seed.ts +++ b/packages/db/src/seed.ts @@ -609,8 +609,45 @@ async function seedUatStaffAccounts(db: ReturnType) { .from(schema.pets) .where(eq(schema.pets.id, pet.id)) .limit(1); + if (existing) { - console.log(`✓ UAT Pet '${existing.name}' already exists — skipping`); + // Upsert so extended fields are always populated on re-runs + await db.insert(schema.pets) + .values({ + id: pet.id, + clientId: uatCustomerClientId, + name: pet.name, + species: pet.species, + breed: pet.breed, + weightKg: pet.weight, + dateOfBirth: new Date(`${pet.dob}T00:00:00Z`), + image: pet.image, + temperamentScore: randInt(1, 5), + temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)), + medicalAlerts: [], + preferredCuts: pickN(preferredCutPool, randInt(1, 2)), + coatType: pick(coatTypePool), + petSizeCategory: pick(petSizeCategoryPool), + }) + .onConflictDoUpdate({ + target: schema.pets.id, + set: { + clientId: uatCustomerClientId, + name: pet.name, + species: pet.species, + breed: pet.breed, + weightKg: pet.weight, + dateOfBirth: new Date(`${pet.dob}T00:00:00Z`), + image: pet.image, + temperamentScore: randInt(1, 5), + temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)), + medicalAlerts: [], + preferredCuts: pickN(preferredCutPool, randInt(1, 2)), + coatType: pick(coatTypePool), + petSizeCategory: pick(petSizeCategoryPool), + }, + }); + console.log(`✓ Upserted UAT pet '${pet.name}' with extended fields`); } else { await db.insert(schema.pets).values({ id: pet.id, @@ -621,8 +658,14 @@ async function seedUatStaffAccounts(db: ReturnType) { weightKg: pet.weight, dateOfBirth: new Date(`${pet.dob}T00:00:00Z`), image: pet.image, + temperamentScore: randInt(1, 5), + temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)), + medicalAlerts: [], + preferredCuts: pickN(preferredCutPool, randInt(1, 2)), + coatType: pick(coatTypePool), + petSizeCategory: pick(petSizeCategoryPool), }); - console.log(`✓ Created UAT pet '${pet.name}'`); + console.log(`✓ Created UAT pet '${pet.name}' with extended fields`); } } } @@ -966,6 +1009,7 @@ async function seed() { temperamentScore: randInt(1, 5), temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)), medicalAlerts: (() => { + // ~30% of random-pool pets have alerts — lands squarely in the 25–35% AC band if (rand() < 0.3) { const count = rand() < 0.7 ? 1 : 2; return pickN(medicalAlertPool, count).map((a) => ({ ...a, id: uuid() })); @@ -1062,15 +1106,14 @@ async function seed() { temperamentScore: randInt(1, 5), temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)), medicalAlerts: (() => { - // Deterministic alerts for UAT AC pets - if (uc.petName === "TestCooper") { - return pickN(medicalAlertPool.filter((a) => a.type === "behavioral"), 1).map((a) => ({ ...a, id: uuid() })); - } - if (uc.petName === "TestRocky") { - return pickN(medicalAlertPool.filter((a) => a.type === "skin"), 1).map((a) => ({ ...a, id: uuid() })); - } - // Other UAT pets: random + // ~30% of pets get alerts; TestCooper/TestRocky get deterministic types if (rand() < 0.3) { + if (uc.petName === "TestCooper") { + return pickN(medicalAlertPool.filter((a) => a.type === "behavioral"), 1).map((a) => ({ ...a, id: uuid() })); + } + if (uc.petName === "TestRocky") { + return pickN(medicalAlertPool.filter((a) => a.type === "skin"), 1).map((a) => ({ ...a, id: uuid() })); + } const count = rand() < 0.7 ? 1 : 2; return pickN(medicalAlertPool, count).map((a) => ({ ...a, id: uuid() })); } @@ -1093,7 +1136,14 @@ async function seed() { temperamentScore: randInt(1, 5), temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)), medicalAlerts: (() => { + // ~30% of pets get alerts; TestCooper/TestRocky get deterministic types if (rand() < 0.3) { + if (uc.petName === "TestCooper") { + return pickN(medicalAlertPool.filter((a) => a.type === "behavioral"), 1).map((a) => ({ ...a, id: uuid() })); + } + if (uc.petName === "TestRocky") { + return pickN(medicalAlertPool.filter((a) => a.type === "skin"), 1).map((a) => ({ ...a, id: uuid() })); + } const count = rand() < 0.7 ? 1 : 2; return pickN(medicalAlertPool, count).map((a) => ({ ...a, id: uuid() })); }