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
114
115
116
117
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