package api import ( "net/http" "strings" "testing" ) // A platform admin has no client, and every tenant query scopes on one. Before // this guard the empty string reached Postgres as `client_id = ”::uuid`, // which is a cast ERROR and not an empty result - so five live endpoints // answered 500 to a signed-in platform admin. Found by calling them, not by a // test: the in-memory fake compares strings and is perfectly happy with "". func TestATenantRouteRefusesAnAccountWithNoCompany(t *testing.T) { s, fs := newServer(t) seedPlatformAdmin(fs) sess := login(t, s, "root@loyaly.ai", "admin123") for _, path := range []string{ "/api/visits", "/api/cameras", "/api/sites", "/api/visitors", "/api/reports/footfall", "/api/sales", "/api/dashboard/summary", } { rec := do(t, s, "GET", path, sess.Token, nil) if rec.Code != http.StatusForbidden { t.Errorf("%s: got %d, want 403 - a platform admin reads a company's "+ "data through /api/admin, and a 500 here reads as a broken "+ "server rather than a wrong door", path, rec.Code) } // The message has to say where to go instead; "forbidden" alone sends // somebody hunting a permissions problem that does not exist. if !strings.Contains(rec.Body.String(), "/api/admin") { t.Errorf("%s: refusal should point at the admin routes: %s", path, rec.Body.String()) } } } // The guard must not lock a platform admin out of their own session, which is // not a company's data and is how they sign out or revoke a lost device. func TestAPlatformAdminKeepsTheirOwnSessionRoutes(t *testing.T) { s, fs := newServer(t) seedPlatformAdmin(fs) sess := login(t, s, "root@loyaly.ai", "admin123") for _, path := range []string{"/api/auth/me", "/api/auth/sessions"} { if rec := do(t, s, "GET", path, sess.Token, nil); rec.Code != http.StatusOK { t.Errorf("%s: got %d, want 200", path, rec.Code) } } } // And the ordinary case must be untouched: a tenant user still reaches // everything they always did. func TestATenantUserIsUnaffectedByTheGuard(t *testing.T) { s, fs := newServer(t) seedUser(fs) sess := login(t, s, "manager@acme.com", "correct horse battery") for _, path := range []string{ "/api/visits", "/api/cameras", "/api/sites", "/api/visitors", "/api/sales", "/api/dashboard/summary", } { if rec := do(t, s, "GET", path, sess.Token, nil); rec.Code != http.StatusOK { t.Errorf("%s: got %d, want 200 for an ordinary tenant account: %s", path, rec.Code, rec.Body.String()) } } }