Files
graph/engine/topfields_test.go
zeekayandHanzo Dev 80a4098363 Let a page ask for as many tables as it needs
The Explore transactions view requests 23 tables in one query and got
"too many top-level fields (23), maximum 20" — the whole tab died on it.

20 was a bare literal at the check while every neighbouring limit lives on
Config. Asking for several tables at once is ordinary, not abusive: the cap
exists to bound work per request, so it belongs with the other limits and
starts high enough for real pages. A zero value means unset, not zero
allowed, so configs written before this field keep working.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-11 10:56:23 -07:00

71 lines
2.3 KiB
Go

package engine
import (
"context"
"fmt"
"strings"
"testing"
"github.com/luxfi/graph/storage"
)
// The Explore transactions view asks for 23 tables in one request and got back
// "too many top-level fields (23), maximum 20" — the page died on a limit that
// was a bare literal while every neighbouring limit was configurable.
func query(n int) string {
// One field per line, the shape Apollo actually sends — parseTopFields
// splits on newlines, so a single-line query reads as one field.
var b strings.Builder
b.WriteString("{\n")
for i := 0; i < n; i++ {
fmt.Fprintf(&b, " f%d { id }\n", i)
}
b.WriteString("}")
return b.String()
}
func newTestEngine(t *testing.T, cfg *Config, fields int) *Engine {
t.Helper()
e := New(nil, cfg)
for i := 0; i < fields; i++ {
e.RegisterResolver(fmt.Sprintf("f%d", i), func(_ context.Context, _ *storage.Store, _ map[string]interface{}) (interface{}, error) {
return []interface{}{}, nil
})
}
return e
}
func errText(r *Response) string {
if len(r.Errors) == 0 {
return ""
}
return r.Errors[0].Message
}
func TestTopFields_RealPageIsNotRejected(t *testing.T) {
e := newTestEngine(t, nil, 23)
if got := errText(e.Execute(context.Background(), &Request{Query: query(23)})); strings.Contains(got, "too many top-level fields") {
t.Fatalf("23 fields rejected by default: %s", got)
}
}
func TestTopFields_CapStillApplies(t *testing.T) {
e := newTestEngine(t, &Config{MaxQueryDepth: 10, MaxTopFields: 5, MaxResultSize: 1 << 20, QueryTimeoutMs: 30000}, 8)
got := errText(e.Execute(context.Background(), &Request{Query: query(8)}))
if !strings.Contains(got, "too many top-level fields (8), maximum 5") {
t.Fatalf("configured cap not enforced, got %q", got)
}
}
func TestTopFields_UnsetConfigFallsBackToDefault(t *testing.T) {
// A zero value means "unset", not "reject everything" — a config written
// before this field existed must not brick every query.
e := newTestEngine(t, &Config{MaxQueryDepth: 10, MaxResultSize: 1 << 20, QueryTimeoutMs: 30000}, 23)
if got := errText(e.Execute(context.Background(), &Request{Query: query(23)})); strings.Contains(got, "too many top-level fields") {
t.Fatalf("zero MaxTopFields rejected a normal query: %s", got)
}
if e.maxTopFields() != defaultMaxTopFields {
t.Fatalf("expected default %d, got %d", defaultMaxTopFields, e.maxTopFields())
}
}