github.com/99designs/gqlgen@v0.17.45/graphql/executor/testexecutor/testexecutor.go (about) 1 package testexecutor 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "time" 10 11 "github.com/vektah/gqlparser/v2" 12 "github.com/vektah/gqlparser/v2/ast" 13 14 "github.com/99designs/gqlgen/graphql" 15 "github.com/99designs/gqlgen/graphql/executor" 16 ) 17 18 type MockResponse struct { 19 Name string `json:"name"` 20 } 21 22 func (mr *MockResponse) UnmarshalGQL(v interface{}) error { 23 return nil 24 } 25 26 func (mr *MockResponse) MarshalGQL(w io.Writer) { 27 buf := new(bytes.Buffer) 28 err := json.NewEncoder(buf).Encode(mr) 29 if err != nil { 30 panic(err) 31 } 32 33 ba := bytes.NewBuffer(bytes.TrimRight(buf.Bytes(), "\n")) 34 35 fmt.Fprint(w, ba) 36 } 37 38 // New provides a server for use in tests that isn't relying on generated code. It isnt a perfect reproduction of 39 // a generated server, but it aims to be good enough to test the handler package without relying on codegen. 40 func New() *TestExecutor { 41 next := make(chan struct{}) 42 43 schema := gqlparser.MustLoadSchema(&ast.Source{Input: ` 44 type Query { 45 name: String! 46 find(id: Int!): String! 47 } 48 type Mutation { 49 name: String! 50 } 51 type Subscription { 52 name: String! 53 } 54 `}) 55 56 exec := &TestExecutor{ 57 next: next, 58 } 59 60 exec.schema = &graphql.ExecutableSchemaMock{ 61 ExecFunc: func(ctx context.Context) graphql.ResponseHandler { 62 rc := graphql.GetOperationContext(ctx) 63 switch rc.Operation.Operation { 64 case ast.Query: 65 ran := false 66 return func(ctx context.Context) *graphql.Response { 67 if ran { 68 return nil 69 } 70 ran = true 71 // Field execution happens inside the generated code, lets simulate some of it. 72 ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ 73 Object: "Query", 74 Field: graphql.CollectedField{ 75 Field: &ast.Field{ 76 Name: "name", 77 Alias: "name", 78 Definition: schema.Types["Query"].Fields.ForName("name"), 79 }, 80 }, 81 }) 82 data := graphql.GetOperationContext(ctx).RootResolverMiddleware(ctx, func(ctx context.Context) graphql.Marshaler { 83 res, err := graphql.GetOperationContext(ctx).ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) { 84 // return &graphql.Response{Data: []byte(`{"name":"test"}`)}, nil 85 return &MockResponse{Name: "test"}, nil 86 }) 87 if err != nil { 88 panic(err) 89 } 90 91 return res.(*MockResponse) 92 }) 93 94 var buf bytes.Buffer 95 data.MarshalGQL(&buf) 96 97 return &graphql.Response{Data: buf.Bytes()} 98 } 99 case ast.Mutation: 100 return graphql.OneShot(graphql.ErrorResponse(ctx, "mutations are not supported")) 101 case ast.Subscription: 102 return func(context context.Context) *graphql.Response { 103 select { 104 case <-ctx.Done(): 105 return nil 106 case <-next: 107 return &graphql.Response{ 108 Data: []byte(`{"name":"test"}`), 109 } 110 } 111 } 112 default: 113 return graphql.OneShot(graphql.ErrorResponse(ctx, "unsupported GraphQL operation")) 114 } 115 }, 116 SchemaFunc: func() *ast.Schema { 117 return schema 118 }, 119 ComplexityFunc: func(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (i int, b bool) { 120 return exec.complexity, true 121 }, 122 } 123 124 exec.Executor = executor.New(exec.schema) 125 return exec 126 } 127 128 // NewError provides a server for use in resolver error tests that isn't relying on generated code. It isnt a perfect reproduction of 129 // a generated server, but it aims to be good enough to test the handler package without relying on codegen. 130 func NewError() *TestExecutor { 131 next := make(chan struct{}) 132 133 schema := gqlparser.MustLoadSchema(&ast.Source{Input: ` 134 type Query { 135 name: String! 136 } 137 `}) 138 139 exec := &TestExecutor{ 140 next: next, 141 } 142 143 exec.schema = &graphql.ExecutableSchemaMock{ 144 ExecFunc: func(ctx context.Context) graphql.ResponseHandler { 145 rc := graphql.GetOperationContext(ctx) 146 switch rc.Operation.Operation { 147 case ast.Query: 148 ran := false 149 return func(ctx context.Context) *graphql.Response { 150 if ran { 151 return nil 152 } 153 ran = true 154 155 graphql.AddError(ctx, fmt.Errorf("resolver error")) 156 157 return &graphql.Response{ 158 Data: []byte(`null`), 159 } 160 } 161 case ast.Mutation: 162 return graphql.OneShot(graphql.ErrorResponse(ctx, "mutations are not supported")) 163 case ast.Subscription: 164 return graphql.OneShot(graphql.ErrorResponse(ctx, "subscription are not supported")) 165 default: 166 return graphql.OneShot(graphql.ErrorResponse(ctx, "unsupported GraphQL operation")) 167 } 168 }, 169 SchemaFunc: func() *ast.Schema { 170 return schema 171 }, 172 ComplexityFunc: func(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (i int, b bool) { 173 return exec.complexity, true 174 }, 175 } 176 177 exec.Executor = executor.New(exec.schema) 178 return exec 179 } 180 181 type TestExecutor struct { 182 *executor.Executor 183 schema graphql.ExecutableSchema 184 next chan struct{} 185 complexity int 186 } 187 188 func (e *TestExecutor) Schema() graphql.ExecutableSchema { 189 return e.schema 190 } 191 192 func (e *TestExecutor) SendNextSubscriptionMessage() { 193 select { 194 case e.next <- struct{}{}: 195 case <-time.After(1 * time.Second): 196 fmt.Println("WARNING: no active subscription") 197 } 198 } 199 200 func (e *TestExecutor) SetCalculatedComplexity(complexity int) { 201 e.complexity = complexity 202 }