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