fix(api): GRO-2014 — profile-summary 500 → 404/401/JSON-500 #137

Merged
Flea Flicker merged 2 commits from fix/gro-2014-profile-summary-error-handling into dev 2026-06-01 18:16:30 +00:00

2 Commits

Author SHA1 Message Date
Flea Flicker 986710aa27 fix(test): retype petProfileSummary chain mock to satisfy tsc --project build
CI / Test (pull_request) Successful in 11s
CI / Lint & Typecheck (pull_request) Successful in 17s
CI / Build & Push Docker Images (pull_request) Successful in 1m8s
CI failed on PR #137 because `tsc --project .` (the build path used by the
Docker image) is stricter than `pnpm typecheck` was reporting during local
iteration — two TS2322 errors surfaced in the new mock:

  1. `chain.from = (table: { _name: string }) => ...` was assigned through
     a `Record<string, (...args: unknown[]) => unknown>` index signature,
     and `{ _name: string }` is not assignable from `unknown`.
  2. `chain.then = (onFulfilled?: (v: unknown[]) => unknown) => ...` was
     not assignable to the `PromiseLike<T>.then` signature TS now infers
     for the awaitable, because TS expects `onfulfilled` to also accept
     `null`.

Replace the proxy-based loose chain with a typed `ChainLike` interface so
the build typechecker is satisfied. Behaviour is unchanged — all 7 GRO-2014
regression tests still pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-01 18:14:31 +00:00
Flea Flicker 762d263016 fix(api): GRO-2014 — profile-summary returns 404/401/JSON-500 instead of empty-body 500
CI / Test (pull_request) Successful in 9s
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Build & Push Docker Images (pull_request) Failing after 33s
Defect: GET /api/pets/:id/profile-summary previously returned an empty-body
500 Internal Server Error for any UUID that the caller had no linkage to
(and presumably also for any malformed/non-UUID path param), because the
route had no upfront UUID validation, no defensive staff context guard,
and no router-level onError to catch downstream Drizzle/Postgres errors.

Changes:

- src/routes/pets.ts
  - Add router.onError that returns a JSON envelope (`{"error":"Internal Server
    Error"}`) instead of Hono's default empty-body 500. Mirrors the pattern
    already used in invoices.ts and reports.ts.
  - profile-summary: validate the :id path param with z.string().uuid()
    before hitting Postgres. Malformed UUIDs now return 404 Not Found
    instead of triggering a Postgres uuid cast that throws and bubbles
    up as a 500.
  - profile-summary: explicit `if (!staffRow)` guard returns 401 instead
    of relying on optional chaining and risking a TypeError later in the
    groomer linkage check on staffRow.id.

- src/__tests__/petProfileSummary.test.ts (new)
  - 7 regression tests covering: malformed UUID → 404; missing staff →
    401; pet not found → 404; groomer with no linkage → 403; manager
    happy path → 200; groomer with linkage → 200; downstream DB throw
    → 500 with JSON body (never empty body).

- UAT_PLAYBOOK.md §3 (TC-API-3.29 / 3.30 / 3.31)
  - Document the new 404 behaviour for unknown and malformed UUIDs and
    the JSON-envelope requirement for any 500.

Notes for QA:

- Spec from GRO-2014: 404 if pet does not exist, 403 if no linkage, 401
  if not authenticated. The "Forbidden if no linkage" path was already
  correct for groomers; the 500 → 404/JSON-500 collapse is the actual
  change in observable behaviour.
- The route's customer-as-groomer auto-provision issue (GRO-2013) is
  *not* addressed here. It remains the customer-side defect; this PR
  only fixes the error-handling regression.

Refs: GRO-1892, GRO-2013

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-01 18:10:28 +00:00