...

Source file src/github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1/tracing_test.go

Documentation: github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1

     1  package apollofederatedtracingv1_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/vektah/gqlparser/v2/gqlerror"
    18  	"google.golang.org/protobuf/proto"
    19  
    20  	"github.com/99designs/gqlgen/graphql"
    21  	"github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1"
    22  	"github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1/generated"
    23  	"github.com/99designs/gqlgen/graphql/handler/extension"
    24  	"github.com/99designs/gqlgen/graphql/handler/lru"
    25  	"github.com/99designs/gqlgen/graphql/handler/testserver"
    26  	"github.com/99designs/gqlgen/graphql/handler/transport"
    27  )
    28  
    29  type alwaysError struct{}
    30  
    31  func (a *alwaysError) Read(p []byte) (int, error) {
    32  	return 0, io.ErrUnexpectedEOF
    33  }
    34  
    35  func TestApolloTracing(t *testing.T) {
    36  	h := testserver.New()
    37  	h.AddTransport(transport.POST{})
    38  	h.Use(&apollofederatedtracingv1.Tracer{})
    39  	h.Use(&delayMiddleware{})
    40  
    41  	resp := doRequest(h, http.MethodPost, "/graphql", `{"query":"{ name }"}`)
    42  	assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String())
    43  	var respData struct {
    44  		Extensions struct {
    45  			FTV1 string `json:"ftv1"`
    46  		} `json:"extensions"`
    47  	}
    48  	require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respData))
    49  
    50  	tracing := respData.Extensions.FTV1
    51  	pbuf, err := base64.StdEncoding.DecodeString(tracing)
    52  	require.Nil(t, err)
    53  
    54  	ftv1 := &generated.Trace{}
    55  	err = proto.Unmarshal(pbuf, ftv1)
    56  	require.Nil(t, err)
    57  
    58  	require.NotZero(t, ftv1.StartTime.Nanos)
    59  	require.Less(t, ftv1.StartTime.Nanos, ftv1.EndTime.Nanos)
    60  	require.EqualValues(t, ftv1.EndTime.Nanos-ftv1.StartTime.Nanos, ftv1.DurationNs)
    61  
    62  	fmt.Printf("%#v\n", resp.Body.String())
    63  	require.Equal(t, "Query", ftv1.Root.Child[0].ParentType)
    64  	require.Equal(t, "name", ftv1.Root.Child[0].GetResponseName())
    65  	require.Equal(t, "String!", ftv1.Root.Child[0].Type)
    66  }
    67  
    68  func TestApolloTracing_Concurrent(t *testing.T) {
    69  	h := testserver.New()
    70  	h.AddTransport(transport.POST{})
    71  	h.Use(&apollofederatedtracingv1.Tracer{})
    72  	for i := 0; i < 2; i++ {
    73  		go func() {
    74  			resp := doRequest(h, http.MethodPost, "/graphql", `{"query":"{ name }"}`)
    75  			assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String())
    76  			var respData struct {
    77  				Extensions struct {
    78  					FTV1 string `json:"ftv1"`
    79  				} `json:"extensions"`
    80  			}
    81  			require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respData))
    82  
    83  			tracing := respData.Extensions.FTV1
    84  			pbuf, err := base64.StdEncoding.DecodeString(tracing)
    85  			require.Nil(t, err)
    86  
    87  			ftv1 := &generated.Trace{}
    88  			err = proto.Unmarshal(pbuf, ftv1)
    89  			require.Nil(t, err)
    90  			require.NotZero(t, ftv1.StartTime.Nanos)
    91  		}()
    92  	}
    93  }
    94  
    95  func TestApolloTracing_withFail(t *testing.T) {
    96  	h := testserver.New()
    97  	h.AddTransport(transport.POST{})
    98  	h.Use(extension.AutomaticPersistedQuery{Cache: lru.New(100)})
    99  	h.Use(&apollofederatedtracingv1.Tracer{})
   100  
   101  	resp := doRequest(h, http.MethodPost, "/graphql", `{"operationName":"A","extensions":{"persistedQuery":{"version":1,"sha256Hash":"338bbc16ac780daf81845339fbf0342061c1e9d2b702c96d3958a13a557083a6"}}}`)
   102  	assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String())
   103  	b := resp.Body.Bytes()
   104  	t.Log(string(b))
   105  	var respData struct {
   106  		Errors gqlerror.List
   107  	}
   108  	require.NoError(t, json.Unmarshal(b, &respData))
   109  	require.Len(t, respData.Errors, 1)
   110  	require.Equal(t, "PersistedQueryNotFound", respData.Errors[0].Message)
   111  }
   112  
   113  // This tests that the tracing extension does not panic when the request
   114  // can't be processed for some reason. The specific cause is not
   115  // important, the scenario being tested is the response interceptor
   116  // being run to process the error response when no other interceptor
   117  // has been run, due to (for example) a problem creating the OperationContext.
   118  func TestApolloTracing_withMissingOp(t *testing.T) {
   119  	h := testserver.New()
   120  	h.AddTransport(transport.POST{})
   121  	h.Use(extension.AutomaticPersistedQuery{Cache: lru.New(100)})
   122  	h.Use(&apollofederatedtracingv1.Tracer{})
   123  
   124  	resp := doRequest(h, http.MethodPost, "/graphql", `{}`)
   125  	assert.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String())
   126  	b := resp.Body.Bytes()
   127  	t.Log(string(b))
   128  	var respData struct {
   129  		Errors gqlerror.List
   130  	}
   131  	require.NoError(t, json.Unmarshal(b, &respData))
   132  	require.Len(t, respData.Errors, 1)
   133  	require.Equal(t, "no operation provided", respData.Errors[0].Message)
   134  }
   135  
   136  func TestApolloTracing_withUnexpectedEOF(t *testing.T) {
   137  	h := testserver.New()
   138  	h.AddTransport(transport.POST{})
   139  	h.Use(&apollofederatedtracingv1.Tracer{})
   140  
   141  	resp := doRequestWithReader(h, http.MethodPost, "/graphql", &alwaysError{})
   142  	assert.Equal(t, http.StatusOK, resp.Code)
   143  }
   144  func doRequest(handler http.Handler, method, target, body string) *httptest.ResponseRecorder {
   145  	return doRequestWithReader(handler, method, target, strings.NewReader(body))
   146  }
   147  
   148  func doRequestWithReader(handler http.Handler, method string, target string,
   149  	reader io.Reader) *httptest.ResponseRecorder {
   150  	r := httptest.NewRequest(method, target, reader)
   151  	r.Header.Set("Content-Type", "application/json")
   152  	r.Header.Set("apollo-federation-include-trace", "ftv1")
   153  	w := httptest.NewRecorder()
   154  
   155  	handler.ServeHTTP(w, r)
   156  	return w
   157  }
   158  
   159  type delayMiddleware struct{}
   160  
   161  func (*delayMiddleware) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
   162  	time.Sleep(time.Millisecond)
   163  	return next(ctx)
   164  }
   165  
   166  func (*delayMiddleware) ExtensionName() string {
   167  	return "delay"
   168  }
   169  
   170  func (*delayMiddleware) Validate(schema graphql.ExecutableSchema) error {
   171  	return nil
   172  }
   173  

View as plain text