Compare commits

..

3 Commits

Author SHA1 Message Date
Chris Farhood c1d28635ba fix(test): fix petsExtendedFields vi.mock hoisting and invalid UUIDs
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Test (pull_request) Successful in 21s
CI / Build & Push Docker Image (pull_request) Successful in 27s
Two pre-existing bugs prevented petsExtendedFields.test.ts from running:

1. vi.mock factory referenced bare `and`, `eq`, `exists`, `or` variables
   that are undefined at hoist time — replaced with inline mock functions
2. CLIENT_ID/PET_ID used non-UUID strings but Zod schema requires uuid()

All 36 test files (521 tests) now pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 05:28:39 +00:00
The Dogfather 51b45b529d Merge pull request 'fix(GRO-1395): add drizzle-orm and postgres to root package.json' (#29) from fix/gro-1395-drizzle-orm-root-dep into dev
CI / Lint & Typecheck (push) Successful in 15s
CI / Test (push) Failing after 20s
CI / Build & Push Docker Image (push) Has been skipped
fix(GRO-1395): add drizzle-orm and postgres to root package.json (#29)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 04:05:32 +00:00
Flea Flicker 4204bea2b3 fix(GRO-1395): add drizzle-orm and postgres to root package.json
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Test (pull_request) Failing after 20s
CI / Build & Push Docker Image (pull_request) Has been skipped
Add drizzle-orm ^0.38.4 and postgres ^3.4.5 to root package.json
dependencies so that pnpm --frozen-lockfile install in CI makes
them available at the root node_modules level.

apps/api is not a pnpm workspace member (workspace declares
packages: ["apps/*"]), so CI's pnpm install does not reach into
apps/api/node_modules/. Adding these deps to the root package.json
fixes the ERR_MODULE_NOT_FOUND error that Vitest encountered when
running tests under pnpm --frozen-lockfile.

Also moves drizzle-orm and postgres from apps/api/devDependencies
to dependencies per the issue spec.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 03:19:25 +00:00
6 changed files with 25 additions and 28 deletions
-8
View File
@@ -57,14 +57,6 @@ 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
@@ -21,8 +21,8 @@ const MANAGER: StaffRow = {
// ─── Mutable mock state ───────────────────────────────────────────────────────
const CLIENT_ID = "client-uuid-extended";
const PET_ID = "pet-uuid-extended";
const CLIENT_ID = "a0000000-0000-4000-8000-000000000001";
const PET_ID = "b0000000-0000-4000-8000-000000000002";
let petRows: Record<string, unknown>[] = [];
let appointmentRows: Record<string, unknown>[] = [];
@@ -145,8 +145,7 @@ function makeDeleteChainable(): unknown {
return chain;
}
vi.mock("../db", async (importOriginal) => {
const db = await importOriginal<typeof import("../db/index.js")>();
vi.mock("../db", () => {
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 {
@@ -164,10 +163,10 @@ vi.mock("../db", async (importOriginal) => {
}),
pets,
appointments,
and: db.and,
eq: db.eq,
exists: db.exists,
or: db.or,
and: (...conds: unknown[]) => conds,
eq: (col: unknown, val: unknown) => ({ col, val }),
exists: (q: unknown) => q,
or: (...conds: unknown[]) => conds,
};
});
@@ -323,11 +322,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: "double" }),
body: JSON.stringify({ coatType: "smooth" }),
});
expect(res.status).toBe(200);
const body = await res.json();
expect(body.coatType).toBe("double");
expect(body.coatType).toBe("smooth");
});
it("updates temperamentScore", async () => {
-5
View File
@@ -103,11 +103,6 @@ export function buildPet(overrides: Partial<PetRow> & { 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"),
};
+1 -1
View File
@@ -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.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
coatType: z.string().max(100).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({
+2
View File
@@ -19,9 +19,11 @@
"@hono/node-server": "^1.13.7",
"@hono/zod-validator": "^0.7.6",
"better-auth": "^1.5.6",
"drizzle-orm": "^0.38.4",
"hono": "^4.6.17",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.16",
"postgres": "^3.4.5",
"stripe": "^22.0.0",
"telnyx": "^1.23.0",
"zod": "^4.3.6"
+13 -4
View File
@@ -28,7 +28,10 @@ importers:
version: 0.7.6(hono@4.12.18)(zod@4.4.3)
better-auth:
specifier: ^1.5.6
version: 1.6.10(vitest@3.2.4(@types/node@22.19.18)(tsx@4.21.0))
version: 1.6.10(drizzle-orm@0.38.4(kysely@0.28.17)(postgres@3.4.9))(vitest@3.2.4(@types/node@22.19.18)(tsx@4.21.0))
drizzle-orm:
specifier: ^0.38.4
version: 0.38.4(kysely@0.28.17)(postgres@3.4.9)
hono:
specifier: ^4.6.17
version: 4.12.18
@@ -38,6 +41,9 @@ importers:
nodemailer:
specifier: ^6.9.16
version: 6.10.1
postgres:
specifier: ^3.4.5
version: 3.4.9
stripe:
specifier: ^22.0.0
version: 22.1.1(@types/node@22.19.18)
@@ -2924,10 +2930,12 @@ snapshots:
nanostores: 1.3.0
zod: 4.4.3
'@better-auth/drizzle-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)':
'@better-auth/drizzle-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.38.4(kysely@0.28.17)(postgres@3.4.9))':
dependencies:
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
'@better-auth/utils': 0.4.0
optionalDependencies:
drizzle-orm: 0.38.4(kysely@0.28.17)(postgres@3.4.9)
'@better-auth/kysely-adapter@1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)':
dependencies:
@@ -3919,10 +3927,10 @@ snapshots:
balanced-match@4.0.4: {}
better-auth@1.6.10(vitest@3.2.4(@types/node@22.19.18)(tsx@4.21.0)):
better-auth@1.6.10(drizzle-orm@0.38.4(kysely@0.28.17)(postgres@3.4.9))(vitest@3.2.4(@types/node@22.19.18)(tsx@4.21.0)):
dependencies:
'@better-auth/core': 1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)
'@better-auth/drizzle-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
'@better-auth/drizzle-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.38.4(kysely@0.28.17)(postgres@3.4.9))
'@better-auth/kysely-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)(kysely@0.28.17)
'@better-auth/memory-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
'@better-auth/mongo-adapter': 1.6.10(@better-auth/core@1.6.10(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.0)
@@ -3939,6 +3947,7 @@ snapshots:
nanostores: 1.3.0
zod: 4.4.3
optionalDependencies:
drizzle-orm: 0.38.4(kysely@0.28.17)(postgres@3.4.9)
vitest: 3.2.4(@types/node@22.19.18)(tsx@4.21.0)
transitivePeerDependencies:
- '@cloudflare/workers-types'