github.com/geneva/gqlgen@v0.17.7-0.20230801155730-7b9317164836/graphql/handler/testserver/testserver.go (about)

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