github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/common_test.go (about) 1 // Copyright (c) 2018 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 prometheus 22 23 import ( 24 "bytes" 25 "context" 26 "fmt" 27 "net/http" 28 "net/http/httptest" 29 "testing" 30 "time" 31 32 "github.com/m3db/m3/src/query/models" 33 "github.com/m3db/m3/src/query/parser/promql" 34 "github.com/m3db/m3/src/query/test" 35 xerrors "github.com/m3db/m3/src/x/errors" 36 xhttp "github.com/m3db/m3/src/x/net/http" 37 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40 ) 41 42 func TestPromCompressedReadSuccess(t *testing.T) { 43 req := httptest.NewRequest("POST", "/dummy", test.GeneratePromReadBody(t)) 44 _, err := ParsePromCompressedRequest(req) 45 assert.NoError(t, err) 46 } 47 48 func TestPromCompressedReadNoBody(t *testing.T) { 49 req := httptest.NewRequest("POST", "/dummy", nil) 50 _, err := ParsePromCompressedRequest(req) 51 assert.Error(t, err) 52 assert.True(t, xerrors.IsInvalidParams(err)) 53 } 54 55 func TestPromCompressedReadEmptyBody(t *testing.T) { 56 req := httptest.NewRequest("POST", "/dummy", bytes.NewReader([]byte{})) 57 _, err := ParsePromCompressedRequest(req) 58 assert.Error(t, err) 59 assert.True(t, xerrors.IsInvalidParams(err)) 60 } 61 62 func TestPromCompressedReadInvalidEncoding(t *testing.T) { 63 req := httptest.NewRequest("POST", "/dummy", bytes.NewReader([]byte{'a'})) 64 _, err := ParsePromCompressedRequest(req) 65 assert.Error(t, err) 66 assert.True(t, xerrors.IsInvalidParams(err)) 67 } 68 69 type writer struct { 70 value string 71 } 72 73 func (w *writer) Write(p []byte) (n int, err error) { 74 w.value = string(p) 75 return len(p), nil 76 } 77 78 type tag struct { 79 name, value string 80 } 81 82 func toTags(name string, tags ...tag) models.Metric { 83 tagOpts := models.NewTagOptions() 84 ts := models.NewTags(len(tags), tagOpts) 85 ts = ts.SetName([]byte(name)) 86 for _, tag := range tags { 87 ts = ts.AddTag(models.Tag{Name: []byte(tag.name), Value: []byte(tag.value)}) 88 } 89 90 return models.Metric{Tags: ts} 91 } 92 93 func TestParseStartAndEnd(t *testing.T) { 94 endTime := time.Now().Truncate(time.Hour) 95 opts := promql.NewParseOptions().SetNowFn(func() time.Time { return endTime }) 96 97 tests := []struct { 98 querystring string 99 exStart time.Time 100 exEnd time.Time 101 exErr bool 102 }{ 103 {querystring: "", exStart: time.Unix(0, 0), exEnd: endTime}, 104 {querystring: "start=100", exStart: time.Unix(100, 0), exEnd: endTime}, 105 {querystring: "start=100&end=200", exStart: time.Unix(100, 0), exEnd: time.Unix(200, 0)}, 106 {querystring: "start=200&end=100", exErr: true}, 107 {querystring: "start=foo&end=100", exErr: true}, 108 {querystring: "start=100&end=bar", exErr: true}, 109 } 110 111 for _, tt := range tests { 112 t.Run(fmt.Sprintf("GET_%s", tt.querystring), func(t *testing.T) { 113 req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, 114 fmt.Sprintf("/?%s", tt.querystring), nil) 115 require.NoError(t, err) 116 117 start, end, err := ParseStartAndEnd(req, opts) 118 if tt.exErr { 119 require.Error(t, err) 120 } else { 121 assert.Equal(t, tt.exStart, start) 122 assert.Equal(t, tt.exEnd, end) 123 } 124 }) 125 } 126 127 for _, tt := range tests { 128 t.Run(fmt.Sprintf("POST_%s", tt.querystring), func(t *testing.T) { 129 b := bytes.NewBuffer([]byte(tt.querystring)) 130 req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/", b) 131 require.NoError(t, err) 132 req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded) 133 134 start, end, err := ParseStartAndEnd(req, opts) 135 if tt.exErr { 136 require.Error(t, err) 137 } else { 138 assert.Equal(t, tt.exStart, start) 139 assert.Equal(t, tt.exEnd, end) 140 } 141 }) 142 } 143 } 144 145 func TestParseRequireStartEnd(t *testing.T) { 146 opts := promql.NewParseOptions() 147 148 tests := []struct { 149 exStart time.Time 150 querystring string 151 requireStartEnd bool 152 exErr bool 153 }{ 154 {querystring: "start=100", requireStartEnd: true, exStart: time.Unix(100, 0)}, 155 {querystring: "", requireStartEnd: true, exErr: true}, 156 {querystring: "start=100", requireStartEnd: false, exStart: time.Unix(100, 0)}, 157 {querystring: "", requireStartEnd: false, exStart: time.Unix(0, 0)}, 158 } 159 for _, tt := range tests { 160 t.Run(fmt.Sprintf("GET_%s", tt.querystring), func(t *testing.T) { 161 req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, 162 fmt.Sprintf("/?%s", tt.querystring), nil) 163 require.NoError(t, err) 164 165 start, _, err := ParseStartAndEnd(req, opts.SetRequireStartEndTime(tt.requireStartEnd)) 166 if tt.exErr { 167 require.Error(t, err) 168 } else { 169 assert.Equal(t, tt.exStart, start) 170 } 171 }) 172 } 173 } 174 175 // TestParseMatch tests the parsing / construction logic around ParseMatch(). 176 // matcher_test.go has more comprehensive testing on parsing details. 177 func TestParseMatch(t *testing.T) { 178 parseOpts := promql.NewParseOptions() 179 tagOpts := models.NewTagOptions() 180 181 tests := []struct { 182 querystring string 183 exMatch []ParsedMatch 184 exErr bool 185 exEmpty bool 186 }{ 187 {exEmpty: true}, 188 { 189 querystring: "match[]=eq_label", 190 exMatch: []ParsedMatch{ 191 { 192 Match: "eq_label", 193 Matchers: models.Matchers{ 194 { 195 Type: models.MatchEqual, 196 Name: []byte("__name__"), 197 Value: []byte("eq_label"), 198 }, 199 }, 200 }, 201 }, 202 }, 203 {querystring: "match[]=illegal%match", exErr: true}, 204 } 205 206 for _, tt := range tests { 207 t.Run(fmt.Sprintf("GET_%s", tt.querystring), func(t *testing.T) { 208 req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, 209 fmt.Sprintf("/?%s", tt.querystring), nil) 210 require.NoError(t, err) 211 212 parsedMatches, ok, err := ParseMatch(req, parseOpts, tagOpts) 213 214 if tt.exErr { 215 require.Error(t, err) 216 require.False(t, ok) 217 require.Empty(t, parsedMatches) 218 return 219 } 220 221 require.NoError(t, err) 222 if tt.exEmpty { 223 require.False(t, ok) 224 require.Empty(t, parsedMatches) 225 } else { 226 require.True(t, ok) 227 require.Equal(t, tt.exMatch, parsedMatches) 228 } 229 }) 230 } 231 232 for _, tt := range tests { 233 t.Run(fmt.Sprintf("POST_%s", tt.querystring), func(t *testing.T) { 234 b := bytes.NewBuffer([]byte(tt.querystring)) 235 req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/", b) 236 require.NoError(t, err) 237 req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded) 238 239 parsedMatches, ok, err := ParseMatch(req, parseOpts, tagOpts) 240 241 if tt.exErr { 242 require.Error(t, err) 243 require.False(t, ok) 244 require.Empty(t, parsedMatches) 245 return 246 } 247 248 require.NoError(t, err) 249 if tt.exEmpty { 250 require.False(t, ok) 251 require.Empty(t, parsedMatches) 252 } else { 253 require.True(t, ok) 254 require.Equal(t, tt.exMatch, parsedMatches) 255 } 256 }) 257 } 258 }