github.com/spread-ai/gqlgen@v0.0.0-20221124102857-a6c8ef538a1d/graphql/executor/executor_test.go (about)

     1  package executor_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/spread-ai/gqlgen/graphql"
     8  	"github.com/spread-ai/gqlgen/graphql/errcode"
     9  	"github.com/spread-ai/gqlgen/graphql/executor/testexecutor"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/vektah/gqlparser/v2/ast"
    13  	"github.com/vektah/gqlparser/v2/gqlerror"
    14  	"github.com/vektah/gqlparser/v2/parser"
    15  )
    16  
    17  func TestExecutor(t *testing.T) {
    18  	exec := testexecutor.New()
    19  
    20  	t.Run("calls query on executable schema", func(t *testing.T) {
    21  		resp := query(exec, "", "{name}")
    22  		assert.Equal(t, `{"name":"test"}`, string(resp.Data))
    23  	})
    24  
    25  	t.Run("validates operation", func(t *testing.T) {
    26  		t.Run("no operation", func(t *testing.T) {
    27  			resp := query(exec, "", "")
    28  			assert.Equal(t, "", string(resp.Data))
    29  			assert.Equal(t, 1, len(resp.Errors))
    30  			assert.Equal(t, errcode.ValidationFailed, resp.Errors[0].Extensions["code"])
    31  		})
    32  
    33  		t.Run("bad operation", func(t *testing.T) {
    34  			resp := query(exec, "badOp", "query test { name }")
    35  			assert.Equal(t, "", string(resp.Data))
    36  			assert.Equal(t, 1, len(resp.Errors))
    37  			assert.Equal(t, errcode.ValidationFailed, resp.Errors[0].Extensions["code"])
    38  		})
    39  	})
    40  
    41  	t.Run("invokes operation middleware in order", func(t *testing.T) {
    42  		var calls []string
    43  		exec.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
    44  			calls = append(calls, "first")
    45  			return next(ctx)
    46  		})
    47  		exec.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
    48  			calls = append(calls, "second")
    49  			return next(ctx)
    50  		})
    51  
    52  		resp := query(exec, "", "{name}")
    53  		assert.Equal(t, `{"name":"test"}`, string(resp.Data))
    54  		assert.Equal(t, []string{"first", "second"}, calls)
    55  	})
    56  
    57  	t.Run("invokes response middleware in order", func(t *testing.T) {
    58  		var calls []string
    59  		exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
    60  			calls = append(calls, "first")
    61  			return next(ctx)
    62  		})
    63  		exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
    64  			calls = append(calls, "second")
    65  			return next(ctx)
    66  		})
    67  
    68  		resp := query(exec, "", "{name}")
    69  		assert.Equal(t, `{"name":"test"}`, string(resp.Data))
    70  		assert.Equal(t, []string{"first", "second"}, calls)
    71  	})
    72  
    73  	t.Run("invokes root field middleware in order", func(t *testing.T) {
    74  		var calls []string
    75  		exec.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
    76  			calls = append(calls, "first")
    77  			return next(ctx)
    78  		})
    79  		exec.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
    80  			calls = append(calls, "second")
    81  			return next(ctx)
    82  		})
    83  
    84  		resp := query(exec, "", "{name}")
    85  		assert.Equal(t, `{"name":"test"}`, string(resp.Data))
    86  		assert.Equal(t, []string{"first", "second"}, calls)
    87  	})
    88  
    89  	t.Run("invokes field middleware in order", func(t *testing.T) {
    90  		var calls []string
    91  		exec.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
    92  			calls = append(calls, "first")
    93  			return next(ctx)
    94  		})
    95  		exec.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
    96  			calls = append(calls, "second")
    97  			return next(ctx)
    98  		})
    99  
   100  		resp := query(exec, "", "{name}")
   101  		assert.Equal(t, `{"name":"test"}`, string(resp.Data))
   102  		assert.Equal(t, []string{"first", "second"}, calls)
   103  	})
   104  
   105  	t.Run("invokes operation mutators", func(t *testing.T) {
   106  		var calls []string
   107  		exec.Use(&testParamMutator{
   108  			Mutate: func(ctx context.Context, req *graphql.RawParams) *gqlerror.Error {
   109  				calls = append(calls, "param")
   110  				return nil
   111  			},
   112  		})
   113  		exec.Use(&testCtxMutator{
   114  			Mutate: func(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error {
   115  				calls = append(calls, "context")
   116  				return nil
   117  			},
   118  		})
   119  		resp := query(exec, "", "{name}")
   120  		assert.Equal(t, `{"name":"test"}`, string(resp.Data))
   121  		assert.Equal(t, []string{"param", "context"}, calls)
   122  	})
   123  
   124  	t.Run("get query parse error in AroundResponses", func(t *testing.T) {
   125  		var errors1 gqlerror.List
   126  		var errors2 gqlerror.List
   127  		exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
   128  			resp := next(ctx)
   129  			errors1 = graphql.GetErrors(ctx)
   130  			errors2 = resp.Errors
   131  			return resp
   132  		})
   133  
   134  		resp := query(exec, "", "invalid")
   135  		assert.Equal(t, "", string(resp.Data))
   136  		assert.Equal(t, 1, len(resp.Errors))
   137  		assert.Equal(t, 1, len(errors1))
   138  		assert.Equal(t, 1, len(errors2))
   139  	})
   140  
   141  	t.Run("query caching", func(t *testing.T) {
   142  		ctx := context.Background()
   143  		cache := &graphql.MapCache{}
   144  		exec.SetQueryCache(cache)
   145  		qry := `query Foo {name}`
   146  
   147  		t.Run("cache miss populates cache", func(t *testing.T) {
   148  			resp := query(exec, "Foo", qry)
   149  			assert.Equal(t, `{"name":"test"}`, string(resp.Data))
   150  
   151  			cacheDoc, ok := cache.Get(ctx, qry)
   152  			require.True(t, ok)
   153  			require.Equal(t, "Foo", cacheDoc.(*ast.QueryDocument).Operations[0].Name)
   154  		})
   155  
   156  		t.Run("cache hits use document from cache", func(t *testing.T) {
   157  			doc, err := parser.ParseQuery(&ast.Source{Input: `query Bar {name}`})
   158  			require.Nil(t, err)
   159  			cache.Add(ctx, qry, doc)
   160  
   161  			resp := query(exec, "Bar", qry)
   162  			assert.Equal(t, `{"name":"test"}`, string(resp.Data))
   163  
   164  			cacheDoc, ok := cache.Get(ctx, qry)
   165  			require.True(t, ok)
   166  			require.Equal(t, "Bar", cacheDoc.(*ast.QueryDocument).Operations[0].Name)
   167  		})
   168  	})
   169  }
   170  
   171  type testParamMutator struct {
   172  	Mutate func(context.Context, *graphql.RawParams) *gqlerror.Error
   173  }
   174  
   175  func (m *testParamMutator) ExtensionName() string {
   176  	return "Operation: Mutate Parameters"
   177  }
   178  
   179  func (m *testParamMutator) Validate(s graphql.ExecutableSchema) error {
   180  	return nil
   181  }
   182  
   183  func (m *testParamMutator) MutateOperationParameters(ctx context.Context, r *graphql.RawParams) *gqlerror.Error {
   184  	return m.Mutate(ctx, r)
   185  }
   186  
   187  type testCtxMutator struct {
   188  	Mutate func(context.Context, *graphql.OperationContext) *gqlerror.Error
   189  }
   190  
   191  func (m *testCtxMutator) ExtensionName() string {
   192  	return "Operation: Mutate the Context"
   193  }
   194  
   195  func (m *testCtxMutator) Validate(s graphql.ExecutableSchema) error {
   196  	return nil
   197  }
   198  
   199  func (m *testCtxMutator) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error {
   200  	return m.Mutate(ctx, rc)
   201  }
   202  
   203  func TestErrorServer(t *testing.T) {
   204  	exec := testexecutor.NewError()
   205  
   206  	t.Run("get resolver error in AroundResponses", func(t *testing.T) {
   207  		var errors1 gqlerror.List
   208  		var errors2 gqlerror.List
   209  		exec.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
   210  			resp := next(ctx)
   211  			errors1 = graphql.GetErrors(ctx)
   212  			errors2 = resp.Errors
   213  			return resp
   214  		})
   215  
   216  		resp := query(exec, "", "{name}")
   217  		assert.Equal(t, "null", string(resp.Data))
   218  		assert.Equal(t, 1, len(errors1))
   219  		assert.Equal(t, 1, len(errors2))
   220  	})
   221  }
   222  
   223  func query(exec *testexecutor.TestExecutor, op, q string) *graphql.Response {
   224  	ctx := graphql.StartOperationTrace(context.Background())
   225  	now := graphql.Now()
   226  	rc, err := exec.CreateOperationContext(ctx, &graphql.RawParams{
   227  		Query:         q,
   228  		OperationName: op,
   229  		ReadTime: graphql.TraceTiming{
   230  			Start: now,
   231  			End:   now,
   232  		},
   233  	})
   234  	if err != nil {
   235  		return exec.DispatchError(ctx, err)
   236  	}
   237  
   238  	resp, ctx2 := exec.DispatchOperation(ctx, rc)
   239  	return resp(ctx2)
   240  }