github.com/renderinc/gqlgen@v0.7.2/complexity/complexity_test.go (about)

     1  package complexity
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"testing"
     7  
     8  	"github.com/99designs/gqlgen/graphql"
     9  	"github.com/stretchr/testify/require"
    10  	"github.com/vektah/gqlparser"
    11  	"github.com/vektah/gqlparser/ast"
    12  )
    13  
    14  var schema = gqlparser.MustLoadSchema(
    15  	&ast.Source{
    16  		Name: "test.graphql",
    17  		Input: `
    18  		interface NameInterface {
    19  			name: String
    20  		}
    21  
    22  		type Item implements NameInterface {
    23  			scalar: String
    24  			name: String
    25  			list(size: Int = 10): [Item]
    26  		}
    27  
    28  		type ExpensiveItem implements NameInterface {
    29  			name: String
    30  		}
    31  
    32  		type Named {
    33  			name: String
    34  		}
    35  
    36  		union NameUnion = Item | Named
    37  
    38  		type Query {
    39  			scalar: String
    40  			object: Item
    41  			interface: NameInterface
    42  			union: NameUnion
    43  			customObject: Item
    44  			list(size: Int = 10): [Item]
    45  		}
    46  		`,
    47  	},
    48  )
    49  
    50  func requireComplexity(t *testing.T, source string, vars map[string]interface{}, complexity int) {
    51  	t.Helper()
    52  	query := gqlparser.MustLoadQuery(schema, source)
    53  	es := &executableSchemaStub{}
    54  	actualComplexity := Calculate(es, query.Operations[0], vars)
    55  	require.Equal(t, complexity, actualComplexity)
    56  }
    57  
    58  func TestCalculate(t *testing.T) {
    59  	t.Run("uses default cost", func(t *testing.T) {
    60  		const query = `
    61  		{
    62  			scalar
    63  		}
    64  		`
    65  		requireComplexity(t, query, nil, 1)
    66  	})
    67  
    68  	t.Run("adds together fields", func(t *testing.T) {
    69  		const query = `
    70  		{
    71  			scalar1: scalar
    72  			scalar2: scalar
    73  		}
    74  		`
    75  		requireComplexity(t, query, nil, 2)
    76  	})
    77  
    78  	t.Run("a level of nesting adds complexity", func(t *testing.T) {
    79  		const query = `
    80  		{
    81  			object {
    82  				scalar
    83  			}
    84  		}
    85  		`
    86  		requireComplexity(t, query, nil, 2)
    87  	})
    88  
    89  	t.Run("adds together children", func(t *testing.T) {
    90  		const query = `
    91  		{
    92  			scalar
    93  			object {
    94  				scalar
    95  			}
    96  		}
    97  		`
    98  		requireComplexity(t, query, nil, 3)
    99  	})
   100  
   101  	t.Run("adds inline fragments", func(t *testing.T) {
   102  		const query = `
   103  		{
   104  			... {
   105  				scalar
   106  			}
   107  		}
   108  		`
   109  		requireComplexity(t, query, nil, 1)
   110  	})
   111  
   112  	t.Run("adds fragments", func(t *testing.T) {
   113  		const query = `
   114  		{
   115  			... Fragment
   116  		}
   117  
   118  		fragment Fragment on Query {
   119  			scalar
   120  		}
   121  		`
   122  		requireComplexity(t, query, nil, 1)
   123  	})
   124  
   125  	t.Run("uses custom complexity", func(t *testing.T) {
   126  		const query = `
   127  		{
   128  			list {
   129  				scalar
   130  			}
   131  		}
   132  		`
   133  		requireComplexity(t, query, nil, 10)
   134  	})
   135  
   136  	t.Run("ignores negative custom complexity values", func(t *testing.T) {
   137  		const query = `
   138  		{
   139  			list(size: -100) {
   140  				scalar
   141  			}
   142  		}
   143  		`
   144  		requireComplexity(t, query, nil, 2)
   145  	})
   146  
   147  	t.Run("custom complexity must be >= child complexity", func(t *testing.T) {
   148  		const query = `
   149  		{
   150  			customObject {
   151  				list(size: 100) {
   152  					scalar
   153  				}
   154  			}
   155  		}
   156  		`
   157  		requireComplexity(t, query, nil, 101)
   158  	})
   159  
   160  	t.Run("interfaces take max concrete cost", func(t *testing.T) {
   161  		const query = `
   162  		{
   163  			interface {
   164  				name
   165  			}
   166  		}
   167  		`
   168  		requireComplexity(t, query, nil, 6)
   169  	})
   170  
   171  	t.Run("guards against integer overflow", func(t *testing.T) {
   172  		if maxInt == math.MaxInt32 {
   173  			// this test is written assuming 64-bit ints
   174  			t.Skip()
   175  		}
   176  		const query = `
   177  		{
   178  			list1: list(size: 2147483647) {
   179  				list(size: 2147483647) {
   180  					list(size: 2) {
   181  						scalar
   182  					}
   183  				}
   184  			}
   185  			# total cost so far: 2*0x7fffffff*0x7fffffff
   186  			# = 0x7ffffffe00000002
   187  			# Adding the same again should cause overflow
   188  			list2: list(size: 2147483647) {
   189  				list(size: 2147483647) {
   190  					list(size: 2) {
   191  						scalar
   192  					}
   193  				}
   194  			}
   195  		}
   196  		`
   197  		requireComplexity(t, query, nil, math.MaxInt64)
   198  	})
   199  }
   200  
   201  type executableSchemaStub struct {
   202  }
   203  
   204  var _ graphql.ExecutableSchema = &executableSchemaStub{}
   205  
   206  func (e *executableSchemaStub) Schema() *ast.Schema {
   207  	return schema
   208  }
   209  
   210  func (e *executableSchemaStub) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) {
   211  	switch typeName + "." + field {
   212  	case "ExpensiveItem.name":
   213  		return 5, true
   214  	case "Query.list", "Item.list":
   215  		return int(args["size"].(int64)) * childComplexity, true
   216  	case "Query.customObject":
   217  		return 1, true
   218  	}
   219  	return 0, false
   220  }
   221  
   222  func (e *executableSchemaStub) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
   223  	panic("Query should never be called by complexity calculations")
   224  }
   225  
   226  func (e *executableSchemaStub) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
   227  	panic("Mutation should never be called by complexity calculations")
   228  }
   229  
   230  func (e *executableSchemaStub) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {
   231  	panic("Subscription should never be called by complexity calculations")
   232  }