1package httpmock 2 3import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "net/http" 9 "net/url" 10 "regexp" 11 "runtime" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16) 17 18const regexpPrefix = "=~" 19 20// NoResponderFound is returned when no responders are found for a given HTTP method and URL. 21var NoResponderFound = errors.New("no responder found") // nolint: golint 22 23type routeKey struct { 24 Method string 25 URL string 26} 27 28var noResponder routeKey 29 30func (r routeKey) String() string { 31 if r == noResponder { 32 return "NO_RESPONDER" 33 } 34 return r.Method + " " + r.URL 35} 36 37// ConnectionFailure is a responder that returns a connection failure. This is the default 38// responder, and is called when no other matching responder is found. 39func ConnectionFailure(*http.Request) (*http.Response, error) { 40 return nil, NoResponderFound 41} 42 43// NewMockTransport creates a new *MockTransport with no responders. 44func NewMockTransport() *MockTransport { 45 return &MockTransport{ 46 responders: make(map[routeKey]Responder), 47 callCountInfo: make(map[routeKey]int), 48 } 49} 50 51type regexpResponder struct { 52 origRx string 53 method string 54 rx *regexp.Regexp 55 responder Responder 56} 57 58// MockTransport implements http.RoundTripper, which fulfills single http requests issued by 59// an http.Client. This implementation doesn't actually make the call, instead deferring to 60// the registered list of responders. 61type MockTransport struct { 62 mu sync.RWMutex 63 responders map[routeKey]Responder 64 regexpResponders []regexpResponder 65 noResponder Responder 66 callCountInfo map[routeKey]int 67 totalCallCount int 68} 69 70// RoundTrip receives HTTP requests and routes them to the appropriate responder. It is required to 71// implement the http.RoundTripper interface. You will not interact with this directly, instead 72// the *http.Client you are using will call it for you. 73func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { 74 url := req.URL.String() 75 76 method := req.Method 77 if method == "" { 78 // http.Request.Method is documented to default to GET: 79 method = http.MethodGet 80 } 81 82 var ( 83 responder Responder 84 respKey routeKey 85 submatches []string 86 ) 87 key := routeKey{ 88 Method: method, 89 } 90 for _, getResponder := range []func(routeKey) (Responder, routeKey, []string){ 91 m.responderForKey, // Exact match 92 m.regexpResponderForKey, // Regexp match 93 } { 94 // try and get a responder that matches the method and URL with 95 // query params untouched: http://z.tld/path?q... 96 key.URL = url 97 responder, respKey, submatches = getResponder(key) 98 if responder != nil { 99 break 100 } 101 102 // if we weren't able to find a responder, try with the URL *and* 103 // sorted query params 104 query := sortedQuery(req.URL.Query()) 105 if query != "" { 106 // Replace unsorted query params by sorted ones: 107 // http://z.tld/path?sorted_q... 108 key.URL = strings.Replace(url, req.URL.RawQuery, query, 1) 109 responder, respKey, submatches = getResponder(key) 110 if responder != nil { 111 break 112 } 113 } 114 115 // if we weren't able to find a responder, try without any query params 116 strippedURL := *req.URL 117 strippedURL.RawQuery = "" 118 strippedURL.Fragment = "" 119 120 // go1.6 does not handle URL.ForceQuery, so in case it is set in go>1.6, 121 // remove the "?" manually if present. 122 surl := strings.TrimSuffix(strippedURL.String(), "?") 123 124 hasQueryString := url != surl 125 126 // if the URL contains a querystring then we strip off the 127 // querystring and try again: http://z.tld/path 128 if hasQueryString { 129 key.URL = surl 130 responder, respKey, submatches = getResponder(key) 131 if responder != nil { 132 break 133 } 134 } 135 136 // if we weren't able to find a responder for the full URL, try with 137 // the path part only 138 pathAlone := req.URL.Path 139 140 // First with unsorted querystring: /path?q... 141 if hasQueryString { 142 key.URL = pathAlone + strings.TrimPrefix(url, surl) // concat after-path part 143 responder, respKey, submatches = getResponder(key) 144 if responder != nil { 145 break 146 } 147 148 // Then with sorted querystring: /path?sorted_q... 149 key.URL = pathAlone + "?" + sortedQuery(req.URL.Query()) 150 if req.URL.Fragment != "" { 151 key.URL += "#" + req.URL.Fragment 152 } 153 responder, respKey, submatches = getResponder(key) 154 if responder != nil { 155 break 156 } 157 } 158 159 // Then using path alone: /path 160 key.URL = pathAlone 161 responder, respKey, submatches = getResponder(key) 162 if responder != nil { 163 break 164 } 165 } 166 167 m.mu.Lock() 168 // if we found a responder, call it 169 if responder != nil { 170 m.callCountInfo[key]++ 171 if key != respKey { 172 m.callCountInfo[respKey]++ 173 } 174 m.totalCallCount++ 175 } else { 176 // we didn't find a responder, so fire the 'no responder' responder 177 if m.noResponder != nil { 178 m.callCountInfo[noResponder]++ 179 m.totalCallCount++ 180 responder = m.noResponder 181 } 182 } 183 m.mu.Unlock() 184 185 if responder == nil { 186 return ConnectionFailure(req) 187 } 188 return runCancelable(responder, setSubmatches(req, submatches)) 189} 190 191func runCancelable(responder Responder, req *http.Request) (*http.Response, error) { 192 ctx := req.Context() 193 if req.Cancel == nil && ctx.Done() == nil { // nolint: staticcheck 194 resp, err := responder(req) 195 return resp, checkStackTracer(req, err) 196 } 197 198 // Set up a goroutine that translates a close(req.Cancel) into a 199 // "request canceled" error, and another one that runs the 200 // responder. Then race them: first to the result channel wins. 201 202 type result struct { 203 response *http.Response 204 err error 205 } 206 resultch := make(chan result, 1) 207 done := make(chan struct{}, 1) 208 209 go func() { 210 select { 211 case <-req.Cancel: // nolint: staticcheck 212 resultch <- result{ 213 response: nil, 214 err: errors.New("request canceled"), 215 } 216 case <-ctx.Done(): 217 resultch <- result{ 218 response: nil, 219 err: ctx.Err(), 220 } 221 case <-done: 222 } 223 }() 224 225 go func() { 226 defer func() { 227 if err := recover(); err != nil { 228 resultch <- result{ 229 response: nil, 230 err: fmt.Errorf("panic in responder: got %q", err), 231 } 232 } 233 }() 234 235 response, err := responder(req) 236 resultch <- result{ 237 response: response, 238 err: err, 239 } 240 }() 241 242 r := <-resultch 243 244 // if a cancel() issued from context.WithCancel() or a 245 // close(req.Cancel) are never coming, we'll need to unblock the 246 // first goroutine. 247 done <- struct{}{} 248 249 return r.response, checkStackTracer(req, r.err) 250} 251 252type stackTracer struct { 253 customFn func(...interface{}) 254 err error 255} 256 257func (n stackTracer) Error() string { 258 if n.err == nil { 259 return "" 260 } 261 return n.err.Error() 262} 263 264// checkStackTracer checks for specific error returned by 265// NewNotFoundResponder function or Debug Responder method. 266func checkStackTracer(req *http.Request, err error) error { 267 if nf, ok := err.(stackTracer); ok { 268 if nf.customFn != nil { 269 pc := make([]uintptr, 128) 270 npc := runtime.Callers(2, pc) 271 pc = pc[:npc] 272 273 var mesg bytes.Buffer 274 var netHTTPBegin, netHTTPEnd bool 275 276 // Start recording at first net/http call if any... 277 for { 278 frames := runtime.CallersFrames(pc) 279 280 var lastFn string 281 for { 282 frame, more := frames.Next() 283 284 if !netHTTPEnd { 285 if netHTTPBegin { 286 netHTTPEnd = !strings.HasPrefix(frame.Function, "net/http.") 287 } else { 288 netHTTPBegin = strings.HasPrefix(frame.Function, "net/http.") 289 } 290 } 291 292 if netHTTPEnd { 293 if lastFn != "" { 294 if mesg.Len() == 0 { 295 if nf.err != nil { 296 mesg.WriteString(nf.err.Error()) 297 } else { 298 fmt.Fprintf(&mesg, "%s %s", req.Method, req.URL) 299 } 300 mesg.WriteString("\nCalled from ") 301 } else { 302 mesg.WriteString("\n ") 303 } 304 fmt.Fprintf(&mesg, "%s()\n at %s:%d", lastFn, frame.File, frame.Line) 305 } 306 } 307 lastFn = frame.Function 308 309 if !more { 310 break 311 } 312 } 313 314 // At least one net/http frame found 315 if mesg.Len() > 0 { 316 break 317 } 318 netHTTPEnd = true // retry without looking at net/http frames 319 } 320 321 nf.customFn(mesg.String()) 322 } 323 err = nf.err 324 } 325 return err 326} 327 328// responderForKey returns a responder for a given key. 329func (m *MockTransport) responderForKey(key routeKey) (Responder, routeKey, []string) { 330 m.mu.RLock() 331 defer m.mu.RUnlock() 332 return m.responders[key], key, nil 333} 334 335// responderForKeyUsingRegexp returns the first responder matching a given key using regexps. 336func (m *MockTransport) regexpResponderForKey(key routeKey) (Responder, routeKey, []string) { 337 m.mu.RLock() 338 defer m.mu.RUnlock() 339 for _, regInfo := range m.regexpResponders { 340 if regInfo.method == key.Method { 341 if sm := regInfo.rx.FindStringSubmatch(key.URL); sm != nil { 342 if len(sm) == 1 { 343 sm = nil 344 } else { 345 sm = sm[1:] 346 } 347 return regInfo.responder, routeKey{ 348 Method: key.Method, 349 URL: regInfo.origRx, 350 }, sm 351 } 352 } 353 } 354 return nil, key, nil 355} 356 357func isRegexpURL(url string) bool { 358 return strings.HasPrefix(url, regexpPrefix) 359} 360 361// RegisterResponder adds a new responder, associated with a given 362// HTTP method and URL (or path). 363// 364// When a request comes in that matches, the responder will be called 365// and the response returned to the client. 366// 367// If url contains query parameters, their order matters as well as 368// their content. All following URLs are here considered as different: 369// http://z.tld?a=1&b=1 370// http://z.tld?b=1&a=1 371// http://z.tld?a&b 372// http://z.tld?a=&b= 373// 374// If url begins with "=~", the following chars are considered as a 375// regular expression. If this regexp can not be compiled, it panics. 376// Note that the "=~" prefix remains in statistics returned by 377// GetCallCountInfo(). As 2 regexps can match the same URL, the regexp 378// responders are tested in the order they are registered. Registering 379// an already existing regexp responder (same method & same regexp 380// string) replaces its responder but does not change its position. 381// 382// See RegisterRegexpResponder() to directly pass a *regexp.Regexp. 383// 384// Example: 385// func TestFetchArticles(t *testing.T) { 386// httpmock.Activate() 387// defer httpmock.DeactivateAndReset() 388// 389// httpmock.RegisterResponder("GET", "http://example.com/", 390// httpmock.NewStringResponder(200, "hello world")) 391// 392// httpmock.RegisterResponder("GET", "/path/only", 393// httpmock.NewStringResponder("any host hello world", 200)) 394// 395// httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`, 396// httpmock.NewStringResponder("any item get", 200)) 397// 398// // requests to http://example.com/ will now return "hello world" and 399// // requests to any host with path /path/only will return "any host hello world" 400// // requests to any host with path matching ^/item/id/\d+\z regular expression will return "any item get" 401// } 402func (m *MockTransport) RegisterResponder(method, url string, responder Responder) { 403 if isRegexpURL(url) { 404 m.registerRegexpResponder(regexpResponder{ 405 origRx: url, 406 method: method, 407 rx: regexp.MustCompile(url[2:]), 408 responder: responder, 409 }) 410 return 411 } 412 413 key := routeKey{ 414 Method: method, 415 URL: url, 416 } 417 418 m.mu.Lock() 419 m.responders[key] = responder 420 m.callCountInfo[key] = 0 421 m.mu.Unlock() 422} 423 424func (m *MockTransport) registerRegexpResponder(regexpResponder regexpResponder) { 425 m.mu.Lock() 426 defer m.mu.Unlock() 427 428found: 429 for { 430 for i, rr := range m.regexpResponders { 431 if rr.method == regexpResponder.method && rr.origRx == regexpResponder.origRx { 432 m.regexpResponders[i] = regexpResponder 433 break found 434 } 435 } 436 m.regexpResponders = append(m.regexpResponders, regexpResponder) 437 break // nolint: staticcheck 438 } 439 440 m.callCountInfo[routeKey{ 441 Method: regexpResponder.method, 442 URL: regexpResponder.origRx, 443 }] = 0 444} 445 446// RegisterRegexpResponder adds a new responder, associated with a given 447// HTTP method and URL (or path) regular expression. 448// 449// When a request comes in that matches, the responder will be called 450// and the response returned to the client. 451// 452// As 2 regexps can match the same URL, the regexp responders are 453// tested in the order they are registered. Registering an already 454// existing regexp responder (same method & same regexp string) 455// replaces its responder but does not change its position. 456// 457// A "=~" prefix is added to the stringified regexp in the statistics 458// returned by GetCallCountInfo(). 459// 460// See RegisterResponder function and the "=~" prefix in its url 461// parameter to avoid compiling the regexp by yourself. 462func (m *MockTransport) RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) { 463 m.registerRegexpResponder(regexpResponder{ 464 origRx: regexpPrefix + urlRegexp.String(), 465 method: method, 466 rx: urlRegexp, 467 responder: responder, 468 }) 469} 470 471// RegisterResponderWithQuery is same as RegisterResponder, but it 472// doesn't depend on query items order. 473// 474// If query is non-nil, its type can be: 475// url.Values 476// map[string]string 477// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) 478// 479// If the query type is not recognized or the string cannot be parsed 480// using net/url.ParseQuery, a panic() occurs. 481// 482// Unlike RegisterResponder, path cannot be prefixed by "=~" to say it 483// is a regexp. If it is, a panic occurs. 484func (m *MockTransport) RegisterResponderWithQuery(method, path string, query interface{}, responder Responder) { 485 if isRegexpURL(path) { 486 panic(`path begins with "=~", RegisterResponder should be used instead of RegisterResponderWithQuery`) 487 } 488 489 var mapQuery url.Values 490 switch q := query.(type) { 491 case url.Values: 492 mapQuery = q 493 494 case map[string]string: 495 mapQuery = make(url.Values, len(q)) 496 for key, e := range q { 497 mapQuery[key] = []string{e} 498 } 499 500 case string: 501 var err error 502 mapQuery, err = url.ParseQuery(q) 503 if err != nil { 504 panic("RegisterResponderWithQuery bad query string: " + err.Error()) 505 } 506 507 default: 508 if query != nil { 509 panic(fmt.Sprintf("RegisterResponderWithQuery bad query type %T. Only url.Values, map[string]string and string are allowed", query)) 510 } 511 } 512 513 if queryString := sortedQuery(mapQuery); queryString != "" { 514 path += "?" + queryString 515 } 516 m.RegisterResponder(method, path, responder) 517} 518 519func sortedQuery(m url.Values) string { 520 if len(m) == 0 { 521 return "" 522 } 523 524 keys := make([]string, 0, len(m)) 525 for k := range m { 526 keys = append(keys, k) 527 } 528 sort.Strings(keys) 529 530 var b bytes.Buffer 531 var values []string // nolint: prealloc 532 533 for _, k := range keys { 534 // Do not alter the passed url.Values 535 values = append(values, m[k]...) 536 sort.Strings(values) 537 538 k = url.QueryEscape(k) 539 540 for _, v := range values { 541 if b.Len() > 0 { 542 b.WriteByte('&') 543 } 544 fmt.Fprintf(&b, "%v=%v", k, url.QueryEscape(v)) 545 } 546 547 values = values[:0] 548 } 549 550 return b.String() 551} 552 553// RegisterNoResponder is used to register a responder that will be called if no other responder is 554// found. The default is ConnectionFailure. 555func (m *MockTransport) RegisterNoResponder(responder Responder) { 556 m.mu.Lock() 557 m.noResponder = responder 558 m.mu.Unlock() 559} 560 561// Reset removes all registered responders (including the no responder) from the MockTransport 562func (m *MockTransport) Reset() { 563 m.mu.Lock() 564 m.responders = make(map[routeKey]Responder) 565 m.regexpResponders = nil 566 m.noResponder = nil 567 m.callCountInfo = make(map[routeKey]int) 568 m.totalCallCount = 0 569 m.mu.Unlock() 570} 571 572// GetCallCountInfo gets the info on all the calls httpmock has caught 573// since it was activated or reset. The info is returned as a map of 574// the calling keys with the number of calls made to them as their 575// value. The key is the method, a space, and the url all concatenated 576// together. 577// 578// As a special case, regexp responders generate 2 entries for each 579// call. One for the call caught and the other for the rule that 580// matched. For example: 581// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body")) 582// http.Get("http://z.com") 583// 584// will generate the following result: 585// map[string]int{ 586// `GET http://z.com`: 1, 587// `GET =~z\.com\z`: 1, 588// } 589func (m *MockTransport) GetCallCountInfo() map[string]int { 590 res := make(map[string]int, len(m.callCountInfo)) 591 m.mu.RLock() 592 for k, v := range m.callCountInfo { 593 res[k.String()] = v 594 } 595 m.mu.RUnlock() 596 return res 597} 598 599// GetTotalCallCount returns the totalCallCount. 600func (m *MockTransport) GetTotalCallCount() int { 601 m.mu.RLock() 602 count := m.totalCallCount 603 m.mu.RUnlock() 604 return count 605} 606 607// DefaultTransport is the default mock transport used by Activate, Deactivate, Reset, 608// DeactivateAndReset, RegisterResponder, and RegisterNoResponder. 609var DefaultTransport = NewMockTransport() 610 611// InitialTransport is a cache of the original transport used so we can put it back 612// when Deactivate is called. 613var InitialTransport = http.DefaultTransport 614 615// Used to handle custom http clients (i.e clients other than http.DefaultClient) 616var oldClients = map[*http.Client]http.RoundTripper{} 617 618// Activate starts the mock environment. This should be called before your tests run. Under the 619// hood this replaces the Transport on the http.DefaultClient with DefaultTransport. 620// 621// To enable mocks for a test, simply activate at the beginning of a test: 622// func TestFetchArticles(t *testing.T) { 623// httpmock.Activate() 624// // all http requests will now be intercepted 625// } 626// 627// If you want all of your tests in a package to be mocked, just call Activate from init(): 628// func init() { 629// httpmock.Activate() 630// } 631func Activate() { 632 if Disabled() { 633 return 634 } 635 636 // make sure that if Activate is called multiple times it doesn't overwrite the InitialTransport 637 // with a mock transport. 638 if http.DefaultTransport != DefaultTransport { 639 InitialTransport = http.DefaultTransport 640 } 641 642 http.DefaultTransport = DefaultTransport 643} 644 645// ActivateNonDefault starts the mock environment with a non-default http.Client. 646// This emulates the Activate function, but allows for custom clients that do not use 647// http.DefaultTransport 648// 649// To enable mocks for a test using a custom client, activate at the beginning of a test: 650// client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}} 651// httpmock.ActivateNonDefault(client) 652func ActivateNonDefault(client *http.Client) { 653 if Disabled() { 654 return 655 } 656 657 // save the custom client & it's RoundTripper 658 if _, ok := oldClients[client]; !ok { 659 oldClients[client] = client.Transport 660 } 661 client.Transport = DefaultTransport 662} 663 664// GetCallCountInfo gets the info on all the calls httpmock has caught 665// since it was activated or reset. The info is returned as a map of 666// the calling keys with the number of calls made to them as their 667// value. The key is the method, a space, and the url all concatenated 668// together. 669// 670// As a special case, regexp responders generate 2 entries for each 671// call. One for the call caught and the other for the rule that 672// matched. For example: 673// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body")) 674// http.Get("http://z.com") 675// 676// will generate the following result: 677// map[string]int{ 678// `GET http://z.com`: 1, 679// `GET =~z\.com\z`: 1, 680// } 681func GetCallCountInfo() map[string]int { 682 return DefaultTransport.GetCallCountInfo() 683} 684 685// GetTotalCallCount gets the total number of calls httpmock has taken since it was activated or 686// reset. 687func GetTotalCallCount() int { 688 return DefaultTransport.GetTotalCallCount() 689} 690 691// Deactivate shuts down the mock environment. Any HTTP calls made after this will use a live 692// transport. 693// 694// Usually you'll call it in a defer right after activating the mock environment: 695// func TestFetchArticles(t *testing.T) { 696// httpmock.Activate() 697// defer httpmock.Deactivate() 698// 699// // when this test ends, the mock environment will close 700// } 701func Deactivate() { 702 if Disabled() { 703 return 704 } 705 http.DefaultTransport = InitialTransport 706 707 // reset the custom clients to use their original RoundTripper 708 for oldClient, oldTransport := range oldClients { 709 oldClient.Transport = oldTransport 710 delete(oldClients, oldClient) 711 } 712} 713 714// Reset will remove any registered mocks and return the mock environment to it's initial state. 715func Reset() { 716 DefaultTransport.Reset() 717} 718 719// DeactivateAndReset is just a convenience method for calling Deactivate() and then Reset() 720// Happy deferring! 721func DeactivateAndReset() { 722 Deactivate() 723 Reset() 724} 725 726// RegisterResponder adds a new responder, associated with a given 727// HTTP method and URL (or path). 728// 729// When a request comes in that matches, the responder will be called 730// and the response returned to the client. 731// 732// If url contains query parameters, their order matters as well as 733// their content. All following URLs are here considered as different: 734// http://z.tld?a=1&b=1 735// http://z.tld?b=1&a=1 736// http://z.tld?a&b 737// http://z.tld?a=&b= 738// 739// If url begins with "=~", the following chars are considered as a 740// regular expression. If this regexp can not be compiled, it panics. 741// Note that the "=~" prefix remains in statistics returned by 742// GetCallCountInfo(). As 2 regexps can match the same URL, the regexp 743// responders are tested in the order they are registered. Registering 744// an already existing regexp responder (same method & same regexp 745// string) replaces its responder but does not change its position. 746// 747// See RegisterRegexpResponder() to directly pass a *regexp.Regexp. 748// 749// Example: 750// func TestFetchArticles(t *testing.T) { 751// httpmock.Activate() 752// defer httpmock.DeactivateAndReset() 753// 754// httpmock.RegisterResponder("GET", "http://example.com/", 755// httpmock.NewStringResponder(200, "hello world")) 756// 757// httpmock.RegisterResponder("GET", "/path/only", 758// httpmock.NewStringResponder("any host hello world", 200)) 759// 760// httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`, 761// httpmock.NewStringResponder("any item get", 200)) 762// 763// // requests to http://example.com/ will now return "hello world" and 764// // requests to any host with path /path/only will return "any host hello world" 765// // requests to any host with path matching ^/item/id/\d+\z regular expression will return "any item get" 766// } 767func RegisterResponder(method, url string, responder Responder) { 768 DefaultTransport.RegisterResponder(method, url, responder) 769} 770 771// RegisterRegexpResponder adds a new responder, associated with a given 772// HTTP method and URL (or path) regular expression. 773// 774// When a request comes in that matches, the responder will be called 775// and the response returned to the client. 776// 777// As 2 regexps can match the same URL, the regexp responders are 778// tested in the order they are registered. Registering an already 779// existing regexp responder (same method & same regexp string) 780// replaces its responder but does not change its position. 781// 782// A "=~" prefix is added to the stringified regexp in the statistics 783// returned by GetCallCountInfo(). 784// 785// See RegisterResponder function and the "=~" prefix in its url 786// parameter to avoid compiling the regexp by yourself. 787func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) { 788 DefaultTransport.RegisterRegexpResponder(method, urlRegexp, responder) 789} 790 791// RegisterResponderWithQuery it is same as RegisterResponder, but 792// doesn't depends on query items order. 793// 794// query type can be: 795// url.Values 796// map[string]string 797// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) 798// 799// If the query type is not recognized or the string cannot be parsed 800// using net/url.ParseQuery, a panic() occurs. 801// 802// Example using a net/url.Values: 803// func TestFetchArticles(t *testing.T) { 804// httpmock.Activate() 805// defer httpmock.DeactivateAndReset() 806// 807// expectedQuery := net.Values{ 808// "a": []string{"3", "1", "8"}, 809// "b": []string{"4", "2"}, 810// } 811// httpmock.RegisterResponderWithQueryValues("GET", "http://example.com/", expectedQuery, 812// httpmock.NewStringResponder("hello world", 200)) 813// 814// // requests to http://example.com?a=1&a=3&a=8&b=2&b=4 815// // and to http://example.com?b=4&a=2&b=2&a=8&a=1 816// // will now return 'hello world' 817// } 818// 819// or using a map[string]string: 820// func TestFetchArticles(t *testing.T) { 821// httpmock.Activate() 822// defer httpmock.DeactivateAndReset() 823// 824// expectedQuery := map[string]string{ 825// "a": "1", 826// "b": "2" 827// } 828// httpmock.RegisterResponderWithQuery("GET", "http://example.com/", expectedQuery, 829// httpmock.NewStringResponder("hello world", 200)) 830// 831// // requests to http://example.com?a=1&b=2 and http://example.com?b=2&a=1 will now return 'hello world' 832// } 833// 834// or using a query string: 835// func TestFetchArticles(t *testing.T) { 836// httpmock.Activate() 837// defer httpmock.DeactivateAndReset() 838// 839// expectedQuery := "a=3&b=4&b=2&a=1&a=8" 840// httpmock.RegisterResponderWithQueryValues("GET", "http://example.com/", expectedQuery, 841// httpmock.NewStringResponder("hello world", 200)) 842// 843// // requests to http://example.com?a=1&a=3&a=8&b=2&b=4 844// // and to http://example.com?b=4&a=2&b=2&a=8&a=1 845// // will now return 'hello world' 846// } 847func RegisterResponderWithQuery(method, path string, query interface{}, responder Responder) { 848 DefaultTransport.RegisterResponderWithQuery(method, path, query, responder) 849} 850 851// RegisterNoResponder adds a mock that will be called whenever a request for an unregistered URL 852// is received. The default behavior is to return a connection error. 853// 854// In some cases you may not want all URLs to be mocked, in which case you can do this: 855// func TestFetchArticles(t *testing.T) { 856// httpmock.Activate() 857// defer httpmock.DeactivateAndReset() 858// httpmock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip) 859// 860// // any requests that don't have a registered URL will be fetched normally 861// } 862func RegisterNoResponder(responder Responder) { 863 DefaultTransport.RegisterNoResponder(responder) 864} 865 866type submatchesKeyType struct{} 867 868var submatchesKey submatchesKeyType 869 870func setSubmatches(req *http.Request, submatches []string) *http.Request { 871 if len(submatches) > 0 { 872 return req.WithContext(context.WithValue(req.Context(), submatchesKey, submatches)) 873 } 874 return req 875} 876 877// ErrSubmatchNotFound is the error returned by GetSubmatch* functions 878// when the given submatch index cannot be found. 879var ErrSubmatchNotFound = errors.New("submatch not found") 880 881// GetSubmatch has to be used in Responders installed by 882// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 883// allows to retrieve the n-th submatch of the matching regexp, as a 884// string. Example: 885// RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, 886// func(req *http.Request) (*http.Response, error) { 887// name, err := GetSubmatch(req, 1) // 1=first regexp submatch 888// if err != nil { 889// return nil, err 890// } 891// return NewJsonResponse(200, map[string]interface{}{ 892// "id": 123, 893// "name": name, 894// }) 895// }) 896// 897// It panics if n < 1. See MustGetSubmatch to avoid testing the 898// returned error. 899func GetSubmatch(req *http.Request, n int) (string, error) { 900 if n <= 0 { 901 panic(fmt.Sprintf("getting submatches starts at 1, not %d", n)) 902 } 903 n-- 904 905 submatches, ok := req.Context().Value(submatchesKey).([]string) 906 if !ok || n >= len(submatches) { 907 return "", ErrSubmatchNotFound 908 } 909 return submatches[n], nil 910} 911 912// GetSubmatchAsInt has to be used in Responders installed by 913// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 914// allows to retrieve the n-th submatch of the matching regexp, as an 915// int64. Example: 916// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, 917// func(req *http.Request) (*http.Response, error) { 918// id, err := GetSubmatchAsInt(req, 1) // 1=first regexp submatch 919// if err != nil { 920// return nil, err 921// } 922// return NewJsonResponse(200, map[string]interface{}{ 923// "id": id, 924// "name": "The beautiful name", 925// }) 926// }) 927// 928// It panics if n < 1. See MustGetSubmatchAsInt to avoid testing the 929// returned error. 930func GetSubmatchAsInt(req *http.Request, n int) (int64, error) { 931 sm, err := GetSubmatch(req, n) 932 if err != nil { 933 return 0, err 934 } 935 return strconv.ParseInt(sm, 10, 64) 936} 937 938// GetSubmatchAsUint has to be used in Responders installed by 939// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 940// allows to retrieve the n-th submatch of the matching regexp, as a 941// uint64. Example: 942// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, 943// func(req *http.Request) (*http.Response, error) { 944// id, err := GetSubmatchAsUint(req, 1) // 1=first regexp submatch 945// if err != nil { 946// return nil, err 947// } 948// return NewJsonResponse(200, map[string]interface{}{ 949// "id": id, 950// "name": "The beautiful name", 951// }) 952// }) 953// 954// It panics if n < 1. See MustGetSubmatchAsUint to avoid testing the 955// returned error. 956func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) { 957 sm, err := GetSubmatch(req, n) 958 if err != nil { 959 return 0, err 960 } 961 return strconv.ParseUint(sm, 10, 64) 962} 963 964// GetSubmatchAsFloat has to be used in Responders installed by 965// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 966// allows to retrieve the n-th submatch of the matching regexp, as a 967// float64. Example: 968// RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, 969// func(req *http.Request) (*http.Response, error) { 970// height, err := GetSubmatchAsFloat(req, 1) // 1=first regexp submatch 971// if err != nil { 972// return nil, err 973// } 974// return NewJsonResponse(200, map[string]interface{}{ 975// "id": id, 976// "name": "The beautiful name", 977// "height": height, 978// }) 979// }) 980// 981// It panics if n < 1. See MustGetSubmatchAsFloat to avoid testing the 982// returned error. 983func GetSubmatchAsFloat(req *http.Request, n int) (float64, error) { 984 sm, err := GetSubmatch(req, n) 985 if err != nil { 986 return 0, err 987 } 988 return strconv.ParseFloat(sm, 64) 989} 990 991// MustGetSubmatch works as GetSubmatch except that it panics in case 992// of error (submatch not found). It has to be used in Responders 993// installed by RegisterRegexpResponder or RegisterResponder + "=~" 994// URL prefix. It allows to retrieve the n-th submatch of the matching 995// regexp, as a string. Example: 996// RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, 997// func(req *http.Request) (*http.Response, error) { 998// name := MustGetSubmatch(req, 1) // 1=first regexp submatch 999// return NewJsonResponse(200, map[string]interface{}{ 1000// "id": 123, 1001// "name": name, 1002// }) 1003// }) 1004// 1005// It panics if n < 1. 1006func MustGetSubmatch(req *http.Request, n int) string { 1007 s, err := GetSubmatch(req, n) 1008 if err != nil { 1009 panic("GetSubmatch failed: " + err.Error()) 1010 } 1011 return s 1012} 1013 1014// MustGetSubmatchAsInt works as GetSubmatchAsInt except that it 1015// panics in case of error (submatch not found or invalid int64 1016// format). It has to be used in Responders installed by 1017// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 1018// allows to retrieve the n-th submatch of the matching regexp, as an 1019// int64. Example: 1020// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, 1021// func(req *http.Request) (*http.Response, error) { 1022// id := MustGetSubmatchAsInt(req, 1) // 1=first regexp submatch 1023// return NewJsonResponse(200, map[string]interface{}{ 1024// "id": id, 1025// "name": "The beautiful name", 1026// }) 1027// }) 1028// 1029// It panics if n < 1. 1030func MustGetSubmatchAsInt(req *http.Request, n int) int64 { 1031 i, err := GetSubmatchAsInt(req, n) 1032 if err != nil { 1033 panic("GetSubmatchAsInt failed: " + err.Error()) 1034 } 1035 return i 1036} 1037 1038// MustGetSubmatchAsUint works as GetSubmatchAsUint except that it 1039// panics in case of error (submatch not found or invalid uint64 1040// format). It has to be used in Responders installed by 1041// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 1042// allows to retrieve the n-th submatch of the matching regexp, as a 1043// uint64. Example: 1044// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, 1045// func(req *http.Request) (*http.Response, error) { 1046// id, err := MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch 1047// return NewJsonResponse(200, map[string]interface{}{ 1048// "id": id, 1049// "name": "The beautiful name", 1050// }) 1051// }) 1052// 1053// It panics if n < 1. 1054func MustGetSubmatchAsUint(req *http.Request, n int) uint64 { 1055 u, err := GetSubmatchAsUint(req, n) 1056 if err != nil { 1057 panic("GetSubmatchAsUint failed: " + err.Error()) 1058 } 1059 return u 1060} 1061 1062// MustGetSubmatchAsFloat works as GetSubmatchAsFloat except that it 1063// panics in case of error (submatch not found or invalid float64 1064// format). It has to be used in Responders installed by 1065// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It 1066// allows to retrieve the n-th submatch of the matching regexp, as a 1067// float64. Example: 1068// RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, 1069// func(req *http.Request) (*http.Response, error) { 1070// height := MustGetSubmatchAsFloat(req, 1) // 1=first regexp submatch 1071// return NewJsonResponse(200, map[string]interface{}{ 1072// "id": id, 1073// "name": "The beautiful name", 1074// "height": height, 1075// }) 1076// }) 1077// 1078// It panics if n < 1. 1079func MustGetSubmatchAsFloat(req *http.Request, n int) float64 { 1080 f, err := GetSubmatchAsFloat(req, n) 1081 if err != nil { 1082 panic("GetSubmatchAsFloat failed: " + err.Error()) 1083 } 1084 return f 1085} 1086