github.com/geneva/gqlgen@v0.17.7-0.20230801155730-7b9317164836/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/geneva/gqlgen/graphql" 16 "github.com/geneva/gqlgen/graphql/handler/apollofederatedtracingv1" 17 "github.com/geneva/gqlgen/graphql/handler/apollofederatedtracingv1/generated" 18 "github.com/geneva/gqlgen/graphql/handler/extension" 19 "github.com/geneva/gqlgen/graphql/handler/lru" 20 "github.com/geneva/gqlgen/graphql/handler/testserver" 21 "github.com/geneva/gqlgen/graphql/handler/transport" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 "github.com/vektah/gqlparser/v2/gqlerror" 25 "google.golang.org/protobuf/proto" 26 ) 27 28 type alwaysError struct{} 29 30 func (a *alwaysError) Read(p []byte) (int, error) { 31 return 0, io.ErrUnexpectedEOF 32 } 33 34 func TestApolloTracing(t *testing.T) { 35 h := testserver.New() 36 h.AddTransport(transport.POST{}) 37 h.Use(&apollofederatedtracingv1.Tracer{}) 38 h.Use(&delayMiddleware{}) 39 40 resp := doRequest(h, http.MethodPost, "/graphql", `{"query":"{ name }"}`) 41 assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) 42 var respData struct { 43 Extensions struct { 44 FTV1 string `json:"ftv1"` 45 } `json:"extensions"` 46 } 47 require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respData)) 48 49 tracing := respData.Extensions.FTV1 50 pbuf, err := base64.StdEncoding.DecodeString(tracing) 51 require.Nil(t, err) 52 53 ftv1 := &generated.Trace{} 54 err = proto.Unmarshal(pbuf, ftv1) 55 require.Nil(t, err) 56 57 require.NotZero(t, ftv1.StartTime.Nanos) 58 require.Less(t, ftv1.StartTime.Nanos, ftv1.EndTime.Nanos) 59 require.EqualValues(t, ftv1.EndTime.Nanos-ftv1.StartTime.Nanos, ftv1.DurationNs) 60 61 fmt.Printf("%#v\n", resp.Body.String()) 62 require.Equal(t, "Query", ftv1.Root.Child[0].ParentType) 63 require.Equal(t, "name", ftv1.Root.Child[0].GetResponseName()) 64 require.Equal(t, "String!", ftv1.Root.Child[0].Type) 65 } 66 67 func TestApolloTracing_Concurrent(t *testing.T) { 68 h := testserver.New() 69 h.AddTransport(transport.POST{}) 70 h.Use(&apollofederatedtracingv1.Tracer{}) 71 for i := 0; i < 2; i++ { 72 go func() { 73 resp := doRequest(h, http.MethodPost, "/graphql", `{"query":"{ name }"}`) 74 assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) 75 var respData struct { 76 Extensions struct { 77 FTV1 string `json:"ftv1"` 78 } `json:"extensions"` 79 } 80 require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respData)) 81 82 tracing := respData.Extensions.FTV1 83 pbuf, err := base64.StdEncoding.DecodeString(tracing) 84 require.Nil(t, err) 85 86 ftv1 := &generated.Trace{} 87 err = proto.Unmarshal(pbuf, ftv1) 88 require.Nil(t, err) 89 require.NotZero(t, ftv1.StartTime.Nanos) 90 }() 91 } 92 } 93 94 func TestApolloTracing_withFail(t *testing.T) { 95 h := testserver.New() 96 h.AddTransport(transport.POST{}) 97 h.Use(extension.AutomaticPersistedQuery{Cache: lru.New(100)}) 98 h.Use(&apollofederatedtracingv1.Tracer{}) 99 100 resp := doRequest(h, http.MethodPost, "/graphql", `{"operationName":"A","extensions":{"persistedQuery":{"version":1,"sha256Hash":"338bbc16ac780daf81845339fbf0342061c1e9d2b702c96d3958a13a557083a6"}}}`) 101 assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) 102 b := resp.Body.Bytes() 103 t.Log(string(b)) 104 var respData struct { 105 Errors gqlerror.List 106 } 107 require.NoError(t, json.Unmarshal(b, &respData)) 108 require.Len(t, respData.Errors, 1) 109 require.Equal(t, "PersistedQueryNotFound", respData.Errors[0].Message) 110 } 111 112 func TestApolloTracing_withUnexpectedEOF(t *testing.T) { 113 h := testserver.New() 114 h.AddTransport(transport.POST{}) 115 h.Use(&apollofederatedtracingv1.Tracer{}) 116 117 resp := doRequestWithReader(h, http.MethodPost, "/graphql", &alwaysError{}) 118 assert.Equal(t, http.StatusOK, resp.Code) 119 } 120 func doRequest(handler http.Handler, method, target, body string) *httptest.ResponseRecorder { 121 return doRequestWithReader(handler, method, target, strings.NewReader(body)) 122 } 123 124 func doRequestWithReader(handler http.Handler, method string, target string, 125 reader io.Reader) *httptest.ResponseRecorder { 126 r := httptest.NewRequest(method, target, reader) 127 r.Header.Set("Content-Type", "application/json") 128 r.Header.Set("apollo-federation-include-trace", "ftv1") 129 w := httptest.NewRecorder() 130 131 handler.ServeHTTP(w, r) 132 return w 133 } 134 135 type delayMiddleware struct{} 136 137 func (*delayMiddleware) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { 138 time.Sleep(time.Millisecond) 139 return next(ctx) 140 } 141 142 func (*delayMiddleware) ExtensionName() string { 143 return "delay" 144 } 145 146 func (*delayMiddleware) Validate(schema graphql.ExecutableSchema) error { 147 return nil 148 }