github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/handleroptions/fetch_options_test.go (about) 1 // Copyright (c) 2019 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 handleroptions 22 23 import ( 24 "bytes" 25 "context" 26 "fmt" 27 "math" 28 "mime/multipart" 29 "net/http" 30 "net/http/httptest" 31 "net/url" 32 "regexp" 33 "testing" 34 "time" 35 36 "github.com/m3db/m3/src/dbnode/encoding" 37 "github.com/m3db/m3/src/dbnode/topology" 38 "github.com/m3db/m3/src/metrics/policy" 39 "github.com/m3db/m3/src/query/models" 40 "github.com/m3db/m3/src/query/storage" 41 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 42 xerrors "github.com/m3db/m3/src/x/errors" 43 "github.com/m3db/m3/src/x/headers" 44 xhttp "github.com/m3db/m3/src/x/net/http" 45 46 "github.com/stretchr/testify/assert" 47 "github.com/stretchr/testify/require" 48 ) 49 50 func TestFetchOptionsBuilder(t *testing.T) { 51 type expectedLookback struct { 52 value time.Duration 53 } 54 55 tests := []struct { 56 name string 57 defaultLimit int 58 defaultRangeLimit time.Duration 59 defaultRestrictByTag *storage.RestrictByTag 60 headers map[string]string 61 query string 62 expectedLimit int 63 expectedRangeLimit time.Duration 64 expectedRestrict *storage.RestrictQueryOptions 65 expectedLookback *expectedLookback 66 expectedReadConsistencyLevel *topology.ReadConsistencyLevel 67 expectedIterateEqualTimestampStrategy *encoding.IterateEqualTimestampStrategy 68 expectedErr bool 69 }{ 70 { 71 name: "default limit with no headers", 72 defaultLimit: 42, 73 headers: map[string]string{}, 74 expectedLimit: 42, 75 }, 76 { 77 name: "limit with header", 78 defaultLimit: 42, 79 headers: map[string]string{ 80 headers.LimitMaxSeriesHeader: "4242", 81 }, 82 expectedLimit: 4242, 83 }, 84 { 85 name: "bad limit header", 86 defaultLimit: 42, 87 headers: map[string]string{ 88 headers.LimitMaxSeriesHeader: "not_a_number", 89 }, 90 expectedErr: true, 91 }, 92 { 93 name: "default range limit with no headers", 94 defaultRangeLimit: 42 * time.Hour, 95 headers: map[string]string{}, 96 expectedRangeLimit: 42 * time.Hour, 97 }, 98 { 99 name: "range limit with header", 100 defaultRangeLimit: 42 * time.Hour, 101 headers: map[string]string{ 102 headers.LimitMaxRangeHeader: "84h", 103 }, 104 expectedRangeLimit: 84 * time.Hour, 105 }, 106 { 107 name: "bad range limit header", 108 defaultRangeLimit: 42 * time.Hour, 109 headers: map[string]string{ 110 // Not a parseable time range string. 111 headers.LimitMaxRangeHeader: "4242", 112 }, 113 expectedErr: true, 114 }, 115 { 116 name: "unaggregated metrics type", 117 headers: map[string]string{ 118 headers.MetricsTypeHeader: storagemetadata.UnaggregatedMetricsType.String(), 119 }, 120 expectedRestrict: &storage.RestrictQueryOptions{ 121 RestrictByType: &storage.RestrictByType{ 122 MetricsType: storagemetadata.UnaggregatedMetricsType, 123 }, 124 }, 125 }, 126 { 127 name: "aggregated metrics type", 128 headers: map[string]string{ 129 headers.MetricsTypeHeader: storagemetadata.AggregatedMetricsType.String(), 130 headers.MetricsStoragePolicyHeader: "1m:14d", 131 }, 132 expectedRestrict: &storage.RestrictQueryOptions{ 133 RestrictByType: &storage.RestrictByType{ 134 MetricsType: storagemetadata.AggregatedMetricsType, 135 StoragePolicy: policy.MustParseStoragePolicy("1m:14d"), 136 }, 137 }, 138 }, 139 { 140 name: "unaggregated metrics type with storage policy", 141 headers: map[string]string{ 142 headers.MetricsTypeHeader: storagemetadata.UnaggregatedMetricsType.String(), 143 headers.MetricsStoragePolicyHeader: "1m:14d", 144 }, 145 expectedErr: true, 146 }, 147 { 148 name: "aggregated metrics type without storage policy", 149 headers: map[string]string{ 150 headers.MetricsTypeHeader: storagemetadata.AggregatedMetricsType.String(), 151 }, 152 expectedErr: true, 153 }, 154 { 155 name: "unrecognized metrics type", 156 headers: map[string]string{ 157 headers.MetricsTypeHeader: "foo", 158 }, 159 expectedErr: true, 160 }, 161 { 162 name: "can set lookback duration", 163 query: "lookback=10s", 164 expectedLookback: &expectedLookback{ 165 value: 10 * time.Second, 166 }, 167 }, 168 { 169 name: "can set lookback duration based on step", 170 query: "lookback=step&step=10s", 171 expectedLookback: &expectedLookback{ 172 value: 10 * time.Second, 173 }, 174 }, 175 { 176 name: "bad lookback returns error", 177 query: "lookback=foo", 178 expectedErr: true, 179 }, 180 { 181 name: "loookback step but step is bad", 182 query: "lookback=step&step=invalid", 183 expectedErr: true, 184 }, 185 { 186 name: "lookback step but step is negative", 187 query: "lookback=step&step=-1", 188 expectedErr: true, 189 }, 190 { 191 name: "restrict by tags json header", 192 headers: map[string]string{ 193 headers.RestrictByTagsJSONHeader: stripSpace(`{ 194 "match":[{"name":"foo", "value":"bar", "type":"EQUAL"}], 195 "strip":["foo"] 196 }`), 197 }, 198 expectedRestrict: &storage.RestrictQueryOptions{ 199 RestrictByTag: &storage.RestrictByTag{ 200 Restrict: models.Matchers{ 201 mustMatcher("foo", "bar", models.MatchEqual), 202 }, 203 Strip: toStrip("foo"), 204 }, 205 }, 206 }, 207 { 208 name: "restrict by tags json defaults", 209 defaultRestrictByTag: &storage.RestrictByTag{ 210 Restrict: models.Matchers{ 211 mustMatcher("foo", "bar", models.MatchEqual), 212 }, 213 Strip: toStrip("foo"), 214 }, 215 expectedRestrict: &storage.RestrictQueryOptions{ 216 RestrictByTag: &storage.RestrictByTag{ 217 Restrict: models.Matchers{ 218 mustMatcher("foo", "bar", models.MatchEqual), 219 }, 220 Strip: toStrip("foo"), 221 }, 222 }, 223 }, 224 { 225 name: "restrict by tags json default override by header", 226 defaultRestrictByTag: &storage.RestrictByTag{ 227 Restrict: models.Matchers{ 228 mustMatcher("foo", "bar", models.MatchEqual), 229 }, 230 Strip: toStrip("foo"), 231 }, 232 headers: map[string]string{ 233 headers.RestrictByTagsJSONHeader: stripSpace(`{ 234 "match":[{"name":"qux", "value":"qaz", "type":"EQUAL"}], 235 "strip":["qux"] 236 }`), 237 }, 238 expectedRestrict: &storage.RestrictQueryOptions{ 239 RestrictByTag: &storage.RestrictByTag{ 240 Restrict: models.Matchers{ 241 mustMatcher("qux", "qaz", models.MatchEqual), 242 }, 243 Strip: toStrip("qux"), 244 }, 245 }, 246 }, 247 { 248 name: "restrict by policies with metrics type", 249 headers: map[string]string{ 250 headers.MetricsTypeHeader: "aggregated", 251 headers.MetricsRestrictByStoragePoliciesHeader: "10m:60d", 252 }, 253 expectedErr: true, 254 }, 255 { 256 name: "restrict by policies with storage policy", 257 headers: map[string]string{ 258 headers.MetricsStoragePolicyHeader: "10m:60d", 259 headers.MetricsRestrictByStoragePoliciesHeader: "10m:60d", 260 }, 261 expectedErr: true, 262 }, 263 { 264 name: "restrict by policies - invalid policy", 265 headers: map[string]string{ 266 headers.MetricsRestrictByStoragePoliciesHeader: "10m", 267 }, 268 expectedErr: true, 269 }, 270 { 271 name: "restrict by policies - invalid delimiter", 272 headers: map[string]string{ 273 headers.MetricsRestrictByStoragePoliciesHeader: "10m:60d,5m:30d", 274 }, 275 expectedErr: true, 276 }, 277 { 278 name: "restrict by policies - single policy", 279 headers: map[string]string{ 280 headers.MetricsRestrictByStoragePoliciesHeader: "10m:60d", 281 }, 282 expectedRestrict: &storage.RestrictQueryOptions{ 283 RestrictByTypes: []*storage.RestrictByType{ 284 { 285 MetricsType: storagemetadata.AggregatedMetricsType, 286 StoragePolicy: policy.MustParseStoragePolicy("10m:60d"), 287 }, 288 }, 289 }, 290 }, 291 { 292 name: "restrict by policies - multiple policy", 293 headers: map[string]string{ 294 headers.MetricsRestrictByStoragePoliciesHeader: "10m:60d;5m:30d", 295 }, 296 expectedRestrict: &storage.RestrictQueryOptions{ 297 RestrictByTypes: []*storage.RestrictByType{ 298 { 299 MetricsType: storagemetadata.AggregatedMetricsType, 300 StoragePolicy: policy.MustParseStoragePolicy("10m:60d"), 301 }, 302 { 303 MetricsType: storagemetadata.AggregatedMetricsType, 304 StoragePolicy: policy.MustParseStoragePolicy("5m:30d"), 305 }, 306 }, 307 }, 308 }, 309 { 310 name: "read overrides", 311 headers: map[string]string{ 312 headers.ReadConsistencyLevelHeader: "all", 313 headers.IterateEqualTimestampStrategyHeader: "iterate_lowest_value", 314 }, 315 expectedReadConsistencyLevel: &topology.ValidReadConsistencyLevels()[5], 316 expectedIterateEqualTimestampStrategy: &encoding.ValidIterateEqualTimestampStrategies()[2], 317 }, 318 } 319 320 for _, test := range tests { 321 t.Run(test.name, func(t *testing.T) { 322 builder, err := NewFetchOptionsBuilder(FetchOptionsBuilderOptions{ 323 Limits: FetchOptionsBuilderLimitsOptions{ 324 SeriesLimit: test.defaultLimit, 325 }, 326 RestrictByTag: test.defaultRestrictByTag, 327 Timeout: 10 * time.Second, 328 }) 329 require.NoError(t, err) 330 331 url := "/foo" 332 if test.query != "" { 333 url += "?" + test.query 334 } 335 req := httptest.NewRequest("GET", url, nil) 336 for k, v := range test.headers { 337 req.Header.Add(k, v) 338 } 339 340 ctx, opts, err := builder.NewFetchOptions(context.Background(), req) 341 if !test.expectedErr { 342 require.NoError(t, err) 343 require.Equal(t, test.expectedLimit, opts.SeriesLimit) 344 if test.expectedRestrict == nil { 345 require.Nil(t, opts.RestrictQueryOptions) 346 } else { 347 require.NotNil(t, opts.RestrictQueryOptions) 348 require.Equal(t, *test.expectedRestrict, *opts.RestrictQueryOptions) 349 } 350 if test.expectedLookback == nil { 351 require.Nil(t, opts.LookbackDuration) 352 } else { 353 require.NotNil(t, opts.LookbackDuration) 354 require.Equal(t, test.expectedLookback.value, *opts.LookbackDuration) 355 } 356 if test.expectedReadConsistencyLevel == nil { 357 require.Nil(t, opts.ReadConsistencyLevel) 358 } else { 359 require.NotNil(t, opts.ReadConsistencyLevel) 360 require.Equal(t, *test.expectedReadConsistencyLevel, *opts.ReadConsistencyLevel) 361 } 362 if test.expectedIterateEqualTimestampStrategy == nil { 363 require.Nil(t, opts.IterateEqualTimestampStrategy) 364 } else { 365 require.NotNil(t, opts.IterateEqualTimestampStrategy) 366 require.Equal(t, *test.expectedIterateEqualTimestampStrategy, *opts.IterateEqualTimestampStrategy) 367 } 368 require.Equal(t, 10*time.Second, opts.Timeout) 369 // Check context has deadline and headers from 370 // the request. 371 _, ok := ctx.Deadline() 372 require.True(t, ok) 373 headers := ctx.Value(RequestHeaderKey) 374 require.NotNil(t, headers) 375 _, ok = headers.(http.Header) 376 require.True(t, ok) 377 } else { 378 require.Error(t, err) 379 } 380 }) 381 } 382 } 383 384 func TestInvalidStep(t *testing.T) { 385 req := httptest.NewRequest("GET", "/foo", nil) 386 vals := make(url.Values) 387 vals.Del(StepParam) 388 vals.Add(StepParam, "-10.50s") 389 req.URL.RawQuery = vals.Encode() 390 _, _, err := ParseStep(req) 391 require.NotNil(t, err, "unable to parse request") 392 } 393 394 func TestParseLookbackDuration(t *testing.T) { 395 r := httptest.NewRequest(http.MethodGet, "/foo", nil) 396 _, ok, err := ParseLookbackDuration(r) 397 require.NoError(t, err) 398 require.False(t, ok) 399 400 r = httptest.NewRequest(http.MethodGet, "/foo?step=60s&lookback=step", nil) 401 v, ok, err := ParseLookbackDuration(r) 402 require.NoError(t, err) 403 require.True(t, ok) 404 assert.Equal(t, time.Minute, v) 405 406 r = httptest.NewRequest(http.MethodGet, "/foo?step=60s&lookback=120s", nil) 407 v, ok, err = ParseLookbackDuration(r) 408 require.NoError(t, err) 409 require.True(t, ok) 410 assert.Equal(t, 2*time.Minute, v) 411 412 r = httptest.NewRequest(http.MethodGet, "/foo?step=60s&lookback=foobar", nil) 413 _, _, err = ParseLookbackDuration(r) 414 require.Error(t, err) 415 } 416 417 func TestParseDuration(t *testing.T) { 418 r, err := http.NewRequest(http.MethodGet, "/foo?step=10s", nil) 419 require.NoError(t, err) 420 v, err := ParseDuration(r, StepParam) 421 require.NoError(t, err) 422 assert.Equal(t, 10*time.Second, v) 423 } 424 425 func TestParseFloatDuration(t *testing.T) { 426 r, err := http.NewRequest(http.MethodGet, "/foo?step=10.50m", nil) 427 require.NoError(t, err) 428 v, err := ParseDuration(r, StepParam) 429 require.NoError(t, err) 430 assert.Equal(t, 10*time.Minute+30*time.Second, v) 431 432 r = httptest.NewRequest(http.MethodGet, "/foo?step=10.00m", nil) 433 require.NoError(t, err) 434 v, err = ParseDuration(r, StepParam) 435 require.NoError(t, err) 436 assert.Equal(t, 10*time.Minute, v) 437 } 438 439 func TestParseDurationParsesIntAsSeconds(t *testing.T) { 440 r, err := http.NewRequest(http.MethodGet, "/foo?step=30", nil) 441 require.NoError(t, err) 442 v, err := ParseDuration(r, StepParam) 443 require.NoError(t, err) 444 assert.Equal(t, 30*time.Second, v) 445 } 446 447 func TestParseDurationParsesFloatAsSeconds(t *testing.T) { 448 r, err := http.NewRequest(http.MethodGet, "/foo?step=30.00", nil) 449 require.NoError(t, err) 450 v, err := ParseDuration(r, StepParam) 451 require.NoError(t, err) 452 assert.Equal(t, 30*time.Second, v) 453 } 454 455 func TestParseDurationError(t *testing.T) { 456 r, err := http.NewRequest(http.MethodGet, "/foo?step=bar10", nil) 457 require.NoError(t, err) 458 _, err = ParseDuration(r, StepParam) 459 assert.Error(t, err) 460 } 461 462 func TestParseDurationOverflowError(t *testing.T) { 463 r, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/foo?step=%f", float64(math.MaxInt64)), nil) 464 require.NoError(t, err) 465 _, err = ParseDuration(r, StepParam) 466 assert.Error(t, err) 467 } 468 469 func TestFetchOptionsWithHeader(t *testing.T) { 470 type expectedLookback struct { 471 value time.Duration 472 } 473 474 headers := map[string]string{ 475 headers.MetricsTypeHeader: storagemetadata.AggregatedMetricsType.String(), 476 headers.MetricsStoragePolicyHeader: "1m:14d", 477 headers.RestrictByTagsJSONHeader: `{ 478 "match":[ 479 {"name":"a", "value":"b", "type":"EQUAL"}, 480 {"name":"c", "value":"d", "type":"NOTEQUAL"}, 481 {"name":"e", "value":"f", "type":"REGEXP"}, 482 {"name":"g", "value":"h", "type":"NOTREGEXP"}, 483 {"name":"i", "value":"j", "type":"EXISTS"}, 484 {"name":"k", "value":"l", "type":"NOTEXISTS"} 485 ], 486 "strip":["foo"] 487 }`, 488 headers.ReadConsistencyLevelHeader: "all", 489 headers.IterateEqualTimestampStrategyHeader: "iterate_lowest_value", 490 } 491 492 builder, err := NewFetchOptionsBuilder(FetchOptionsBuilderOptions{ 493 Limits: FetchOptionsBuilderLimitsOptions{ 494 SeriesLimit: 5, 495 }, 496 Timeout: 10 * time.Second, 497 }) 498 require.NoError(t, err) 499 500 req := httptest.NewRequest("GET", "/", nil) 501 for k, v := range headers { 502 req.Header.Add(k, v) 503 } 504 505 _, opts, err := builder.NewFetchOptions(context.Background(), req) 506 require.NoError(t, err) 507 require.NotNil(t, opts.RestrictQueryOptions) 508 ex := &storage.RestrictQueryOptions{ 509 RestrictByType: &storage.RestrictByType{ 510 MetricsType: storagemetadata.AggregatedMetricsType, 511 StoragePolicy: policy.MustParseStoragePolicy("1m:14d"), 512 }, 513 RestrictByTag: &storage.RestrictByTag{ 514 Restrict: models.Matchers{ 515 mustMatcher("a", "b", models.MatchEqual), 516 mustMatcher("c", "d", models.MatchNotEqual), 517 mustMatcher("e", "f", models.MatchRegexp), 518 mustMatcher("g", "h", models.MatchNotRegexp), 519 mustMatcher("i", "j", models.MatchField), 520 mustMatcher("k", "l", models.MatchNotField), 521 }, 522 Strip: toStrip("foo"), 523 }, 524 } 525 526 require.Equal(t, ex, opts.RestrictQueryOptions) 527 require.Equal(t, topology.ReadConsistencyLevelAll, *opts.ReadConsistencyLevel) 528 require.Equal(t, encoding.IterateLowestValue, *opts.IterateEqualTimestampStrategy) 529 } 530 531 func stripSpace(str string) string { 532 return regexp.MustCompile(`\s+`).ReplaceAllString(str, "") 533 } 534 535 func TestParseRequestTimeout(t *testing.T) { 536 req := httptest.NewRequest("GET", "/read?timeout=2m", nil) 537 dur, err := ParseRequestTimeout(req, time.Second) 538 require.NoError(t, err) 539 assert.Equal(t, 2*time.Minute, dur) 540 } 541 542 func TestParseRelatedQueryOptions(t *testing.T) { 543 t.Parallel() 544 545 tests := map[string]struct { 546 expectErr bool 547 expectedOk bool 548 headers []string 549 expectedResult *storage.RelatedQueryOptions 550 }{ 551 "simple": { 552 headers: []string{"1635160222:1635166222"}, 553 expectErr: false, 554 expectedOk: true, 555 expectedResult: &storage.RelatedQueryOptions{ 556 Timespans: []storage.QueryTimespan{ 557 {Start: 1635160222000000000, End: 1635166222000000000}, 558 }, 559 }, 560 }, 561 "multiple queries (second header ignored)": { 562 headers: []string{"1635160222:1635166222", "1635161222:1635165222"}, 563 expectErr: false, 564 expectedOk: true, 565 expectedResult: &storage.RelatedQueryOptions{ 566 Timespans: []storage.QueryTimespan{ 567 {Start: 1635160222000000000, End: 1635166222000000000}, 568 }, 569 }, 570 }, 571 "multiple queries same header.": { 572 headers: []string{"1635160222:1635166222;1635161222:1635165222"}, 573 expectErr: false, 574 expectedOk: true, 575 expectedResult: &storage.RelatedQueryOptions{ 576 Timespans: []storage.QueryTimespan{ 577 {Start: 1635160222000000000, End: 1635166222000000000}, 578 {Start: 1635161222000000000, End: 1635165222000000000}, 579 }, 580 }, 581 }, 582 "no related_queries": { 583 headers: []string{}, 584 expectErr: false, 585 expectedOk: false, 586 expectedResult: nil, 587 }, 588 "incomplete pair": { 589 headers: []string{"1635160222"}, 590 expectErr: true, 591 expectedOk: false, 592 expectedResult: nil, 593 }, 594 "invalid pair (start time)": { 595 headers: []string{"2m:6m"}, 596 expectErr: true, 597 expectedOk: false, 598 expectedResult: nil, 599 }, 600 "invalid pair (end time)": { 601 headers: []string{"1635160222:6m"}, 602 expectErr: true, 603 expectedOk: false, 604 expectedResult: nil, 605 }, 606 "invalid pair (end time after start time)": { 607 headers: []string{"1635166222:1635160222"}, 608 expectErr: true, 609 expectedOk: false, 610 expectedResult: nil, 611 }, 612 } 613 614 for name, tc := range tests { 615 name, tc := name, tc 616 t.Run(name, func(t *testing.T) { 617 t.Parallel() 618 619 req := httptest.NewRequest("GET", "/read", nil) 620 for _, header := range tc.headers { 621 req.Header.Add(headers.RelatedQueriesHeader, header) 622 } 623 options, ok, err := ParseRelatedQueryOptions(req) 624 assert.Equal(t, tc.expectedOk, ok, 625 "Expected result of ok to be %v got %v", tc.expectedOk, ok) 626 627 if tc.expectErr { 628 assert.Error(t, err) 629 } else { 630 assert.NoError(t, err) 631 } 632 633 if tc.expectedResult == nil { 634 assert.Nil(t, options) 635 } else { 636 assert.NotNil(t, options) 637 assert.Equal(t, tc.expectedResult, options) 638 } 639 }) 640 } 641 } 642 643 func TestTimeoutParseWithHeader(t *testing.T) { 644 req := httptest.NewRequest("POST", "/dummy", nil) 645 req.Header.Add("timeout", "1ms") 646 647 timeout, err := ParseRequestTimeout(req, time.Second) 648 assert.NoError(t, err) 649 assert.Equal(t, timeout, time.Millisecond) 650 651 req.Header.Add(headers.TimeoutHeader, "1s") 652 timeout, err = ParseRequestTimeout(req, time.Second) 653 assert.NoError(t, err) 654 assert.Equal(t, timeout, time.Second) 655 656 req.Header.Del("timeout") 657 req.Header.Del(headers.TimeoutHeader) 658 timeout, err = ParseRequestTimeout(req, 2*time.Minute) 659 assert.NoError(t, err) 660 assert.Equal(t, timeout, 2*time.Minute) 661 662 req.Header.Add("timeout", "invalid") 663 _, err = ParseRequestTimeout(req, 15*time.Second) 664 assert.Error(t, err) 665 assert.True(t, xerrors.IsInvalidParams(err)) 666 } 667 668 func TestTimeoutParseWithPostRequestParam(t *testing.T) { 669 params := url.Values{} 670 params.Add("timeout", "1ms") 671 672 buff := bytes.NewBuffer(nil) 673 form := multipart.NewWriter(buff) 674 form.WriteField("timeout", "1ms") 675 require.NoError(t, form.Close()) 676 677 req := httptest.NewRequest("POST", "/dummy", buff) 678 req.Header.Set(xhttp.HeaderContentType, form.FormDataContentType()) 679 680 timeout, err := ParseRequestTimeout(req, time.Second) 681 assert.NoError(t, err) 682 assert.Equal(t, timeout, time.Millisecond) 683 } 684 685 func TestTimeoutParseWithGetRequestParam(t *testing.T) { 686 params := url.Values{} 687 params.Add("timeout", "1ms") 688 689 req := httptest.NewRequest("GET", "/dummy?"+params.Encode(), nil) 690 691 timeout, err := ParseRequestTimeout(req, time.Second) 692 assert.NoError(t, err) 693 assert.Equal(t, timeout, time.Millisecond) 694 } 695 696 func TestInstanceMultiple(t *testing.T) { 697 req := httptest.NewRequest("GET", "/", nil) 698 m, err := ParseInstanceMultiple(req, 2.0) 699 require.NoError(t, err) 700 require.Equal(t, float32(2.0), m) 701 702 req.Header.Set(headers.LimitInstanceMultipleHeader, "3.0") 703 m, err = ParseInstanceMultiple(req, 2.0) 704 require.NoError(t, err) 705 require.Equal(t, float32(3.0), m) 706 707 req.Header.Set(headers.LimitInstanceMultipleHeader, "blah") 708 _, err = ParseInstanceMultiple(req, 2.0) 709 require.Error(t, err) 710 require.Contains(t, err.Error(), "could not parse instance multiple") 711 }