github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/middleware_test.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package native 22 23 import ( 24 "context" 25 "errors" 26 "net/http" 27 "net/http/httptest" 28 "net/url" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/gorilla/mux" 34 "github.com/jonboulle/clockwork" 35 "github.com/stretchr/testify/require" 36 "go.uber.org/zap" 37 "go.uber.org/zap/zapcore" 38 "go.uber.org/zap/zaptest/observer" 39 40 "github.com/m3db/m3/src/query/api/v1/middleware" 41 "github.com/m3db/m3/src/x/headers" 42 "github.com/m3db/m3/src/x/instrument" 43 xhttp "github.com/m3db/m3/src/x/net/http" 44 ) 45 46 func TestQueryResponse(t *testing.T) { 47 now := time.Now().UTC().Round(0) 48 startTime := now 49 endTime := startTime.Add(time.Hour) 50 cases := []struct { 51 name string 52 code int 53 duration time.Duration 54 threshold time.Duration 55 disabled bool 56 err error 57 form map[string]string 58 requestHeaders map[string]string 59 responseHeaders map[string]string 60 fields map[string]interface{} 61 }{ 62 { 63 name: "happy path", 64 code: 200, 65 duration: time.Second, 66 threshold: time.Microsecond, 67 form: map[string]string{ 68 "query": "fooquery", 69 "start": startTime.Format(time.RFC3339Nano), 70 "end": endTime.Format(time.RFC3339Nano), 71 "extra": "foobar", 72 }, 73 requestHeaders: map[string]string{ 74 headers.LimitHeader: "10", 75 headers.LimitMaxDocsHeader: "100", 76 "foo": "bar", 77 }, 78 responseHeaders: map[string]string{ 79 headers.TimeoutHeader: "10", 80 headers.ReturnedMetadataLimitedHeader: "100", 81 "foo": "bar", 82 }, 83 fields: map[string]interface{}{ 84 "query": "fooquery", 85 "start": startTime, 86 "end": endTime, 87 "status": int64(200), 88 "queryRange": time.Hour, 89 headers.LimitHeader: "10", 90 headers.LimitMaxDocsHeader: "100", 91 headers.TimeoutHeader: "10", 92 headers.ReturnedMetadataLimitedHeader: "100", 93 }, 94 }, 95 { 96 name: "no request data", 97 code: 504, 98 duration: time.Second, 99 threshold: time.Microsecond, 100 fields: map[string]interface{}{ 101 "query": "", 102 "start": time.Time{}, 103 "end": time.Time{}, 104 "status": int64(504), 105 "queryRange": time.Duration(0), 106 }, 107 }, 108 { 109 name: "instant", 110 form: map[string]string{ 111 "query": "fooquery", 112 "start": "now", 113 "end": "now", 114 }, 115 code: 200, 116 duration: time.Second, 117 threshold: time.Microsecond, 118 fields: map[string]interface{}{ 119 "query": "fooquery", 120 "start": now, 121 "end": now, 122 "status": int64(200), 123 "queryRange": time.Duration(0), 124 }, 125 }, 126 { 127 name: "error", 128 form: map[string]string{ 129 "query": "fooquery", 130 "start": "now", 131 "end": "now", 132 }, 133 code: 500, 134 err: errors.New("boom"), 135 duration: time.Second, 136 threshold: time.Microsecond, 137 fields: map[string]interface{}{ 138 "query": "fooquery", 139 "start": now, 140 "end": now, 141 "status": int64(500), 142 "queryRange": time.Duration(0), 143 "error": "boom", 144 }, 145 }, 146 { 147 name: "below threshold", 148 form: map[string]string{ 149 "query": "fooquery", 150 "start": "now", 151 "end": "now", 152 }, 153 code: 200, 154 duration: time.Millisecond, 155 threshold: time.Second, 156 }, 157 { 158 name: "disabled", 159 form: map[string]string{ 160 "query": "fooquery", 161 "start": "now", 162 "end": "now", 163 }, 164 code: 200, 165 disabled: true, 166 duration: time.Second, 167 threshold: time.Millisecond, 168 }, 169 } 170 171 for _, tc := range cases { 172 tc := tc 173 t.Run(tc.name, func(t *testing.T) { 174 core, recorded := observer.New(zapcore.InfoLevel) 175 iOpts := instrument.NewOptions().SetLogger(zap.New(core)) 176 clock := clockwork.NewFakeClockAt(now) 177 opts := WithQueryParams(middleware.Options{ 178 InstrumentOpts: iOpts, 179 Clock: clock, 180 Metrics: middleware.MetricsOptions{}, 181 Logging: middleware.LoggingOptions{ 182 Threshold: tc.threshold, 183 Disabled: tc.disabled, 184 }, 185 }) 186 h := middleware.ResponseLogging(opts).Middleware( 187 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 188 clock.Advance(tc.duration) 189 if tc.err != nil { 190 xhttp.WriteError(w, tc.err) 191 return 192 } 193 for k, v := range tc.responseHeaders { 194 w.Header().Set(k, v) 195 } 196 w.WriteHeader(tc.code) 197 })) 198 r := mux.NewRouter() 199 r.Handle("/testRoute", h) 200 server := httptest.NewServer(r) 201 defer server.Close() 202 203 values := url.Values{} 204 for k, v := range tc.form { 205 values.Add(k, v) 206 } 207 req, err := http.NewRequestWithContext(context.Background(), "POST", server.URL+"/testRoute", 208 strings.NewReader(values.Encode())) 209 for k, v := range tc.requestHeaders { 210 req.Header.Set(k, v) 211 } 212 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 213 require.NoError(t, err) 214 resp, err := server.Client().Do(req) 215 require.NoError(t, err) 216 require.NoError(t, resp.Body.Close()) 217 require.Equal(t, tc.code, resp.StatusCode) 218 msgs := recorded.FilterMessage("finished handling request").All() 219 if len(tc.fields) == 0 { 220 require.Len(t, msgs, 0) 221 } else { 222 require.Len(t, msgs, 1) 223 fields := msgs[0].ContextMap() 224 require.Equal(t, "/testRoute", fields["url"]) 225 require.True(t, fields["duration"].(time.Duration) >= tc.duration) 226 227 for k, v := range tc.fields { 228 require.Equal(t, v, fields[k], "log field %v", k) 229 } 230 require.Len(t, fields, len(tc.fields)+2) 231 } 232 }) 233 } 234 }