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 }
242
View as plain text