Files
graph/engine/query_test.go
T
zeekayandHanzo Dev 9bf7bae609 answer every field a document asks for, minified or not
parseTopFields split top-level fields on NEWLINES, so a one-line document
parsed as a single field — name taken up to the first paren — and every field
after it was dropped with no error. The explore page asks for its bundle,
factory, tokens, pools and day series in one document; minified, it would have
been answered with the bundle alone. A field now ends where its own arguments
and selection set end, which is the same place whether the client pretty-prints
or not.

Also from reading the live wire: a day's token-denominated value locked was
rendered as currency (two decimals reads a small holding as none), and the
token a row points at was a stub — a client selecting through it saw a token
that had never traded.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-11 11:47:49 -07:00

90 lines
3.0 KiB
Go

package engine
import "testing"
// A document is the same document minified. Splitting top-level fields on
// newlines answered a one-line query with its FIRST field and dropped the rest
// without an error — the shape every client library produces when it strips
// whitespace, and the shape of the explore page's whole document.
func names(t *testing.T, query string) []string {
t.Helper()
fs, err := parseTopFields(query)
if err != nil {
t.Fatalf("parse %q: %v", query, err)
}
out := make([]string, 0, len(fs))
for _, f := range fs {
n := f.name
if f.alias != "" {
n = f.alias + ":" + f.name
}
out = append(out, n)
}
return out
}
func TestTopFieldsSurviveMinification(t *testing.T) {
const pretty = `{
bundle(id: "1") { ethPriceUSD }
factories(first: 1) { id poolCount }
tokenDayDatas(where: { date_gte: 1767225600 }, orderBy: date, orderDirection: desc, first: 100) {
token { id }
date
volumeUSD
}
poolDayDatas(first: 90, orderBy: date, orderDirection: desc, where: { pool: "0xabc" }) { date open high low close }
}`
minified := `{bundle(id:"1"){ethPriceUSD} factories(first:1){id poolCount} tokenDayDatas(where:{date_gte:1767225600},orderBy:date,orderDirection:desc,first:100){token{id} date volumeUSD} poolDayDatas(first:90,orderBy:date,orderDirection:desc,where:{pool:"0xabc"}){date open high low close}}`
want := []string{"bundle", "factories", "tokenDayDatas", "poolDayDatas"}
for label, q := range map[string]string{"pretty": pretty, "minified": minified} {
got := names(t, q)
if len(got) != len(want) {
t.Fatalf("%s: parsed %v, want %v", label, got, want)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("%s: field %d = %q, want %q", label, i, got[i], want[i])
}
}
}
// The arguments have to survive the split too, or a chart asks for 90 days
// of one pool and is answered with a default page of everything.
fs, err := parseTopFields(minified)
if err != nil {
t.Fatal(err)
}
limit, orderBy, dir, where := parseListArgs(fs[3].args, 30)
if limit != 90 || orderBy != "date" || dir != "desc" {
t.Errorf("poolDayDatas args = first %d, orderBy %q, %q; want 90, date, desc", limit, orderBy, dir)
}
if where == nil || where["pool"] != "0xabc" {
t.Errorf("poolDayDatas where = %v, want pool 0xabc", where)
}
}
func TestTopFieldsParseAliasesAndBareFields(t *testing.T) {
for _, c := range []struct {
query string
want []string
}{
{`{a:pools(first:1){id} b:pools(first:2){id}}`, []string{"a:pools", "b:pools"}},
{`{bundle{ethPriceUSD} factory{id}}`, []string{"bundle", "factory"}},
{"{ bundles\n factories }", []string{"bundles", "factories"}},
{`{pools(where:{token0:"0x1"}){id token0{id}} swaps(first:1){id}}`, []string{"pools", "swaps"}},
} {
got := names(t, c.query)
if len(got) != len(c.want) {
t.Errorf("%s parsed %v, want %v", c.query, got, c.want)
continue
}
for i := range c.want {
if got[i] != c.want[i] {
t.Errorf("%s: field %d = %q, want %q", c.query, i, got[i], c.want[i])
}
}
}
}