fix(portal): GRO-2203 validate petId as UUID before PATCH lookup (500→404) (#177)
CI / Lint & Typecheck (push) Successful in 29s
CI / Test (push) Successful in 29s
CI / Lint & Typecheck (pull_request) Failing after 2s
CI / Test (pull_request) Successful in 25s
CI / Build & Push Docker Images (pull_request) Has been skipped
CI / Build & Push Docker Images (push) Successful in 47s

This commit was merged in pull request #177.
This commit is contained in:
2026-06-08 17:03:44 +00:00
parent d0c0b1b646
commit b842237425
3 changed files with 26 additions and 0 deletions
+1
View File
@@ -281,6 +281,7 @@ This means:
| TC-API-8.13 | Portal pet update — owner success + persistence (GRO-2187, fixes [GRO-1480](/GRO/issues/GRO-1480) §5.23) | With a portal session for the pet's owner, `PATCH /api/portal/pets/{petId}` with body `{ "name": "...", "breed": "...", "weightKg": 18.25, "healthAlerts": "...", "coatType": "double", "petSizeCategory": "xlarge", "preferredCuts": ["teddy bear"], "medicalAlerts": [{"type":"allergy","description":"oatmeal","severity":"medium"}] }` | 200 OK; response reflects the update with `petSizeCategory: "extra_large"` (web `xlarge` → DB `extra_large`). A follow-up `GET /api/portal/pets` shows the persisted values |
| TC-API-8.14 | Portal pet update — non-owner blocked (GRO-2187) | `PATCH /api/portal/pets/{petId}` for a pet owned by a different client, using another client's portal session | 403 Forbidden (or 404 if pet id is unknown); no mutation persisted |
| TC-API-8.15 | Portal pet update — invalid enum rejected (GRO-2187) | `PATCH /api/portal/pets/{petId}` with `coatType: "fluffy"` or `petSizeCategory: "gigantic"` | 422 Unprocessable Entity; pet unchanged |
| TC-API-8.16 | Portal pet update — malformed (non-UUID) petId returns 404 (GRO-2203) | With a valid portal session, `PATCH /api/portal/pets/not-a-uuid` with header `X-Impersonation-Session-Id` and body `{"coatType":"short"}` | 404 Not Found with body `{"error":"Not found"}` (was an unhandled 500 from the Postgres uuid cast in GRO-2203; mirrors the GRO-2014 guard). No mutation persisted |
### 4.9 Waitlist
+17
View File
@@ -280,6 +280,23 @@ describe("PATCH /portal/pets/:petId", () => {
expect(res.status).toBe(404);
});
it("returns 404 for a malformed (non-UUID) petId without hitting the db (GRO-2203)", async () => {
selectSessionRow = ACTIVE_SESSION;
// A non-UUID petId previously reached `where(eq(pets.id, ...))` and made
// Postgres throw "invalid input syntax for type uuid" → unhandled 500.
// It must now short-circuit to 404 before any select/update.
selectPetRow = PET;
const res = await jsonPatch(
`/portal/pets/not-a-uuid`,
{ coatType: "short" },
{ "X-Impersonation-Session-Id": SESSION_ID }
);
expect(res.status).toBe(404);
expect(updatedValues).toHaveLength(0);
});
it("returns 422 for an invalid coatType", async () => {
selectSessionRow = ACTIVE_SESSION;
selectPetRow = PET;
+8
View File
@@ -296,6 +296,14 @@ portalRouter.patch(
const body = c.req.valid("json");
const clientId = c.get("portalClientId");
// GRO-2203: validate UUID format before hitting Postgres. Passing a non-UUID
// string to a uuid column makes the driver throw ("invalid input syntax for
// type uuid"), which previously surfaced as an unhandled 500. Mirror the
// GRO-2014 fix in pets.ts and treat a malformed id as Not found.
if (!z.string().uuid().safeParse(petId).success) {
return c.json({ error: "Not found" }, 404);
}
const [pet] = await db
.select()
.from(pets)