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