A wrong URL answered 500, and only the real database said so
/api/admin/clients/not-a-uuid/sites returned 500. `c.id = $1::uuid` makes Postgres cast the path segment, and casting a malformed string - or the empty one the shape check handed back in its place - is an ERROR, not a miss. `c.id::text = $1` cannot fail: an id that is not a uuid matches nothing, which is the 404 a wrong URL should get. The two sibling resolvers were already written this way and correctly 404'd the same input. I applied the rule to two of three places, which is the shape of a rule that holds until somebody adds the next write path. The shape check is gone with it - it existed only to produce the empty string that then broke the cast. The in-memory fake could not have caught this and did not: it resolves a merchant with a map lookup, so every handler test passed, including the one named for the case. That test stays, because 404-not-500 is still the contract, but the property belongs to Postgres - so api_admin_monitor_live_test.go asserts it where it lives, over every free-text identifier these queries take. It skips without TEST_DATABASE_URL, like the rest of the live store tests. Also in deploy.sh, found by reading its own output: step 3 reported the WRONG backup. `ls | tail -1` sorts alphabetically, so pre-...-demo-12 sorts before pre-...-demo-6 and it printed a dump from four days earlier. A deploy that names the wrong safety net is worse than one that names none, because that is the file somebody reaches for at the worst possible moment. It echoes the filename it just wrote, and refuses to continue on an empty one - pipefail catches a failing pg_dump, but a zero-byte gzip would still have satisfied it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
@@ -22,6 +22,15 @@ import (
|
||||
// ClientDetail is one merchant, with the owner a support conversation starts
|
||||
// from. ListClients cannot carry it: an owner lookup per row would be a query
|
||||
// per merchant on a screen that only needs the name.
|
||||
//
|
||||
// `c.id::text = $1`, for the same reason the two resolvers below use it, and
|
||||
// this one learned it the hard way: as `c.id = $1::uuid` it answered 500 to
|
||||
// /api/admin/clients/not-a-uuid/sites on the first real database, because
|
||||
// casting a malformed string - or the empty one a shape check hands back - to
|
||||
// uuid is an ERROR in Postgres rather than a miss. Comparing the column as
|
||||
// text cannot fail: an id that is not a uuid simply matches nothing, which is
|
||||
// the 404 a wrong URL should get. The sibling queries were already written
|
||||
// this way and correctly 404'd; only this one was not.
|
||||
func (s *Store) ClientDetail(ctx context.Context, clientID string) (api.ClientDetail, error) {
|
||||
var c api.ClientDetail
|
||||
var at time.Time
|
||||
@@ -36,7 +45,7 @@ func (s *Store) ClientDetail(ctx context.Context, clientID string) (api.ClientDe
|
||||
WHERE au.client_id = c.id AND au.role = 'owner'
|
||||
AND au.active ORDER BY au.created_at LIMIT 1), '')
|
||||
FROM clients c
|
||||
WHERE c.id = $1::uuid`, clientID).
|
||||
WHERE c.id::text = $1`, clientID).
|
||||
Scan(&c.ID, &c.Slug, &c.Name, &c.Active, &at, &c.Sites, &c.Users,
|
||||
&c.OwnerEmail, &c.OwnerName)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
||||
Reference in New Issue
Block a user