...

Source file src/github.com/99designs/gqlgen/graphql/executor/executor_test.go

Documentation: github.com/99designs/gqlgen/graphql/executor

     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