Files
releases/v1/topdown/graphql_test.go
T
Ville Vesilehto f77322b3fb build: bump Go version requirement to 1.24 (#7839)
Go 1.23 is no longer supported as per Go release policy.

Changes:

- Use Go v1.24.6 as the project SDK requirement
- Apply lint fixes for Go 1.24
- Fix "non-constant format string in call" issues as seen in CI.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-08-24 09:02:09 +02:00

1000 lines
26 KiB
Go

// Copyright 2025 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package topdown
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"testing"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/topdown/cache"
)
const employeeGQLSchema = `
type Employee {
id: String!
salary: Int!
}
schema {
query: Query
}
type Query {
employeeByID(id: String!): Employee
}`
const invalidEmployeeGQLSchema = `
type Employee {
id: String!
salary: Int!
}
schema {
query: Query
}
type Broken {
fixme
type Query {
employeeByID(id: String!): Employee
}`
const employeeGQLQueryAST = `{"Operations":[{"Name":"","Operation":"query","SelectionSet":[{"Alias":"employeeByID","Arguments":[{"Name":"id","Value":{"Kind":3,"Raw":"alice"}}],"Name":"employeeByID","SelectionSet":[{"Alias":"salary","Name":"salary"}]}]}]}`
const employeeGQLSchemaAST = `{"Definitions":[{"BuiltIn":false,"Description":"","Fields":[{"Description":"","Name":"id","Type":{"NamedType":"String","NonNull":true}},{"Description":"","Name":"salary","Type":{"NamedType":"Int","NonNull":true}}],"Kind":"OBJECT","Name":"Employee"},{"BuiltIn":false,"Description":"","Fields":[{"Arguments":[{"Description":"","Name":"id","Type":{"NamedType":"String","NonNull":true}}],"Description":"","Name":"employeeByID","Type":{"NamedType":"Employee","NonNull":false}}],"Kind":"OBJECT","Name":"Query"}],"Schema":[{"Description":"","OperationTypes":[{"Operation":"query","Type":"Query"}]}]}`
var employeeGQLQueryASTObj = ast.MustParseTerm(employeeGQLQueryAST).Value.(ast.Object)
var employeeGQLSchemaASTObj = ast.MustParseTerm(employeeGQLSchemaAST).Value.(ast.Object)
func TestGraphQLParseString(t *testing.T) {
t.Parallel()
cases := []struct {
note string
schema string
query string
result string
wantErr bool
}{
{
note: "valid employee query and GQL schema",
schema: employeeGQLSchema,
query: `{ employeeByID(id: "alice") { salary } }`,
result: `[{"Operations": [{"Name": "", "Operation": "query", "SelectionSet": [{"Alias": "employeeByID", "Arguments": [{"Name": "id", "Value": {"Kind": 3, "Raw": "alice"}}], "Name": "employeeByID", "SelectionSet": [{"Alias": "salary", "Name": "salary"}]}]}]}, {"Definitions": [{"BuiltIn": false, "Description": "", "Fields": [{"Description": "", "Name": "id", "Type": {"NamedType": "String", "NonNull": true}}, {"Description": "", "Name": "salary", "Type": {"NamedType": "Int", "NonNull": true}}], "Kind": "OBJECT", "Name": "Employee"}, {"BuiltIn": false, "Description": "", "Fields": [{"Arguments": [{"Description": "", "Name": "id", "Type": {"NamedType": "String", "NonNull": true}}], "Description": "", "Name": "employeeByID", "Type": {"NamedType": "Employee", "NonNull": false}}], "Kind": "OBJECT", "Name": "Query"}], "Schema": [{"Description": "", "OperationTypes": [{"Operation": "query", "Type": "Query"}]}]}]`,
wantErr: false,
},
{
note: "valid employee schema, invalid query",
schema: employeeGQLSchema,
query: `{employeeByID("alice"`,
result: "",
wantErr: true,
},
{
note: "invalid",
schema: invalidEmployeeGQLSchema,
query: `{ employeeByID(id:"bob") } `, // missing fields
result: "",
wantErr: true,
},
{
note: "empty",
schema: ``,
query: `{ employeeByID(id: "charlie") { id salary } }`,
result: "",
wantErr: true,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result *ast.Term
var err error
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLParse(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{ast.NewTerm(ast.String(tc.query)), ast.NewTerm(ast.String(tc.schema))},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if (result != nil) && (tc.result != result.String()) {
t.Errorf("Unexpected result, expected %#v, got %s", tc.result, result.String())
return
}
}
// Without the cache
err = builtinGraphQLParse(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{ast.NewTerm(ast.String(tc.query)), ast.NewTerm(ast.String(tc.schema))},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if (result != nil) && (tc.result != result.String()) {
t.Errorf("Unexpected result, expected %#v, got %s", tc.result, result.String())
return
}
})
}
}
func TestGraphQLParseObject(t *testing.T) {
t.Parallel()
// Create a default Term with the expected result for the happy path here
// so we can include it in the test case table
defaultExpectedResult := ast.ArrayTerm(
ast.NewTerm(employeeGQLQueryASTObj),
ast.NewTerm(employeeGQLSchemaASTObj),
)
cases := []struct {
note string
schema string
query string
result *ast.Term
wantErr bool
}{
{
note: "valid employee schema, valid query",
schema: employeeGQLSchemaAST,
query: `{ employeeByID(id: "alice") { salary } }`,
result: defaultExpectedResult,
wantErr: false,
},
{
note: "valid employee schema, invalid query",
schema: employeeGQLSchemaAST,
query: `{employeeByID("alice"`,
result: defaultExpectedResult,
wantErr: true,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result *ast.Term
var err error
inputTerm := ast.NewTerm(ast.MustParseTerm(tc.schema).Value.(ast.Object))
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLParse(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{ast.NewTerm(ast.String(tc.query)), inputTerm},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if result != nil && !tc.wantErr {
if !tc.result.Equal(result) {
t.Errorf("Unexpected result, expected\n%s\ngot\n%s\n", tc.result.String(), result.String())
return
}
}
result = nil
}
// Without the cache
err = builtinGraphQLParse(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{ast.NewTerm(ast.String(tc.query)), inputTerm},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if result != nil && !tc.wantErr {
if !tc.result.Equal(result) {
t.Errorf("Unexpected result, expected\n%s\ngot\n%s\n", tc.result.String(), result.String())
return
}
}
result = nil
})
}
}
func TestGraphQLSchemaIsValid(t *testing.T) {
t.Parallel()
cases := []struct {
note string
schema *ast.Term
result ast.Value
wantErr bool
}{
{
note: "valid employee",
schema: ast.NewTerm(ast.String(employeeGQLSchema)),
result: ast.Boolean(true),
wantErr: false,
},
{
note: "invalid",
schema: ast.NewTerm(ast.String(invalidEmployeeGQLSchema)),
result: ast.Boolean(false),
wantErr: false,
},
{
note: "empty",
schema: ast.NewTerm(ast.String(``)),
result: ast.Boolean(true), // An empty schema is valid because it is merged with the base schema
wantErr: false,
},
{
note: "valid employee schema as object",
schema: ast.NewTerm(ast.MustParseTerm(employeeGQLSchemaAST).Value.(ast.Object)),
result: ast.Boolean(true),
wantErr: false,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result ast.Value
var err error
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLSchemaIsValid(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{tc.schema},
func(term *ast.Term) error {
result = term.Value
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if tc.result != result {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
}
// Without the cache
err = builtinGraphQLSchemaIsValid(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{tc.schema},
func(term *ast.Term) error {
result = term.Value
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if tc.result != result {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
})
}
}
func TestGraphQLParseAndVerify(t *testing.T) {
t.Parallel()
// Use this to map result item position to purpose for better error messages
resultItemDescription := []string{"is_valid", "query_ast", "schema_ast"}
failureResult := ast.ArrayTerm(
ast.BooleanTerm(false),
ast.MustParseTerm("{}"),
ast.MustParseTerm("{}"),
)
cases := []struct {
note string
schema string
query string
result *ast.Term
wantErr bool
}{
{
note: "valid employee query and GQL schema",
schema: employeeGQLSchema,
query: `{ employeeByID(id: "alice") { salary } }`,
result: ast.ArrayTerm(
ast.BooleanTerm(true),
ast.NewTerm(employeeGQLQueryASTObj),
ast.NewTerm(employeeGQLSchemaASTObj),
),
wantErr: false,
},
{
note: "valid employee schema, invalid query",
schema: employeeGQLSchema,
query: `{employeeByID("alice"`,
result: failureResult,
wantErr: false,
},
{
note: "invalid schema",
schema: invalidEmployeeGQLSchema,
query: `{ employeeByID(id: "alice") { salary } }`,
result: failureResult,
wantErr: false,
},
{
note: "invalid query",
schema: employeeGQLSchema,
query: `{ employeeByID(id:"bob") } `, // missing fields
result: failureResult,
wantErr: false,
},
{
note: "empty schema is not ok",
schema: ``,
query: `{ employeeByID(id: "charlie") { id salary } }`,
result: failureResult,
wantErr: false,
},
{
note: "empty query is ok",
schema: employeeGQLSchema,
query: ``,
result: ast.ArrayTerm(
ast.BooleanTerm(true),
ast.MustParseTerm("{}"),
ast.NewTerm(employeeGQLSchemaASTObj),
),
wantErr: false,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result *ast.Term
var err error
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLParseAndVerify(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{ast.NewTerm(ast.String(tc.query)), ast.NewTerm(ast.String(tc.schema))},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
// Check each item in array result
for i := range tc.result.Value.(*ast.Array).Len() {
expected := tc.result.Value.(*ast.Array).Elem(i)
actual := result.Value.(*ast.Array).Elem(i)
if !expected.Equal(actual) {
fmt.Fprintf(os.Stderr, "DEBUG: expected:\n%s\ngot:\n%s\n", expected.String(), actual.String())
t.Errorf("Unexpected value at result[%d] (%s), expected %#v, got %#v", i, resultItemDescription[i], expected, actual)
return
}
}
}
// Without the cache
err = builtinGraphQLParseAndVerify(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{ast.NewTerm(ast.String(tc.query)), ast.NewTerm(ast.String(tc.schema))},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
// Check each item in array result
for i := range tc.result.Value.(*ast.Array).Len() {
expected := tc.result.Value.(*ast.Array).Elem(i)
actual := result.Value.(*ast.Array).Elem(i)
if !expected.Equal(actual) {
t.Errorf("Unexpected value at result[%d] (%s), expected %#v, got %#v", i, resultItemDescription[i], expected, actual)
return
}
}
})
}
}
func TestGraphQLIsValid(t *testing.T) {
t.Parallel()
cases := []struct {
note string
query *ast.Term
schema *ast.Term
result ast.Value
wantErr bool
}{
{
note: "valid employee - query as string",
query: ast.NewTerm(ast.String(`{ employeeByID(id: "alice") { salary } }`)),
schema: ast.NewTerm(ast.String(employeeGQLSchema)),
result: ast.Boolean(true),
wantErr: false,
},
{
note: "valid employee - query as object",
query: ast.NewTerm(ast.MustParseTerm(employeeGQLQueryAST).Value.(ast.Object)),
schema: ast.NewTerm(ast.String(employeeGQLSchema)),
result: ast.Boolean(true),
wantErr: false,
},
{
note: "invalid schema",
query: ast.NewTerm(ast.String(`{ employeeByID(id: "alice") { salary } }`)),
schema: ast.NewTerm(ast.String(invalidEmployeeGQLSchema)),
result: ast.Boolean(false),
wantErr: false,
},
{
note: "invalid query",
query: ast.NewTerm(ast.String(`{ employeeByID(id: "bob") }`)),
schema: ast.NewTerm(ast.String(employeeGQLSchema)),
result: ast.Boolean(false),
wantErr: false,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result ast.Value
var err error
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLIsValid(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{tc.query, tc.schema},
func(term *ast.Term) error {
result = term.Value
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if tc.result != result {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
}
// Without the cache
err = builtinGraphQLIsValid(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{tc.query, tc.schema},
func(term *ast.Term) error {
result = term.Value
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if tc.result != result {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
})
}
}
func TestGraphQLParseQuery(t *testing.T) {
t.Parallel()
cases := []struct {
note string
query *ast.Term
result *ast.Term
wantErr bool
}{
{
note: "valid employee - query as string",
query: ast.NewTerm(ast.String(`{ employeeByID(id: "alice") { salary } }`)),
result: ast.NewTerm(employeeGQLQueryASTObj),
wantErr: false,
},
{
note: "invalid query",
query: ast.NewTerm(ast.String(`{ employeeByID("id: bob") }`)),
result: nil,
wantErr: true,
},
{
note: "empty query is valid",
query: ast.NewTerm(ast.String(``)),
result: ast.MustParseTerm("{}"),
wantErr: false,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result *ast.Term
var err error
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLParseQuery(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{tc.query},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if !tc.result.Equal(result) {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
}
// Without the cache
err = builtinGraphQLParseQuery(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{tc.query},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if !tc.result.Equal(result) {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
})
}
}
func TestGraphQLParseSchema(t *testing.T) {
t.Parallel()
cases := []struct {
note string
schema *ast.Term
result *ast.Term
wantErr bool
}{
{
note: "valid schema as string",
schema: ast.NewTerm(ast.String(employeeGQLSchema)),
result: ast.NewTerm(employeeGQLSchemaASTObj),
wantErr: false,
},
{
note: "invalid schema as string",
schema: ast.NewTerm(ast.String(invalidEmployeeGQLSchema)),
result: nil,
wantErr: true,
},
{
note: "empty schema is valid",
schema: ast.NewTerm(ast.String(``)),
result: ast.MustParseTerm("{}"),
wantErr: false,
},
}
valueCache := cache.NewInterQueryValueCache(
t.Context(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
gqlCacheName: {
MaxNumEntries: &[]int{10}[0],
},
},
},
})
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
var result *ast.Term
var err error
// Call function multiple times to hit the cache
for i := 1; i <= 3; i++ {
err = builtinGraphQLParseSchema(
BuiltinContext{
InterQueryBuiltinValueCache: valueCache,
},
[]*ast.Term{tc.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if !tc.result.Equal(result) {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
}
// Without the cache
err = builtinGraphQLParseSchema(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{tc.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
if tc.wantErr && err == nil {
t.Errorf("Unexpected return value, expected error, got nil")
return
}
if !tc.wantErr && err != nil {
t.Errorf("Unexpected return value, expected nil, got error: %s", err)
return
}
if !tc.result.Equal(result) {
t.Errorf("Unexpected result, expected %#v, got %#v", tc.result, result)
return
}
})
}
}
func TestGraphQLParseSchemaAlloc(t *testing.T) {
cases := []struct {
note string
schema *ast.Term
maxAlloc uint64
}{
{
note: "default schema",
schema: ast.NewTerm(ast.String(schemaWithExtraEmployeeTypes(0))),
maxAlloc: 1 * 1024 * 1024,
},
// Uncomment when https://github.com/open-policy-agent/opa/pull/7509 is merged
// {
// note: "default schema plus 100 additional types",
// schema: ast.NewTerm(ast.String(schemaWithExtraEmployeeTypes(100))),
// maxAlloc: 10 * 1024 * 1024,
// },
// {
// note: "default schema plus 1,000 additional types",
// schema: ast.NewTerm(ast.String(schemaWithExtraEmployeeTypes(1000))),
// maxAlloc: 50 * 1024 * 1024,
// },
// {
// note: "default schema plus 10,000 additional types",
// schema: ast.NewTerm(ast.String(schemaWithExtraEmployeeTypes(10000))),
// maxAlloc: 100 * 1024 * 1024,
// },
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
var startMemStats runtime.MemStats
runtime.ReadMemStats(&startMemStats)
_ = builtinGraphQLParseSchema(
BuiltinContext{
InterQueryBuiltinValueCache: nil,
},
[]*ast.Term{tc.schema},
func(term *ast.Term) error {
return nil
},
)
var finishMemStats runtime.MemStats
runtime.ReadMemStats(&finishMemStats)
allocDifference := finishMemStats.Alloc - startMemStats.Alloc
runtime.GC()
if allocDifference > tc.maxAlloc {
t.Errorf("Parsing schema '%s' expected alloc < %d, got %d", tc.note, tc.maxAlloc, allocDifference)
return
}
})
}
}
func TestFormatGqlParserError(t *testing.T) {
testCases := []struct {
desc string
inErr error
outErr error
}{
// Expected errors based on https://github.com/vektah/gqlparser/blob/master/gqlerror/error.go#L40-L67
{
desc: "valid gqlparser error with filename and no location",
inErr: errors.New("filename.gql: error string with filename and no location"),
outErr: errors.New("GraphQL parse error: filename.gql: error string with filename and no location"),
},
{
desc: "valid gqlparser error with filename and location",
inErr: errors.New("filename.gql:1:2: error string with filename and location"),
outErr: errors.New("error string with filename and location in GraphQL string at location 1:2"),
},
{
desc: "valid gqlparser error without filename and no location",
inErr: errors.New("input: error string without filename and no location"),
outErr: errors.New("GraphQL parse error: input: error string without filename and no location"),
},
{
desc: "valid gqlparser error without filename and with location",
inErr: errors.New("input:1:2: error string without filename and with location"),
outErr: errors.New("error string without filename and with location in GraphQL string at location 1:2"),
},
// Unexpected errors
{
desc: "Handle nil even though it is unnecessary today",
inErr: nil,
outErr: nil,
},
{
desc: "empty",
inErr: errors.New(""),
outErr: errors.New("GraphQL parse error: "),
},
{
desc: "string with no :",
inErr: errors.New("test"),
outErr: errors.New("GraphQL parse error: test"),
},
{
desc: "string with 2:",
inErr: errors.New("x:y:z"),
outErr: errors.New("GraphQL parse error: x:y:z"),
},
{
desc: "string with 3: and alpha locations",
inErr: errors.New("input: b:c:d"),
outErr: errors.New("GraphQL parse error: input: b:c:d"),
},
{
desc: "string with 8: and empty locations",
inErr: errors.New("::::::::"),
outErr: errors.New("GraphQL parse error: ::::::::"),
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
gotErr := formatGqlParserError(tc.inErr)
if gotErr == nil {
if tc.outErr != nil {
t.Errorf("gotErr = %v, wantErr %v", gotErr, tc.outErr)
return
}
} else if gotErr.Error() != tc.outErr.Error() {
t.Errorf("gotErr = %v, wantErr %v", gotErr, tc.outErr)
}
})
}
}
// Inflate GraphQL schema size with `count` extra types
func schemaWithExtraEmployeeTypes(count int) string {
// build up `count` more types on basic schema
var builder strings.Builder
builder.WriteString(employeeGQLSchema)
for i := range count {
fmt.Fprintf(&builder, "\ntype Employee%d {\n id: String!\n salary: Int!\n}\n", i)
}
return builder.String()
}