Compare commits
2 Commits
uat
...
ff1de7f77d
| Author | SHA1 | Date | |
|---|---|---|---|
| ff1de7f77d | |||
| f6b438d2c7 |
@@ -114,6 +114,7 @@ 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.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.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.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 coat_type enum has all seed values | After UAT seed completes, inspect the coat_type enum on the UAT DB — it must contain: short, medium, long, double, wire, silky, curly, hairless | UAT seed jobs (`reset-demo-data`, `seed-test-data`) complete 1/1 with no `enum_in` error; coat_type includes all 8 values used by seed.ts `coatTypePool` |
|
||||||
|
|
||||||
### 4.4 Appointment Scheduling
|
### 4.4 Appointment Scheduling
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- Migration: 0035_add_missing_coat_type_values.sql
|
||||||
|
-- Adds missing values to coat_type enum that seed.ts requires but which were
|
||||||
|
-- omitted from the 0031_buffer_rules.sql CREATE TYPE statement (migration drift).
|
||||||
|
-- 0031 created: 'smooth', 'double', 'wire', 'curly', 'long', 'hairless'
|
||||||
|
-- Missing (from schema.ts coatTypeEnum): 'short', 'medium', 'silky'
|
||||||
|
|
||||||
|
ALTER TYPE "coat_type" ADD VALUE IF NOT EXISTS 'short';
|
||||||
|
ALTER TYPE "coat_type" ADD VALUE IF NOT EXISTS 'medium';
|
||||||
|
ALTER TYPE "coat_type" ADD VALUE IF NOT EXISTS 'silky';
|
||||||
@@ -246,6 +246,13 @@
|
|||||||
"when": 1751140800000,
|
"when": 1751140800000,
|
||||||
"tag": "0034_extend_pet_profile_columns",
|
"tag": "0034_extend_pet_profile_columns",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1751480000000,
|
||||||
|
"tag": "0035_add_missing_coat_type_values",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+92
-93
@@ -385,78 +385,19 @@ const servicesDef = [
|
|||||||
{ id: "b0000001-0000-0000-0000-00000000000a", name: "Sanitary Trim", desc: "Hygienic trim of paw pads, face, and sanitary areas", price: 2500, dur: 20 },
|
{ id: "b0000001-0000-0000-0000-00000000000a", name: "Sanitary Trim", desc: "Hygienic trim of paw pads, face, and sanitary areas", price: 2500, dur: 20 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ── Known-users-only seed (prod/demo) ───────────────────────────────────────
|
// ── UAT staff account seeding (shared between seed paths) ─────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeds only the minimal known users for prod/demo environments.
|
* Seeds or upserts the deterministic UAT staff accounts with numeric OIDC subs
|
||||||
* Creates: Demo Manager staff + Demo Client + Demo Dog + basic services.
|
* from SEED_UAT_*_OIDC_SUB / SEED_UAT_GROOMER_OIDC_SUBS env vars.
|
||||||
* Idempotent: skips creation if records already exist.
|
*
|
||||||
|
* In the full seed path this must run AFTER random staff are created so the
|
||||||
|
* deterministic upserts land on the correct rows (groomers referenced by the
|
||||||
|
* UAT test-client appointment logic use groomers[0] etc.).
|
||||||
|
*
|
||||||
|
* In seedKnownUsers() this replaces the inline UAT-staff block.
|
||||||
*/
|
*/
|
||||||
async function seedKnownUsers() {
|
async function seedUatStaffAccounts(db: ReturnType<typeof drizzle>) {
|
||||||
const url = process.env.DATABASE_URL;
|
|
||||||
if (!url) {
|
|
||||||
console.error("DATABASE_URL is not set");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = postgres(url, { max: 5 });
|
|
||||||
const db = drizzle(client, { schema });
|
|
||||||
|
|
||||||
console.log("Seeding known users (prod/demo mode)...\n");
|
|
||||||
|
|
||||||
const KNOWN_STAFF_ID = "00000000-0000-0000-0000-000000000001";
|
|
||||||
const DEMO_CLIENT_ID = "00000000-0000-0000-0000-000000000002";
|
|
||||||
const DEMO_PET_ID = "00000000-0000-0000-0000-000000000003";
|
|
||||||
|
|
||||||
// ── Staff: Demo Manager ──
|
|
||||||
const [existingStaff] = await db
|
|
||||||
.select()
|
|
||||||
.from(schema.staff)
|
|
||||||
.where(eq(schema.staff.email, "demo-manager@groombook.dev"))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (existingStaff) {
|
|
||||||
console.log(`✓ Staff '${existingStaff.name}' already exists — skipping`);
|
|
||||||
} else {
|
|
||||||
await db.insert(schema.staff).values({
|
|
||||||
id: KNOWN_STAFF_ID,
|
|
||||||
name: "Demo Manager",
|
|
||||||
email: "demo-manager@groombook.dev",
|
|
||||||
oidcSub: "demo-manager-001",
|
|
||||||
role: "manager",
|
|
||||||
isSuperUser: true,
|
|
||||||
active: true,
|
|
||||||
});
|
|
||||||
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Staff: SEED_ADMIN_EMAIL admin ──
|
|
||||||
const adminEmail = process.env.SEED_ADMIN_EMAIL;
|
|
||||||
if (adminEmail) {
|
|
||||||
const adminName = process.env.SEED_ADMIN_NAME ?? "Admin";
|
|
||||||
const ADMIN_STAFF_ID = "00000000-0000-0000-0000-000000000002";
|
|
||||||
const [existingAdmin] = await db
|
|
||||||
.select()
|
|
||||||
.from(schema.staff)
|
|
||||||
.where(eq(schema.staff.email, adminEmail))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (existingAdmin) {
|
|
||||||
console.log(`✓ Staff admin '${existingAdmin.name}' already exists — skipping`);
|
|
||||||
} else {
|
|
||||||
await db.insert(schema.staff).values({
|
|
||||||
id: ADMIN_STAFF_ID,
|
|
||||||
name: adminName,
|
|
||||||
email: adminEmail,
|
|
||||||
oidcSub: adminEmail,
|
|
||||||
role: "manager",
|
|
||||||
isSuperUser: true,
|
|
||||||
active: true,
|
|
||||||
});
|
|
||||||
console.log(`✓ Created staff admin '${adminName}' (${adminEmail})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Staff: UAT Super User (oidcSub from SEED_UAT_SUPER_OIDC_SUB env var) ──
|
// ── Staff: UAT Super User (oidcSub from SEED_UAT_SUPER_OIDC_SUB env var) ──
|
||||||
const uatSuperOidcSub = process.env.SEED_UAT_SUPER_OIDC_SUB;
|
const uatSuperOidcSub = process.env.SEED_UAT_SUPER_OIDC_SUB;
|
||||||
if (uatSuperOidcSub) {
|
if (uatSuperOidcSub) {
|
||||||
@@ -680,6 +621,84 @@ async function seedKnownUsers() {
|
|||||||
console.log(`✓ Created UAT pet '${pet.name}'`);
|
console.log(`✓ Created UAT pet '${pet.name}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Known-users-only seed (prod/demo) ───────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds only the minimal known users for prod/demo environments.
|
||||||
|
* Creates: Demo Manager staff + Demo Client + Demo Dog + basic services.
|
||||||
|
* Idempotent: skips creation if records already exist.
|
||||||
|
*/
|
||||||
|
async function seedKnownUsers() {
|
||||||
|
const url = process.env.DATABASE_URL;
|
||||||
|
if (!url) {
|
||||||
|
console.error("DATABASE_URL is not set");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = postgres(url, { max: 5 });
|
||||||
|
const db = drizzle(client, { schema });
|
||||||
|
|
||||||
|
console.log("Seeding known users (prod/demo mode)...\n");
|
||||||
|
|
||||||
|
const KNOWN_STAFF_ID = "00000000-0000-0000-0000-000000000001";
|
||||||
|
const DEMO_CLIENT_ID = "00000000-0000-0000-0000-000000000002";
|
||||||
|
const DEMO_PET_ID = "00000000-0000-0000-0000-000000000003";
|
||||||
|
|
||||||
|
// ── Staff: Demo Manager ──
|
||||||
|
const [existingStaff] = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.staff)
|
||||||
|
.where(eq(schema.staff.email, "demo-manager@groombook.dev"))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingStaff) {
|
||||||
|
console.log(`✓ Staff '${existingStaff.name}' already exists — skipping`);
|
||||||
|
} else {
|
||||||
|
await db.insert(schema.staff).values({
|
||||||
|
id: KNOWN_STAFF_ID,
|
||||||
|
name: "Demo Manager",
|
||||||
|
email: "demo-manager@groombook.dev",
|
||||||
|
oidcSub: "demo-manager-001",
|
||||||
|
role: "manager",
|
||||||
|
isSuperUser: true,
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Staff: SEED_ADMIN_EMAIL admin ──
|
||||||
|
const adminEmail = process.env.SEED_ADMIN_EMAIL;
|
||||||
|
if (adminEmail) {
|
||||||
|
const adminName = process.env.SEED_ADMIN_NAME ?? "Admin";
|
||||||
|
const ADMIN_STAFF_ID = "00000000-0000-0000-0000-000000000002";
|
||||||
|
const [existingAdmin] = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.staff)
|
||||||
|
.where(eq(schema.staff.email, adminEmail))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingAdmin) {
|
||||||
|
console.log(`✓ Staff admin '${existingAdmin.name}' already exists — skipping`);
|
||||||
|
} else {
|
||||||
|
await db.insert(schema.staff).values({
|
||||||
|
id: ADMIN_STAFF_ID,
|
||||||
|
name: adminName,
|
||||||
|
email: adminEmail,
|
||||||
|
oidcSub: adminEmail,
|
||||||
|
role: "manager",
|
||||||
|
isSuperUser: true,
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
console.log(`✓ Created staff admin '${adminName}' (${adminEmail})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UAT staff accounts + Better Auth credentials (shared impl) ──────────────
|
||||||
|
// Extracted into seedUatStaffAccounts() so it runs in both seedKnownUsers()
|
||||||
|
// and the full seed() UAT branch.
|
||||||
|
await seedUatStaffAccounts(db);
|
||||||
|
|
||||||
// ── Services: idempotent upsert using name as unique key ─────────────────────
|
// ── Services: idempotent upsert using name as unique key ─────────────────────
|
||||||
// UNIQUE constraint on services.name (migration 0020) must exist first.
|
// UNIQUE constraint on services.name (migration 0020) must exist first.
|
||||||
@@ -847,30 +866,10 @@ async function seed() {
|
|||||||
console.log(`✓ Upserted admin staff '${adminName}' (${adminEmail})`);
|
console.log(`✓ Upserted admin staff '${adminName}' (${adminEmail})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── UAT Groomer Personas (SEED_UAT_GROOMER_EMAILS + SEED_UAT_GROOMER_NAMES) ──
|
// ── UAT staff accounts + Better Auth credentials (shared impl) ──────────────
|
||||||
const groomerEmails = process.env.SEED_UAT_GROOMER_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) ?? [];
|
// Seeds deterministic UAT staff with numeric OIDC subs and Better Auth credentials.
|
||||||
const groomerNames = process.env.SEED_UAT_GROOMER_NAMES?.split(",").map((n) => n.trim()).filter(Boolean) ?? [];
|
// Must run AFTER random staff are created so upserts land correctly.
|
||||||
const groomerCount = Math.min(groomerEmails.length, groomerNames.length);
|
await seedUatStaffAccounts(db);
|
||||||
for (let i = 0; i < groomerCount; i++) {
|
|
||||||
const email = groomerEmails[i]!;
|
|
||||||
const name = groomerNames[i]!;
|
|
||||||
const staffId = `00000000-0000-0000-0000-${String(5 + i).padStart(12, "0")}`;
|
|
||||||
await db.insert(schema.staff)
|
|
||||||
.values({
|
|
||||||
id: staffId,
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
oidcSub: email,
|
|
||||||
role: "groomer",
|
|
||||||
isSuperUser: false,
|
|
||||||
active: true,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: schema.staff.email,
|
|
||||||
set: { id: staffId, name, role: "groomer", isSuperUser: false, active: true },
|
|
||||||
});
|
|
||||||
console.log(`✓ Upserted groomer '${name}' (${email})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Services ──
|
// ── Services ──
|
||||||
// Upsert services using name as unique key. With deterministic IDs in
|
// Upsert services using name as unique key. With deterministic IDs in
|
||||||
|
|||||||
Reference in New Issue
Block a user