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  }