Three uuids on one arrival, three different answers
Asked of the row the feed actually returns. site_id had a reference all along and the feed was not sending it. A client could read the shop's NAME off an arrival and still had no way to ask for that shop except by uuid - the exact gap the reference scheme exists to close. site_slug now travels with it. visit_id stays a uuid and needs no reference: no route takes it, it is a key a client de-duplicates on because delivery is at-least-once, and nobody says a visit id out loud. The uuid in a face URL must STAY random. visit_faces.id is gen_random_uuid() and a derived or sequential one would let somebody walk a shop's customers by date - the same reason bucket keys are random rather than derived from the event id. A readable identifier is right for a customer and wrong for the thing that points at their photograph. And seq is now json:"-". visits.seq is a plain bigserial, so it counts every visit on the PLATFORM, and shipping it put the total footfall of every customer we have on every row of every tenant's feed - the same German-tank estimate that decided visitors.number had to be per client. It was a convenience for "have I fallen behind", nothing ever read it, and the cursor answers that without disclosing a number. The SSE event id was never the raw value; it has always been the opaque cursor. The one test that broke was reading seq back off the wire to assert the cursor pointed at the last row of a burst. It asserts against the seeded position now: the property is unchanged, and the test can no longer see what a client cannot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
This commit is contained in:
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -108,3 +109,34 @@ func TestBothSiteParameterNamesAreAccepted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An arrival names its shop three ways, and the one a client can filter by must
|
||||
// be among them. Reading "TeNext Chennai" off a row and then having no way to
|
||||
// ask for that shop except by uuid is the exact gap the reference scheme
|
||||
// exists to close.
|
||||
func TestAnArrivalCarriesTheShopReferenceItCanBeFilteredBy(t *testing.T) {
|
||||
srv, fs := newServer(t)
|
||||
seedUser(fs)
|
||||
fs.arrivals = []Arrival{{
|
||||
VisitID: "c64dc53f-c7f0-4e61-a3b4-9a230f52b3a3", Seq: 265,
|
||||
SiteID: "7c9bb456-e0c6-436d-ba01-5d6cc4e3f466",
|
||||
Site: "TeNext Chennai", SiteSlug: "chennai",
|
||||
}}
|
||||
tok := login(t, srv, "manager@acme.com", "correct horse battery").Token
|
||||
|
||||
rec := do(t, srv, http.MethodGet, "/api/visits", tok, nil)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, `"site_slug":"chennai"`) {
|
||||
t.Fatalf("an arrival must carry the shop's reference: %s", body)
|
||||
}
|
||||
|
||||
// seq is a plain bigserial, so it counts every visit on the PLATFORM. It
|
||||
// must not travel: that is the total footfall of every customer we have,
|
||||
// on every row of every tenant's feed.
|
||||
if strings.Contains(body, `"seq"`) {
|
||||
t.Fatalf("the platform-wide visit counter leaked into the feed: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user