1
2
3
4
5
6
7 package semconvutil
8
9 import (
10 "net/http"
11 "net/http/httptest"
12 "net/url"
13 "strconv"
14 "testing"
15
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18
19 "go.opentelemetry.io/otel/attribute"
20 "go.opentelemetry.io/otel/codes"
21 )
22
23 func TestHTTPClientResponse(t *testing.T) {
24 const stat, n = 201, 397
25 resp := &http.Response{
26 StatusCode: stat,
27 ContentLength: n,
28 }
29 got := HTTPClientResponse(resp)
30 assert.Equal(t, 2, cap(got), "slice capacity")
31 assert.ElementsMatch(t, []attribute.KeyValue{
32 attribute.Key("http.status_code").Int(stat),
33 attribute.Key("http.response_content_length").Int(n),
34 }, got)
35 }
36
37 func TestHTTPSClientRequest(t *testing.T) {
38 req := &http.Request{
39 Method: http.MethodGet,
40 URL: &url.URL{
41 Scheme: "https",
42 Host: "127.0.0.1:443",
43 Path: "/resource",
44 },
45 Proto: "HTTP/1.0",
46 ProtoMajor: 1,
47 ProtoMinor: 0,
48 }
49
50 assert.ElementsMatch(
51 t,
52 []attribute.KeyValue{
53 attribute.String("http.method", "GET"),
54 attribute.String("http.url", "https://127.0.0.1:443/resource"),
55 attribute.String("net.peer.name", "127.0.0.1"),
56 },
57 HTTPClientRequest(req),
58 )
59 }
60
61 func TestHTTPSClientRequestMetrics(t *testing.T) {
62 req := &http.Request{
63 Method: http.MethodGet,
64 URL: &url.URL{
65 Scheme: "https",
66 Host: "127.0.0.1:443",
67 Path: "/resource",
68 },
69 Proto: "HTTP/1.0",
70 ProtoMajor: 1,
71 ProtoMinor: 0,
72 }
73
74 assert.ElementsMatch(
75 t,
76 []attribute.KeyValue{
77 attribute.String("http.method", "GET"),
78 attribute.String("net.peer.name", "127.0.0.1"),
79 },
80 HTTPClientRequestMetrics(req),
81 )
82 }
83
84 func TestHTTPClientRequest(t *testing.T) {
85 const (
86 user = "alice"
87 n = 128
88 agent = "Go-http-client/1.1"
89 )
90 req := &http.Request{
91 Method: http.MethodGet,
92 URL: &url.URL{
93 Scheme: "http",
94 Host: "127.0.0.1:8080",
95 Path: "/resource",
96 },
97 Proto: "HTTP/1.0",
98 ProtoMajor: 1,
99 ProtoMinor: 0,
100 Header: http.Header{
101 "User-Agent": []string{agent},
102 },
103 ContentLength: n,
104 }
105 req.SetBasicAuth(user, "pswrd")
106
107 assert.ElementsMatch(
108 t,
109 []attribute.KeyValue{
110 attribute.String("http.method", "GET"),
111 attribute.String("http.url", "http://127.0.0.1:8080/resource"),
112 attribute.String("net.peer.name", "127.0.0.1"),
113 attribute.Int("net.peer.port", 8080),
114 attribute.String("user_agent.original", agent),
115 attribute.Int("http.request_content_length", n),
116 },
117 HTTPClientRequest(req),
118 )
119 }
120
121 func TestHTTPClientRequestMetrics(t *testing.T) {
122 const (
123 user = "alice"
124 n = 128
125 agent = "Go-http-client/1.1"
126 )
127 req := &http.Request{
128 Method: http.MethodGet,
129 URL: &url.URL{
130 Scheme: "http",
131 Host: "127.0.0.1:8080",
132 Path: "/resource",
133 },
134 Proto: "HTTP/1.0",
135 ProtoMajor: 1,
136 ProtoMinor: 0,
137 Header: http.Header{
138 "User-Agent": []string{agent},
139 },
140 ContentLength: n,
141 }
142 req.SetBasicAuth(user, "pswrd")
143
144 assert.ElementsMatch(
145 t,
146 []attribute.KeyValue{
147 attribute.String("http.method", "GET"),
148 attribute.String("net.peer.name", "127.0.0.1"),
149 attribute.Int("net.peer.port", 8080),
150 },
151 HTTPClientRequestMetrics(req),
152 )
153 }
154
155 func TestHTTPClientRequestRequired(t *testing.T) {
156 req := new(http.Request)
157 var got []attribute.KeyValue
158 assert.NotPanics(t, func() { got = HTTPClientRequest(req) })
159 want := []attribute.KeyValue{
160 attribute.String("http.method", "GET"),
161 attribute.String("http.url", ""),
162 attribute.String("net.peer.name", ""),
163 }
164 assert.Equal(t, want, got)
165 }
166
167 func TestHTTPServerRequest(t *testing.T) {
168 got := make(chan *http.Request, 1)
169 handler := func(w http.ResponseWriter, r *http.Request) {
170 got <- r
171 w.WriteHeader(http.StatusOK)
172 }
173
174 srv := httptest.NewServer(http.HandlerFunc(handler))
175 defer srv.Close()
176
177 srvURL, err := url.Parse(srv.URL)
178 require.NoError(t, err)
179 srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32)
180 require.NoError(t, err)
181
182 resp, err := srv.Client().Get(srv.URL)
183 require.NoError(t, err)
184 require.NoError(t, resp.Body.Close())
185
186 req := <-got
187 peer, peerPort := splitHostPort(req.RemoteAddr)
188
189 const user = "alice"
190 req.SetBasicAuth(user, "pswrd")
191
192 const clientIP = "127.0.0.5"
193 req.Header.Add("X-Forwarded-For", clientIP)
194
195 assert.ElementsMatch(t,
196 []attribute.KeyValue{
197 attribute.String("http.method", "GET"),
198 attribute.String("http.scheme", "http"),
199 attribute.String("net.host.name", srvURL.Hostname()),
200 attribute.Int("net.host.port", int(srvPort)),
201 attribute.String("net.sock.peer.addr", peer),
202 attribute.Int("net.sock.peer.port", peerPort),
203 attribute.String("user_agent.original", "Go-http-client/1.1"),
204 attribute.String("http.client_ip", clientIP),
205 attribute.String("net.protocol.version", "1.1"),
206 attribute.String("http.target", "/"),
207 },
208 HTTPServerRequest("", req))
209 }
210
211 func TestHTTPServerRequestMetrics(t *testing.T) {
212 got := make(chan *http.Request, 1)
213 handler := func(w http.ResponseWriter, r *http.Request) {
214 got <- r
215 w.WriteHeader(http.StatusOK)
216 }
217
218 srv := httptest.NewServer(http.HandlerFunc(handler))
219 defer srv.Close()
220
221 srvURL, err := url.Parse(srv.URL)
222 require.NoError(t, err)
223 srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32)
224 require.NoError(t, err)
225
226 resp, err := srv.Client().Get(srv.URL)
227 require.NoError(t, err)
228 require.NoError(t, resp.Body.Close())
229
230 req := <-got
231
232 assert.ElementsMatch(t,
233 []attribute.KeyValue{
234 attribute.String("http.method", "GET"),
235 attribute.String("http.scheme", "http"),
236 attribute.String("net.host.name", srvURL.Hostname()),
237 attribute.Int("net.host.port", int(srvPort)),
238 attribute.String("net.protocol.name", "http"),
239 attribute.String("net.protocol.version", "1.1"),
240 },
241 HTTPServerRequestMetrics("", req))
242 }
243
244 func TestHTTPServerName(t *testing.T) {
245 req := new(http.Request)
246 var got []attribute.KeyValue
247 const (
248 host = "test.semconv.server"
249 port = 8080
250 )
251 portStr := strconv.Itoa(port)
252 server := host + ":" + portStr
253 assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) })
254 assert.Contains(t, got, attribute.String("net.host.name", host))
255 assert.Contains(t, got, attribute.Int("net.host.port", port))
256
257 req = &http.Request{Host: "alt.host.name:" + portStr}
258
259
260 assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) })
261 assert.Contains(t, got, attribute.String("net.host.name", host))
262 assert.Contains(t, got, attribute.Int("net.host.port", port))
263 }
264
265 func TestHTTPServerRequestFailsGracefully(t *testing.T) {
266 req := new(http.Request)
267 var got []attribute.KeyValue
268 assert.NotPanics(t, func() { got = HTTPServerRequest("", req) })
269 want := []attribute.KeyValue{
270 attribute.String("http.method", "GET"),
271 attribute.String("http.scheme", "http"),
272 attribute.String("net.host.name", ""),
273 }
274 assert.ElementsMatch(t, want, got)
275 }
276
277 func TestHTTPMethod(t *testing.T) {
278 assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST"))
279 assert.Equal(t, attribute.String("http.method", "GET"), hc.method(""))
280 assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage"))
281 }
282
283 func TestHTTPScheme(t *testing.T) {
284 assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false))
285 assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true))
286 }
287
288 func TestHTTPServerClientIP(t *testing.T) {
289 tests := []struct {
290 xForwardedFor string
291 want string
292 }{
293 {"", ""},
294 {"127.0.0.1", "127.0.0.1"},
295 {"127.0.0.1,127.0.0.5", "127.0.0.1"},
296 }
297 for _, test := range tests {
298 got := serverClientIP(test.xForwardedFor)
299 assert.Equal(t, test.want, got, test.xForwardedFor)
300 }
301 }
302
303 func TestRequiredHTTPPort(t *testing.T) {
304 tests := []struct {
305 https bool
306 port int
307 want int
308 }{
309 {true, 443, -1},
310 {true, 80, 80},
311 {true, 8081, 8081},
312 {false, 443, 443},
313 {false, 80, -1},
314 {false, 8080, 8080},
315 }
316 for _, test := range tests {
317 got := requiredHTTPPort(test.https, test.port)
318 assert.Equal(t, test.want, got, test.https, test.port)
319 }
320 }
321
322 func TestFirstHostPort(t *testing.T) {
323 host, port := "127.0.0.1", 8080
324 hostport := "127.0.0.1:8080"
325 sources := [][]string{
326 {hostport},
327 {"", hostport},
328 {"", "", hostport},
329 {"", "", hostport, ""},
330 {"", "", hostport, "127.0.0.3:80"},
331 }
332
333 for _, src := range sources {
334 h, p := firstHostPort(src...)
335 assert.Equal(t, host, h, src)
336 assert.Equal(t, port, p, src)
337 }
338 }
339
340 func TestHTTPClientStatus(t *testing.T) {
341 tests := []struct {
342 code int
343 stat codes.Code
344 msg bool
345 }{
346 {0, codes.Error, true},
347 {http.StatusContinue, codes.Unset, false},
348 {http.StatusSwitchingProtocols, codes.Unset, false},
349 {http.StatusProcessing, codes.Unset, false},
350 {http.StatusEarlyHints, codes.Unset, false},
351 {http.StatusOK, codes.Unset, false},
352 {http.StatusCreated, codes.Unset, false},
353 {http.StatusAccepted, codes.Unset, false},
354 {http.StatusNonAuthoritativeInfo, codes.Unset, false},
355 {http.StatusNoContent, codes.Unset, false},
356 {http.StatusResetContent, codes.Unset, false},
357 {http.StatusPartialContent, codes.Unset, false},
358 {http.StatusMultiStatus, codes.Unset, false},
359 {http.StatusAlreadyReported, codes.Unset, false},
360 {http.StatusIMUsed, codes.Unset, false},
361 {http.StatusMultipleChoices, codes.Unset, false},
362 {http.StatusMovedPermanently, codes.Unset, false},
363 {http.StatusFound, codes.Unset, false},
364 {http.StatusSeeOther, codes.Unset, false},
365 {http.StatusNotModified, codes.Unset, false},
366 {http.StatusUseProxy, codes.Unset, false},
367 {306, codes.Unset, false},
368 {http.StatusTemporaryRedirect, codes.Unset, false},
369 {http.StatusPermanentRedirect, codes.Unset, false},
370 {http.StatusBadRequest, codes.Error, false},
371 {http.StatusUnauthorized, codes.Error, false},
372 {http.StatusPaymentRequired, codes.Error, false},
373 {http.StatusForbidden, codes.Error, false},
374 {http.StatusNotFound, codes.Error, false},
375 {http.StatusMethodNotAllowed, codes.Error, false},
376 {http.StatusNotAcceptable, codes.Error, false},
377 {http.StatusProxyAuthRequired, codes.Error, false},
378 {http.StatusRequestTimeout, codes.Error, false},
379 {http.StatusConflict, codes.Error, false},
380 {http.StatusGone, codes.Error, false},
381 {http.StatusLengthRequired, codes.Error, false},
382 {http.StatusPreconditionFailed, codes.Error, false},
383 {http.StatusRequestEntityTooLarge, codes.Error, false},
384 {http.StatusRequestURITooLong, codes.Error, false},
385 {http.StatusUnsupportedMediaType, codes.Error, false},
386 {http.StatusRequestedRangeNotSatisfiable, codes.Error, false},
387 {http.StatusExpectationFailed, codes.Error, false},
388 {http.StatusTeapot, codes.Error, false},
389 {http.StatusMisdirectedRequest, codes.Error, false},
390 {http.StatusUnprocessableEntity, codes.Error, false},
391 {http.StatusLocked, codes.Error, false},
392 {http.StatusFailedDependency, codes.Error, false},
393 {http.StatusTooEarly, codes.Error, false},
394 {http.StatusUpgradeRequired, codes.Error, false},
395 {http.StatusPreconditionRequired, codes.Error, false},
396 {http.StatusTooManyRequests, codes.Error, false},
397 {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false},
398 {http.StatusUnavailableForLegalReasons, codes.Error, false},
399 {499, codes.Error, false},
400 {http.StatusInternalServerError, codes.Error, false},
401 {http.StatusNotImplemented, codes.Error, false},
402 {http.StatusBadGateway, codes.Error, false},
403 {http.StatusServiceUnavailable, codes.Error, false},
404 {http.StatusGatewayTimeout, codes.Error, false},
405 {http.StatusHTTPVersionNotSupported, codes.Error, false},
406 {http.StatusVariantAlsoNegotiates, codes.Error, false},
407 {http.StatusInsufficientStorage, codes.Error, false},
408 {http.StatusLoopDetected, codes.Error, false},
409 {http.StatusNotExtended, codes.Error, false},
410 {http.StatusNetworkAuthenticationRequired, codes.Error, false},
411 {600, codes.Error, true},
412 }
413
414 for _, test := range tests {
415 t.Run(strconv.Itoa(test.code), func(t *testing.T) {
416 c, msg := HTTPClientStatus(test.code)
417 assert.Equal(t, test.stat, c)
418 if test.msg && msg == "" {
419 t.Errorf("expected non-empty message for %d", test.code)
420 } else if !test.msg && msg != "" {
421 t.Errorf("expected empty message for %d, got: %s", test.code, msg)
422 }
423 })
424 }
425 }
426
427 func TestHTTPServerStatus(t *testing.T) {
428 tests := []struct {
429 code int
430 stat codes.Code
431 msg bool
432 }{
433 {0, codes.Error, true},
434 {http.StatusContinue, codes.Unset, false},
435 {http.StatusSwitchingProtocols, codes.Unset, false},
436 {http.StatusProcessing, codes.Unset, false},
437 {http.StatusEarlyHints, codes.Unset, false},
438 {http.StatusOK, codes.Unset, false},
439 {http.StatusCreated, codes.Unset, false},
440 {http.StatusAccepted, codes.Unset, false},
441 {http.StatusNonAuthoritativeInfo, codes.Unset, false},
442 {http.StatusNoContent, codes.Unset, false},
443 {http.StatusResetContent, codes.Unset, false},
444 {http.StatusPartialContent, codes.Unset, false},
445 {http.StatusMultiStatus, codes.Unset, false},
446 {http.StatusAlreadyReported, codes.Unset, false},
447 {http.StatusIMUsed, codes.Unset, false},
448 {http.StatusMultipleChoices, codes.Unset, false},
449 {http.StatusMovedPermanently, codes.Unset, false},
450 {http.StatusFound, codes.Unset, false},
451 {http.StatusSeeOther, codes.Unset, false},
452 {http.StatusNotModified, codes.Unset, false},
453 {http.StatusUseProxy, codes.Unset, false},
454 {306, codes.Unset, false},
455 {http.StatusTemporaryRedirect, codes.Unset, false},
456 {http.StatusPermanentRedirect, codes.Unset, false},
457 {http.StatusBadRequest, codes.Unset, false},
458 {http.StatusUnauthorized, codes.Unset, false},
459 {http.StatusPaymentRequired, codes.Unset, false},
460 {http.StatusForbidden, codes.Unset, false},
461 {http.StatusNotFound, codes.Unset, false},
462 {http.StatusMethodNotAllowed, codes.Unset, false},
463 {http.StatusNotAcceptable, codes.Unset, false},
464 {http.StatusProxyAuthRequired, codes.Unset, false},
465 {http.StatusRequestTimeout, codes.Unset, false},
466 {http.StatusConflict, codes.Unset, false},
467 {http.StatusGone, codes.Unset, false},
468 {http.StatusLengthRequired, codes.Unset, false},
469 {http.StatusPreconditionFailed, codes.Unset, false},
470 {http.StatusRequestEntityTooLarge, codes.Unset, false},
471 {http.StatusRequestURITooLong, codes.Unset, false},
472 {http.StatusUnsupportedMediaType, codes.Unset, false},
473 {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false},
474 {http.StatusExpectationFailed, codes.Unset, false},
475 {http.StatusTeapot, codes.Unset, false},
476 {http.StatusMisdirectedRequest, codes.Unset, false},
477 {http.StatusUnprocessableEntity, codes.Unset, false},
478 {http.StatusLocked, codes.Unset, false},
479 {http.StatusFailedDependency, codes.Unset, false},
480 {http.StatusTooEarly, codes.Unset, false},
481 {http.StatusUpgradeRequired, codes.Unset, false},
482 {http.StatusPreconditionRequired, codes.Unset, false},
483 {http.StatusTooManyRequests, codes.Unset, false},
484 {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false},
485 {http.StatusUnavailableForLegalReasons, codes.Unset, false},
486 {499, codes.Unset, false},
487 {http.StatusInternalServerError, codes.Error, false},
488 {http.StatusNotImplemented, codes.Error, false},
489 {http.StatusBadGateway, codes.Error, false},
490 {http.StatusServiceUnavailable, codes.Error, false},
491 {http.StatusGatewayTimeout, codes.Error, false},
492 {http.StatusHTTPVersionNotSupported, codes.Error, false},
493 {http.StatusVariantAlsoNegotiates, codes.Error, false},
494 {http.StatusInsufficientStorage, codes.Error, false},
495 {http.StatusLoopDetected, codes.Error, false},
496 {http.StatusNotExtended, codes.Error, false},
497 {http.StatusNetworkAuthenticationRequired, codes.Error, false},
498 {600, codes.Error, true},
499 }
500
501 for _, test := range tests {
502 c, msg := HTTPServerStatus(test.code)
503 assert.Equal(t, test.stat, c)
504 if test.msg && msg == "" {
505 t.Errorf("expected non-empty message for %d", test.code)
506 } else if !test.msg && msg != "" {
507 t.Errorf("expected empty message for %d, got: %s", test.code, msg)
508 }
509 }
510 }
511
View as plain text