github.com/99designs/gqlgen@v0.17.45/graphql/executor/executor_test.go (about) 1 package executor_test 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 "github.com/vektah/gqlparser/v2/ast" 10 "github.com/vektah/gqlparser/v2/gqlerror" 11 "github.com/vektah/gqlparser/v2/parser" 12 13 "github.com/99designs/gqlgen/graphql" 14 "github.com/99designs/gqlgen/graphql/errcode" 15 "github.com/99designs/gqlgen/graphql/executor/testexecutor" 16 ) 17 18 func TestExecutor(t *testing.T) { 19 exec := testexecutor.New() 20 21 t.Run("calls query on executable schema", func(t *testing.T) { 22 resp := query(exec, "", "{name}") 23 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 24 }) 25 26 t.Run("validates operation", func(t *testing.T) { 27 t.Run("no operation", func(t *testing.T) { 28 resp := query(exec, "", "") 29 assert.Equal(t, "", string(resp.Data)) 30 assert.Equal(t, 1, len(resp.Errors)) 31 assert.Equal(t, errcode.ValidationFailed, resp.Errors[0].Extensions["code"]) 32 }) 33 34 t.Run("bad operation", func(t *testing.T) { 35 resp := query(exec, "badOp", "query test { name }") 36 assert.Equal(t, "", string(resp.Data)) 37 assert.Equal(t, 1, len(resp.Errors)) 38 assert.Equal(t, errcode.ValidationFailed, resp.Errors[0].Extensions["code"]) 39 }) 40 }) 41 42 t.Run("invokes operation middleware in order", func(t *testing.T) { 43 var calls []string 44 exec.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { 45 calls = append(calls, "first") 46 return next(ctx) 47 }) 48 exec.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { 49 calls = append(calls, "second") 50 return next(ctx) 51 }) 52 53 resp := query(exec, "", "{name}") 54 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 55 assert.Equal(t, []string{"first", "second"}, calls) 56 }) 57 58 t.Run("invokes response middleware in order", func(t *testing.T) { 59 var calls []string 60 exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { 61 calls = append(calls, "first") 62 return next(ctx) 63 }) 64 exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { 65 calls = append(calls, "second") 66 return next(ctx) 67 }) 68 69 resp := query(exec, "", "{name}") 70 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 71 assert.Equal(t, []string{"first", "second"}, calls) 72 }) 73 74 t.Run("invokes root field middleware in order", func(t *testing.T) { 75 var calls []string 76 exec.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler { 77 calls = append(calls, "first") 78 return next(ctx) 79 }) 80 exec.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler { 81 calls = append(calls, "second") 82 return next(ctx) 83 }) 84 85 resp := query(exec, "", "{name}") 86 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 87 assert.Equal(t, []string{"first", "second"}, calls) 88 }) 89 90 t.Run("invokes field middleware in order", func(t *testing.T) { 91 var calls []string 92 exec.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { 93 calls = append(calls, "first") 94 return next(ctx) 95 }) 96 exec.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { 97 calls = append(calls, "second") 98 return next(ctx) 99 }) 100 101 resp := query(exec, "", "{name}") 102 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 103 assert.Equal(t, []string{"first", "second"}, calls) 104 }) 105 106 t.Run("invokes operation mutators", func(t *testing.T) { 107 var calls []string 108 exec.Use(&testParamMutator{ 109 Mutate: func(ctx context.Context, req *graphql.RawParams) *gqlerror.Error { 110 calls = append(calls, "param") 111 return nil 112 }, 113 }) 114 exec.Use(&testCtxMutator{ 115 Mutate: func(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error { 116 calls = append(calls, "context") 117 return nil 118 }, 119 }) 120 resp := query(exec, "", "{name}") 121 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 122 assert.Equal(t, []string{"param", "context"}, calls) 123 }) 124 125 t.Run("get query parse error in AroundResponses", func(t *testing.T) { 126 var errors1 gqlerror.List 127 var errors2 gqlerror.List 128 exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { 129 resp := next(ctx) 130 errors1 = graphql.GetErrors(ctx) 131 errors2 = resp.Errors 132 return resp 133 }) 134 135 resp := query(exec, "", "invalid") 136 assert.Equal(t, "", string(resp.Data)) 137 assert.Equal(t, 1, len(resp.Errors)) 138 assert.Equal(t, 1, len(errors1)) 139 assert.Equal(t, 1, len(errors2)) 140 }) 141 142 t.Run("query caching", func(t *testing.T) { 143 ctx := context.Background() 144 cache := &graphql.MapCache{} 145 exec.SetQueryCache(cache) 146 qry := `query Foo {name}` 147 148 t.Run("cache miss populates cache", func(t *testing.T) { 149 resp := query(exec, "Foo", qry) 150 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 151 152 cacheDoc, ok := cache.Get(ctx, qry) 153 require.True(t, ok) 154 require.Equal(t, "Foo", cacheDoc.(*ast.QueryDocument).Operations[0].Name) 155 }) 156 157 t.Run("cache hits use document from cache", func(t *testing.T) { 158 doc, err := parser.ParseQuery(&ast.Source{Input: `query Bar {name}`}) 159 require.Nil(t, err) 160 cache.Add(ctx, qry, doc) 161 162 resp := query(exec, "Bar", qry) 163 assert.Equal(t, `{"name":"test"}`, string(resp.Data)) 164 165 cacheDoc, ok := cache.Get(ctx, qry) 166 require.True(t, ok) 167 require.Equal(t, "Bar", cacheDoc.(*ast.QueryDocument).Operations[0].Name) 168 }) 169 }) 170 } 171 172 type testParamMutator struct { 173 Mutate func(context.Context, *graphql.RawParams) *gqlerror.Error 174 } 175 176 func (m *testParamMutator) ExtensionName() string { 177 return "Operation: Mutate Parameters" 178 } 179 180 func (m *testParamMutator) Validate(s graphql.ExecutableSchema) error { 181 return nil 182 } 183 184 func (m *testParamMutator) MutateOperationParameters(ctx context.Context, r *graphql.RawParams) *gqlerror.Error { 185 return m.Mutate(ctx, r) 186 } 187 188 type testCtxMutator struct { 189 Mutate func(context.Context, *graphql.OperationContext) *gqlerror.Error 190 } 191 192 func (m *testCtxMutator) ExtensionName() string { 193 return "Operation: Mutate the Context" 194 } 195 196 func (m *testCtxMutator) Validate(s graphql.ExecutableSchema) error { 197 return nil 198 } 199 200 func (m *testCtxMutator) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error { 201 return m.Mutate(ctx, rc) 202 } 203 204 func TestErrorServer(t *testing.T) { 205 exec := testexecutor.NewError() 206 207 t.Run("get resolver error in AroundResponses", func(t *testing.T) { 208 var errors1 gqlerror.List 209 var errors2 gqlerror.List 210 exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { 211 resp := next(ctx) 212 errors1 = graphql.GetErrors(ctx) 213 errors2 = resp.Errors 214 return resp 215 }) 216 217 resp := query(exec, "", "{name}") 218 assert.Equal(t, "null", string(resp.Data)) 219 assert.Equal(t, 1, len(errors1)) 220 assert.Equal(t, 1, len(errors2)) 221 }) 222 } 223 224 func query(exec *testexecutor.TestExecutor, op, q string) *graphql.Response { 225 ctx := graphql.StartOperationTrace(context.Background()) 226 now := graphql.Now() 227 rc, err := exec.CreateOperationContext(ctx, &graphql.RawParams{ 228 Query: q, 229 OperationName: op, 230 ReadTime: graphql.TraceTiming{ 231 Start: now, 232 End: now, 233 }, 234 }) 235 if err != nil { 236 return exec.DispatchError(ctx, err) 237 } 238 239 resp, ctx2 := exec.DispatchOperation(ctx, rc) 240 return resp(ctx2) 241 }