1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
19 "git.arvados.org/arvados.git/sdk/go/ctxlog"
20 "github.com/sirupsen/logrus"
21 check "gopkg.in/check.v1"
24 func Test(t *testing.T) {
28 var _ = check.Suite(&Suite{})
36 func (s *Suite) SetUpTest(c *check.C) {
37 s.logdata = bytes.NewBuffer(nil)
40 s.log.Formatter = &logrus.JSONFormatter{
41 TimestampFormat: time.RFC3339Nano,
43 s.ctx = ctxlog.Context(context.Background(), s.log)
46 func (s *Suite) TestWithDeadline(c *check.C) {
47 req, err := http.NewRequest("GET", "https://foo.example/bar", nil)
48 c.Assert(err, check.IsNil)
50 // Short timeout cancels context in <1s
51 resp := httptest.NewRecorder()
52 HandlerWithDeadline(time.Millisecond, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
54 case <-req.Context().Done():
56 case <-time.After(time.Second):
59 })).ServeHTTP(resp, req.WithContext(s.ctx))
60 c.Check(resp.Body.String(), check.Equals, "ok")
62 // Long timeout does not cancel context in <1ms
63 resp = httptest.NewRecorder()
64 HandlerWithDeadline(time.Second, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
66 case <-req.Context().Done():
67 c.Error("request context done too soon")
68 case <-time.After(time.Millisecond):
71 })).ServeHTTP(resp, req.WithContext(s.ctx))
72 c.Check(resp.Body.String(), check.Equals, "ok")
75 func (s *Suite) TestExemptFromDeadline(c *check.C) {
79 Handler: HandlerWithDeadline(time.Millisecond, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
80 if req.URL.Path == "/exempt" {
81 ExemptFromDeadline(req)
83 time.Sleep(time.Second / 10)
84 if req.Context().Err() != nil {
90 BaseContext: func(net.Listener) context.Context { return s.ctx },
96 resp, err := http.Get("http://" + srv.Addr + "/normal")
97 c.Assert(err, check.IsNil)
98 c.Check(resp.StatusCode, check.Equals, 499)
99 body, err := ioutil.ReadAll(resp.Body)
100 c.Check(err, check.IsNil)
101 c.Check(string(body), check.Equals, "")
103 resp, err = http.Get("http://" + srv.Addr + "/exempt")
104 c.Assert(err, check.IsNil)
105 c.Check(resp.StatusCode, check.Equals, 200)
106 body, err = ioutil.ReadAll(resp.Body)
107 c.Check(err, check.IsNil)
108 c.Check(string(body), check.Equals, "ok")
111 func (s *Suite) TestLogRequests(c *check.C) {
112 h := AddRequestIDs(LogRequests(
113 http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
114 w.Write([]byte("hello world"))
117 req, err := http.NewRequest("GET", "https://foo.example/bar", nil)
118 req.Header.Set("X-Forwarded-For", "1.2.3.4:12345")
119 c.Assert(err, check.IsNil)
120 resp := httptest.NewRecorder()
122 h.ServeHTTP(resp, req.WithContext(s.ctx))
124 dec := json.NewDecoder(s.logdata)
126 gotReq := make(map[string]interface{})
127 err = dec.Decode(&gotReq)
128 c.Check(err, check.IsNil)
129 c.Logf("%#v", gotReq)
130 c.Check(gotReq["RequestID"], check.Matches, "req-[a-z0-9]{20}")
131 c.Check(gotReq["reqForwardedFor"], check.Equals, "1.2.3.4:12345")
132 c.Check(gotReq["msg"], check.Equals, "request")
134 gotResp := make(map[string]interface{})
135 err = dec.Decode(&gotResp)
136 c.Check(err, check.IsNil)
137 c.Logf("%#v", gotResp)
138 c.Check(gotResp["RequestID"], check.Equals, gotReq["RequestID"])
139 c.Check(gotResp["reqForwardedFor"], check.Equals, "1.2.3.4:12345")
140 c.Check(gotResp["msg"], check.Equals, "response")
142 c.Assert(gotResp["time"], check.FitsTypeOf, "")
143 _, err = time.Parse(time.RFC3339Nano, gotResp["time"].(string))
144 c.Check(err, check.IsNil)
146 for _, key := range []string{"timeToStatus", "timeWriteBody", "timeTotal"} {
147 c.Assert(gotResp[key], check.FitsTypeOf, float64(0))
148 c.Check(gotResp[key].(float64), check.Not(check.Equals), float64(0))
152 func (s *Suite) TestLogErrorBody(c *check.C) {
153 dec := json.NewDecoder(s.logdata)
155 for _, trial := range []struct {
162 {"ok", 200, "hello world", false, ""},
163 {"redir", 302, "<a href='http://foo.example/baz'>redir</a>", false, ""},
164 {"4xx short body", 400, "oops", true, "oops"},
165 {"4xx long body", 400, fmt.Sprintf("%0*d", sniffBytes*2, 1), true, fmt.Sprintf("%0*d", sniffBytes, 0)},
166 {"5xx empty body", 500, "", true, ""},
168 comment := check.Commentf("in trial: %q", trial.label)
170 req, err := http.NewRequest("GET", "https://foo.example/bar", nil)
171 c.Assert(err, check.IsNil)
172 resp := httptest.NewRecorder()
175 http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
176 w.WriteHeader(trial.statusCode)
177 w.Write([]byte(trial.sentBody))
179 ).ServeHTTP(resp, req.WithContext(s.ctx))
181 gotReq := make(map[string]interface{})
182 err = dec.Decode(&gotReq)
183 c.Check(err, check.IsNil)
184 c.Logf("%#v", gotReq)
185 gotResp := make(map[string]interface{})
186 err = dec.Decode(&gotResp)
187 c.Check(err, check.IsNil)
188 c.Logf("%#v", gotResp)
190 c.Check(gotResp["respBody"], check.Equals, trial.expectBody, comment)
192 c.Check(gotResp["respBody"], check.IsNil, comment)