Files
zeekay 604929d3af rest: name the wire, not the vendor
The comments on this door carried another company's brand 72 times — their
product name, their client library, their console. This is our canonical
repo; the surface is ours and reads as ours.

What the prose says now is what the code DOES: a REST data wire with one
table per path and filters in the query string, answering a bare array with
the count in Content-Range. Every fact that made those comments worth having
survives — why a value binds rather than renders, why an unaligned offset is
refused, why neq is `<>` and not the null-safe form — because none of it
depended on whose wire it is.

The wire VALUES stay exactly as they are: PGRST116, PGRST100,
application/vnd.pgrst.object+json, SQLSTATE 23505. Those are not branding,
they are the contract — a client branches on them, and renaming one would
break every caller while changing nothing about who owns the surface.
2026-08-11 12:24:14 -07:00

198 lines
6.6 KiB
Go

package apis
import (
"net/url"
"strings"
"testing"
)
// A wire query becomes Base's own, exactly. These are the shapes the console
// grid actually sends, so a regression here is a broken table view.
//
// Note what is NOT here any more: an assertion that a filter renders to a STRING.
// It used to, and the string it rendered ({:p0} plus a sibling param) was never
// substituted by anything, so every filtered read answered 400 while this test
// stayed green. A predicate is data now, and the test that proves it works is the
// HTTP one that filters a real collection and gets rows back.
func TestRestQueryTranslates(t *testing.T) {
for _, s := range []struct {
name string
in string
counted bool
want map[string]string
}{{
name: "select becomes fields",
in: "select=id,name",
want: map[string]string{"fields": "id,name", "skipTotal": "1"},
}, {
name: "select=* asks for everything, which is the default",
in: "select=*",
want: map[string]string{"skipTotal": "1"},
}, {
name: "a bare order term is ascending",
in: "order=created",
want: map[string]string{"sort": "+created", "skipTotal": "1"},
}, {
name: "descending, and several terms keep their order",
in: "order=created.desc,name.asc",
want: map[string]string{"sort": "-created,+name", "skipTotal": "1"},
}, {
name: "limit and a page-aligned offset become page/perPage",
in: "limit=25&offset=50",
want: map[string]string{"perPage": "25", "page": "3", "skipTotal": "1"},
}, {
name: "asking for a count drops skipTotal",
in: "limit=10",
counted: true,
want: map[string]string{"perPage": "10", "page": "1"},
}, {
// A predicate never reaches the query string at all — it travels as data.
name: "a predicate leaves no trace in the query params",
in: "status=eq.active",
want: map[string]string{"skipTotal": "1"},
}} {
t.Run(s.name, func(t *testing.T) {
in, err := url.ParseQuery(s.in)
if err != nil {
t.Fatalf("bad test input: %v", err)
}
got, _, err := restQuery(in, s.counted)
if err != nil {
t.Fatalf("restQuery(%q) errored: %v", s.in, err)
}
if len(got) != len(s.want) {
t.Fatalf("restQuery(%q) = %v, want %v", s.in, got, s.want)
}
for k, want := range s.want {
if got.Get(k) != want {
t.Errorf("restQuery(%q)[%s] = %q, want %q", s.in, k, got.Get(k), want)
}
}
})
}
}
// The predicates a query carries, as data.
func TestRestPredicates(t *testing.T) {
for _, s := range []struct {
name string
in string
want []restPredicate
}{{
name: "one comparison",
in: "status=eq.active",
want: []restPredicate{{Col: "status", Op: "eq", Vals: []string{"active"}}},
}, {
name: "several, in column order so one request is one expression",
in: "status=eq.active&age=gte.18",
want: []restPredicate{
{Col: "age", Op: "gte", Vals: []string{"18"}},
{Col: "status", Op: "eq", Vals: []string{"active"}},
},
}, {
name: "in carries every value",
in: "id=in.(a,b,c)",
want: []restPredicate{{Col: "id", Op: "in", Vals: []string{"a", "b", "c"}}},
}, {
name: "the wildcard this wire spells with a star becomes SQL's",
in: "name=like.*ace*",
want: []restPredicate{{Col: "name", Op: "like", Vals: []string{"%ace%"}}},
}, {
name: "select and paging are not predicates",
in: "select=id&limit=5&offset=0&order=id",
want: []restPredicate{},
}} {
t.Run(s.name, func(t *testing.T) {
in, _ := url.ParseQuery(s.in)
got, err := restPredicates(in)
if err != nil {
t.Fatalf("restPredicates(%q) errored: %v", s.in, err)
}
if len(got) != len(s.want) {
t.Fatalf("restPredicates(%q) = %+v, want %+v", s.in, got, s.want)
}
for i := range got {
if got[i].Col != s.want[i].Col || got[i].Op != s.want[i].Op ||
strings.Join(got[i].Vals, ",") != strings.Join(s.want[i].Vals, ",") {
t.Errorf("predicate %d = %+v, want %+v", i, got[i], s.want[i])
}
}
})
}
}
// A window or an operator this store cannot honour is REFUSED. Rounding an
// unaligned offset would answer with real rows for the wrong range, which is
// indistinguishable from correct data at the call site.
func TestRestQueryRefusesWhatItCannotHonour(t *testing.T) {
for _, s := range []struct{ name, in, wants string }{
{"offset off a page boundary", "limit=25&offset=30", "page boundary"},
{"offset with no limit", "offset=30", "needs a limit"},
{"a negative limit", "limit=-1", "non-negative"},
{"an unparseable limit", "limit=many", "non-negative"},
{"an unknown operator", "status=matches.x", "unknown operator"},
{"a predicate with no operator", "status=active", "needs an operator"},
{"is with something that is not a literal", "x=is.7", "null, true or false"},
{"an order direction this store cannot express", "order=a.desc.nullsfirst", "asc and desc"},
} {
t.Run(s.name, func(t *testing.T) {
in, _ := url.ParseQuery(s.in)
_, _, err := restQuery(in, false)
if err == nil {
t.Fatalf("restQuery(%q) was accepted; it should be refused", s.in)
}
if !strings.Contains(err.Error(), s.wants) {
t.Errorf("restQuery(%q) said %q, which does not mention %q — the caller has to be able to act on it",
s.in, err.Error(), s.wants)
}
})
}
}
// The predicate order is stable for one request. url.Values iterates randomly,
// so without an explicit order the same query yields a different expression run
// to run, which cannot be reproduced from a log.
func TestRestPredicatesAreStableAcrossRuns(t *testing.T) {
in, _ := url.ParseQuery("b=eq.2&a=eq.1&c=eq.3")
first, err := restPredicates(in)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 50; i++ {
got, err := restPredicates(in)
if err != nil {
t.Fatal(err)
}
for j := range got {
if got[j].Col != first[j].Col {
t.Fatalf("predicate order changed between runs: %v then %v", first, got)
}
}
}
if first[0].Col != "a" {
t.Errorf("predicates do not lead with the first column by name: %+v", first)
}
}
// A caller's value stays a VALUE. It is never rendered into an expression, which
// is what makes the door safe to expose — restWhere binds it as a parameter and
// the column is resolved to an identifier before it can reach SQL.
func TestRestPredicateCarriesTheValueVerbatim(t *testing.T) {
hostile := `x' || 1=1 || '`
in, _ := url.ParseQuery("name=eq." + url.QueryEscape(hostile))
preds, err := restPredicates(in)
if err != nil {
t.Fatal(err)
}
if len(preds) != 1 {
t.Fatalf("want one predicate, got %+v", preds)
}
if preds[0].Vals[0] != hostile {
t.Fatalf("the value should travel verbatim as data, got %q", preds[0].Vals[0])
}
if strings.Contains(preds[0].Col, "1=1") {
t.Fatalf("the value leaked into the column: %q", preds[0].Col)
}
}