github.com/fortexxx/gqlgen@v0.10.3-0.20191216030626-ca5ea8b21ead/complexity/complexity_test.go (about)

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