github.com/99designs/gqlgen@v0.17.45/graphql/handler/apollofederatedtracingv1/tracing_test.go (about) 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 }