1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
17 "git.arvados.org/arvados.git/sdk/go/ctxlog"
18 "github.com/sirupsen/logrus"
19 check "gopkg.in/check.v1"
22 func Test(t *testing.T) {
26 var _ = check.Suite(&Suite{})
34 func (s *Suite) SetUpTest(c *check.C) {
35 s.logdata = bytes.NewBuffer(nil)
38 s.log.Formatter = &logrus.JSONFormatter{
39 TimestampFormat: time.RFC3339Nano,
41 s.ctx = ctxlog.Context(context.Background(), s.log)
44 func (s *Suite) TestWithDeadline(c *check.C) {
45 req, err := http.NewRequest("GET", "https://foo.example/bar", nil)
46 c.Assert(err, check.IsNil)
48 // Short timeout cancels context in <1s
49 resp := httptest.NewRecorder()
50 HandlerWithDeadline(time.Millisecond, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
52 case <-req.Context().Done():
54 case <-time.After(time.Second):
57 })).ServeHTTP(resp, req.WithContext(s.ctx))
58 c.Check(resp.Body.String(), check.Equals, "ok")
60 // Long timeout does not cancel context in <1ms
61 resp = httptest.NewRecorder()
62 HandlerWithDeadline(time.Second, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
64 case <-req.Context().Done():
65 c.Error("request context done too soon")
66 case <-time.After(time.Millisecond):
69 })).ServeHTTP(resp, req.WithContext(s.ctx))
70 c.Check(resp.Body.String(), check.Equals, "ok")
73 func (s *Suite) TestLogRequests(c *check.C) {
74 h := AddRequestIDs(LogRequests(
75 http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
76 w.Write([]byte("hello world"))
79 req, err := http.NewRequest("GET", "https://foo.example/bar", nil)
80 req.Header.Set("X-Forwarded-For", "1.2.3.4:12345")
81 c.Assert(err, check.IsNil)
82 resp := httptest.NewRecorder()
84 h.ServeHTTP(resp, req.WithContext(s.ctx))
86 dec := json.NewDecoder(s.logdata)
88 gotReq := make(map[string]interface{})
89 err = dec.Decode(&gotReq)
90 c.Check(err, check.IsNil)
92 c.Check(gotReq["RequestID"], check.Matches, "req-[a-z0-9]{20}")
93 c.Check(gotReq["reqForwardedFor"], check.Equals, "1.2.3.4:12345")
94 c.Check(gotReq["msg"], check.Equals, "request")
96 gotResp := make(map[string]interface{})
97 err = dec.Decode(&gotResp)
98 c.Check(err, check.IsNil)
99 c.Logf("%#v", gotResp)
100 c.Check(gotResp["RequestID"], check.Equals, gotReq["RequestID"])
101 c.Check(gotResp["reqForwardedFor"], check.Equals, "1.2.3.4:12345")
102 c.Check(gotResp["msg"], check.Equals, "response")
104 c.Assert(gotResp["time"], check.FitsTypeOf, "")
105 _, err = time.Parse(time.RFC3339Nano, gotResp["time"].(string))
106 c.Check(err, check.IsNil)
108 for _, key := range []string{"timeToStatus", "timeWriteBody", "timeTotal"} {
109 c.Assert(gotResp[key], check.FitsTypeOf, float64(0))
110 c.Check(gotResp[key].(float64), check.Not(check.Equals), float64(0))
114 func (s *Suite) TestLogErrorBody(c *check.C) {
115 dec := json.NewDecoder(s.logdata)
117 for _, trial := range []struct {
124 {"ok", 200, "hello world", false, ""},
125 {"redir", 302, "<a href='http://foo.example/baz'>redir</a>", false, ""},
126 {"4xx short body", 400, "oops", true, "oops"},
127 {"4xx long body", 400, fmt.Sprintf("%0*d", sniffBytes*2, 1), true, fmt.Sprintf("%0*d", sniffBytes, 0)},
128 {"5xx empty body", 500, "", true, ""},
130 comment := check.Commentf("in trial: %q", trial.label)
132 req, err := http.NewRequest("GET", "https://foo.example/bar", nil)
133 c.Assert(err, check.IsNil)
134 resp := httptest.NewRecorder()
137 http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
138 w.WriteHeader(trial.statusCode)
139 w.Write([]byte(trial.sentBody))
141 ).ServeHTTP(resp, req.WithContext(s.ctx))
143 gotReq := make(map[string]interface{})
144 err = dec.Decode(&gotReq)
145 c.Check(err, check.IsNil)
146 c.Logf("%#v", gotReq)
147 gotResp := make(map[string]interface{})
148 err = dec.Decode(&gotResp)
149 c.Check(err, check.IsNil)
150 c.Logf("%#v", gotResp)
152 c.Check(gotResp["respBody"], check.Equals, trial.expectBody, comment)
154 c.Check(gotResp["respBody"], check.IsNil, comment)