1package httpmock
2
3import (
4	"bytes"
5	"context"
6	"encoding/json"
7	"errors"
8	"io/ioutil"
9	"net"
10	"net/http"
11	"net/url"
12	"reflect"
13	"regexp"
14	"strings"
15	"testing"
16	"time"
17)
18
19var testURL = "http://www.example.com/"
20
21func assertBody(t *testing.T, resp *http.Response, expected string) {
22	defer resp.Body.Close()
23
24	data, err := ioutil.ReadAll(resp.Body)
25	if err != nil {
26		t.Fatal(err)
27	}
28
29	got := string(data)
30
31	if got != expected {
32		t.Errorf("Expected body: %#v, got %#v", expected, got)
33	}
34}
35
36func TestRouteKey(t *testing.T) {
37	got, expected := noResponder.String(), "NO_RESPONDER"
38	if got != expected {
39		t.Errorf("got: %v, expected: %v", got, expected)
40	}
41
42	got, expected = routeKey{Method: "GET", URL: "/foo"}.String(), "GET /foo"
43	if got != expected {
44		t.Errorf("got: %v, expected: %v", got, expected)
45	}
46}
47
48func TestMockTransport(t *testing.T) {
49	Activate()
50	defer Deactivate()
51
52	url := "https://github.com/"
53	body := `["hello world"]` + "\n"
54
55	RegisterResponder("GET", url, NewStringResponder(200, body))
56
57	// Read it as a simple string (ioutil.ReadAll will trigger io.EOF)
58	func() {
59		resp, err := http.Get(url)
60		if err != nil {
61			t.Fatal(err)
62		}
63		defer resp.Body.Close()
64
65		data, err := ioutil.ReadAll(resp.Body)
66		if err != nil {
67			t.Fatal(err)
68		}
69
70		if string(data) != body {
71			t.FailNow()
72		}
73
74		// the http client wraps our NoResponderFound error, so we just try and match on text
75		if _, err := http.Get(testURL); !strings.Contains(err.Error(),
76			NoResponderFound.Error()) {
77
78			t.Fatal(err)
79		}
80	}()
81
82	// Do it again, but twice with json decoder (json Decode will not
83	// reach EOF, but Close is called as the JSON response is complete)
84	for i := 0; i < 2; i++ {
85		func() {
86			resp, err := http.Get(url)
87			if err != nil {
88				t.Fatal(err)
89			}
90			defer resp.Body.Close()
91
92			var res []string
93			err = json.NewDecoder(resp.Body).Decode(&res)
94			if err != nil {
95				t.Fatal(err)
96			}
97
98			if len(res) != 1 || res[0] != "hello world" {
99				t.Fatalf(`%v read instead of ["hello world"]`, res)
100			}
101		}()
102	}
103}
104
105// We should be able to find GET handlers when using an http.Request with a
106// default (zero-value) .Method.
107func TestMockTransportDefaultMethod(t *testing.T) {
108	Activate()
109	defer Deactivate()
110
111	const urlString = "https://github.com/"
112	url, err := url.Parse(urlString)
113	if err != nil {
114		t.Fatal(err)
115	}
116	body := "hello world"
117
118	RegisterResponder("GET", urlString, NewStringResponder(200, body))
119
120	req := &http.Request{
121		URL: url,
122		// Note: Method unspecified (zero-value)
123	}
124
125	client := &http.Client{}
126	resp, err := client.Do(req)
127	if err != nil {
128		t.Fatal(err)
129	}
130	defer resp.Body.Close()
131
132	data, err := ioutil.ReadAll(resp.Body)
133	if err != nil {
134		t.Fatal(err)
135	}
136
137	if string(data) != body {
138		t.FailNow()
139	}
140}
141
142func TestMockTransportReset(t *testing.T) {
143	DeactivateAndReset()
144
145	if len(DefaultTransport.responders) > 0 {
146		t.Fatal("expected no responders at this point")
147	}
148
149	RegisterResponder("GET", testURL, nil)
150
151	if len(DefaultTransport.responders) != 1 {
152		t.Fatal("expected one responder")
153	}
154
155	Reset()
156
157	if len(DefaultTransport.responders) > 0 {
158		t.Fatal("expected no responders as they were just reset")
159	}
160}
161
162func TestMockTransportNoResponder(t *testing.T) {
163	Activate()
164	defer DeactivateAndReset()
165
166	Reset()
167
168	if DefaultTransport.noResponder != nil {
169		t.Fatal("expected noResponder to be nil")
170	}
171
172	if _, err := http.Get(testURL); err == nil {
173		t.Fatal("expected to receive a connection error due to lack of responders")
174	}
175
176	RegisterNoResponder(NewStringResponder(200, "hello world"))
177
178	resp, err := http.Get(testURL)
179	if err != nil {
180		t.Fatal("expected request to succeed")
181	}
182
183	data, err := ioutil.ReadAll(resp.Body)
184	if err != nil {
185		t.Fatal(err)
186	}
187
188	if string(data) != "hello world" {
189		t.Fatal("expected body to be 'hello world'")
190	}
191}
192
193func TestMockTransportQuerystringFallback(t *testing.T) {
194	Activate()
195	defer DeactivateAndReset()
196
197	// register the testURL responder
198	RegisterResponder("GET", testURL, NewStringResponder(200, "hello world"))
199
200	for _, suffix := range []string{"?", "?hello=world", "?hello=world#foo", "?hello=world&hello=all", "#foo"} {
201		reqURL := testURL + suffix
202
203		// make a request for the testURL with a querystring
204		resp, err := http.Get(reqURL)
205		if err != nil {
206			t.Fatalf("expected request %s to succeed", reqURL)
207		}
208
209		data, err := ioutil.ReadAll(resp.Body)
210		if err != nil {
211			t.Fatalf("%s error: %s", reqURL, err)
212		}
213
214		if string(data) != "hello world" {
215			t.Fatalf("expected body of %s to be 'hello world'", reqURL)
216		}
217	}
218}
219
220func TestMockTransportPathOnlyFallback(t *testing.T) {
221	// Just in case a panic occurs
222	defer DeactivateAndReset()
223
224	for _, test := range []struct {
225		Responder string
226		Paths     []string
227	}{
228		{
229			// unsorted query string matches exactly
230			Responder: "/hello/world?query=string&abc=zz#fragment",
231			Paths: []string{
232				testURL + "hello/world?query=string&abc=zz#fragment",
233			},
234		},
235		{
236			// sorted query string matches all cases
237			Responder: "/hello/world?abc=zz&query=string#fragment",
238			Paths: []string{
239				testURL + "hello/world?query=string&abc=zz#fragment",
240				testURL + "hello/world?abc=zz&query=string#fragment",
241			},
242		},
243		{
244			// unsorted query string matches exactly
245			Responder: "/hello/world?query=string&abc=zz",
246			Paths: []string{
247				testURL + "hello/world?query=string&abc=zz",
248			},
249		},
250		{
251			// sorted query string matches all cases
252			Responder: "/hello/world?abc=zz&query=string",
253			Paths: []string{
254				testURL + "hello/world?query=string&abc=zz",
255				testURL + "hello/world?abc=zz&query=string",
256			},
257		},
258		{
259			// unsorted query string matches exactly
260			Responder: "/hello/world?query=string&query=string2&abc=zz",
261			Paths: []string{
262				testURL + "hello/world?query=string&query=string2&abc=zz",
263			},
264		},
265		// sorted query string matches all cases
266		{
267			Responder: "/hello/world?abc=zz&query=string&query=string2",
268			Paths: []string{
269				testURL + "hello/world?query=string&query=string2&abc=zz",
270				testURL + "hello/world?query=string2&query=string&abc=zz",
271				testURL + "hello/world?abc=zz&query=string2&query=string",
272			},
273		},
274		{
275			Responder: "/hello/world?query",
276			Paths: []string{
277				testURL + "hello/world?query",
278			},
279		},
280		{
281			Responder: "/hello/world?query&abc",
282			Paths: []string{
283				testURL + "hello/world?query&abc",
284				// testURL + "hello/world?abc&query" won' work as "=" is needed, see below
285			},
286		},
287		{
288			// In case the sorting does not matter for received params without
289			// values, we must register params with "="
290			Responder: "/hello/world?abc=&query=",
291			Paths: []string{
292				testURL + "hello/world?query&abc",
293				testURL + "hello/world?abc&query",
294			},
295		},
296		{
297			Responder: "/hello/world#fragment",
298			Paths: []string{
299				testURL + "hello/world#fragment",
300			},
301		},
302		{
303			Responder: "/hello/world",
304			Paths: []string{
305				testURL + "hello/world?query=string&abc=zz#fragment",
306				testURL + "hello/world?query=string&abc=zz",
307				testURL + "hello/world#fragment",
308				testURL + "hello/world",
309			},
310		},
311		// Regexp cases
312		{
313			Responder: `=~^http://.*/hello/.*ld\z`,
314			Paths: []string{
315				testURL + "hello/world?query=string&abc=zz#fragment",
316				testURL + "hello/world?query=string&abc=zz",
317				testURL + "hello/world#fragment",
318				testURL + "hello/world",
319			},
320		},
321		{
322			Responder: `=~^http://.*/hello/.*ld(\z|[?#])`,
323			Paths: []string{
324				testURL + "hello/world?query=string&abc=zz#fragment",
325				testURL + "hello/world?query=string&abc=zz",
326				testURL + "hello/world#fragment",
327				testURL + "hello/world",
328			},
329		},
330		{
331			Responder: `=~^/hello/.*ld\z`,
332			Paths: []string{
333				testURL + "hello/world?query=string&abc=zz#fragment",
334				testURL + "hello/world?query=string&abc=zz",
335				testURL + "hello/world#fragment",
336				testURL + "hello/world",
337			},
338		},
339		{
340			Responder: `=~^/hello/.*ld(\z|[?#])`,
341			Paths: []string{
342				testURL + "hello/world?query=string&abc=zz#fragment",
343				testURL + "hello/world?query=string&abc=zz",
344				testURL + "hello/world#fragment",
345				testURL + "hello/world",
346			},
347		},
348		{
349			Responder: `=~abc=zz`,
350			Paths: []string{
351				testURL + "hello/world?query=string&abc=zz#fragment",
352				testURL + "hello/world?query=string&abc=zz",
353			},
354		},
355	} {
356		Activate()
357
358		// register the responder
359		RegisterResponder("GET", test.Responder, NewStringResponder(200, "hello world"))
360
361		for _, reqURL := range test.Paths {
362			// make a request for the testURL with a querystring
363			resp, err := http.Get(reqURL)
364			if err != nil {
365				t.Fatalf("%s: expected request %s to succeed", test.Responder, reqURL)
366			}
367
368			data, err := ioutil.ReadAll(resp.Body)
369			if err != nil {
370				t.Fatalf("%s: %s error: %s", test.Responder, reqURL, err)
371			}
372
373			if string(data) != "hello world" {
374				t.Fatalf("%s: expected body of %s to be 'hello world'", test.Responder, reqURL)
375			}
376		}
377
378		DeactivateAndReset()
379	}
380}
381
382type dummyTripper struct{}
383
384func (d *dummyTripper) RoundTrip(*http.Request) (*http.Response, error) {
385	return nil, nil
386}
387
388func TestMockTransportInitialTransport(t *testing.T) {
389	DeactivateAndReset()
390
391	tripper := &dummyTripper{}
392	http.DefaultTransport = tripper
393
394	Activate()
395
396	if http.DefaultTransport == tripper {
397		t.Fatal("expected http.DefaultTransport to be a mock transport")
398	}
399
400	Deactivate()
401
402	if http.DefaultTransport != tripper {
403		t.Fatal("expected http.DefaultTransport to be dummy")
404	}
405}
406
407func TestMockTransportNonDefault(t *testing.T) {
408	// create a custom http client w/ custom Roundtripper
409	client := &http.Client{
410		Transport: &http.Transport{
411			Proxy: http.ProxyFromEnvironment,
412			Dial: (&net.Dialer{
413				Timeout:   60 * time.Second,
414				KeepAlive: 30 * time.Second,
415			}).Dial,
416			TLSHandshakeTimeout: 60 * time.Second,
417		},
418	}
419
420	// activate mocks for the client
421	ActivateNonDefault(client)
422	defer DeactivateAndReset()
423
424	body := "hello world!"
425
426	RegisterResponder("GET", testURL, NewStringResponder(200, body))
427
428	req, err := http.NewRequest("GET", testURL, nil)
429	if err != nil {
430		t.Fatal(err)
431	}
432
433	resp, err := client.Do(req)
434	if err != nil {
435		t.Fatal(err)
436	}
437
438	defer resp.Body.Close()
439
440	data, err := ioutil.ReadAll(resp.Body)
441	if err != nil {
442		t.Fatal(err)
443	}
444
445	if string(data) != body {
446		t.FailNow()
447	}
448}
449
450func TestMockTransportRespectsCancel(t *testing.T) {
451	Activate()
452	defer DeactivateAndReset()
453
454	const (
455		cancelNone = iota
456		cancelReq
457		cancelCtx
458	)
459
460	cases := []struct {
461		withCancel   int
462		cancelNow    bool
463		withPanic    bool
464		expectedBody string
465		expectedErr  error
466	}{
467		// No cancel specified at all. Falls back to normal behavior
468		{cancelNone, false, false, "hello world", nil},
469
470		// Cancel returns error
471		{cancelReq, true, false, "", errors.New("request canceled")},
472
473		// Cancel via context returns error
474		{cancelCtx, true, false, "", errors.New("context canceled")},
475
476		// Request can be cancelled but it is not cancelled.
477		{cancelReq, false, false, "hello world", nil},
478
479		// Request can be cancelled but it is not cancelled.
480		{cancelCtx, false, false, "hello world", nil},
481
482		// Panic in cancelled request is handled
483		{cancelReq, false, true, "", errors.New(`panic in responder: got "oh no"`)},
484
485		// Panic in cancelled request is handled
486		{cancelCtx, false, true, "", errors.New(`panic in responder: got "oh no"`)},
487	}
488
489	for _, c := range cases {
490		Reset()
491		if c.withPanic {
492			RegisterResponder("GET", testURL, func(r *http.Request) (*http.Response, error) {
493				time.Sleep(10 * time.Millisecond)
494				panic("oh no")
495			})
496		} else {
497			RegisterResponder("GET", testURL, func(r *http.Request) (*http.Response, error) {
498				time.Sleep(10 * time.Millisecond)
499				return NewStringResponse(http.StatusOK, "hello world"), nil
500			})
501		}
502
503		req, err := http.NewRequest("GET", testURL, nil)
504		if err != nil {
505			t.Fatal(err)
506		}
507
508		switch c.withCancel {
509		case cancelReq:
510			cancel := make(chan struct{}, 1)
511			req.Cancel = cancel // nolint: staticcheck
512			if c.cancelNow {
513				cancel <- struct{}{}
514			}
515		case cancelCtx:
516			ctx, cancel := context.WithCancel(req.Context())
517			req = req.WithContext(ctx)
518			if c.cancelNow {
519				cancel()
520			} else {
521				defer cancel() // avoid ctx leak
522			}
523		}
524
525		resp, err := http.DefaultClient.Do(req)
526
527		// If we expect an error but none was returned, it's fatal for this test...
528		if err == nil && c.expectedErr != nil {
529			t.Fatal("Error should not be nil")
530		}
531
532		if err != nil {
533			got := err.(*url.Error)
534			// Do not use reflect.DeepEqual as go 1.13 includes stack frames
535			// into errors issued by errors.New()
536			if c.expectedErr == nil || got.Err.Error() != c.expectedErr.Error() {
537				t.Errorf("Expected error: %v, got: %v", c.expectedErr, got.Err)
538			}
539		}
540
541		if c.expectedBody != "" {
542			assertBody(t, resp, c.expectedBody)
543		}
544	}
545}
546
547func TestMockTransportRespectsTimeout(t *testing.T) {
548	timeout := time.Millisecond
549	client := &http.Client{
550		Timeout: timeout,
551	}
552
553	ActivateNonDefault(client)
554	defer DeactivateAndReset()
555
556	RegisterResponder(
557		"GET", testURL,
558		func(r *http.Request) (*http.Response, error) {
559			time.Sleep(100 * timeout)
560			return NewStringResponse(http.StatusOK, ""), nil
561		},
562	)
563
564	_, err := client.Get(testURL)
565	if err == nil {
566		t.Fail()
567	}
568}
569
570func TestMockTransportCallCount(t *testing.T) {
571	Reset()
572	Activate()
573	defer Deactivate()
574
575	const (
576		url  = "https://github.com/path?b=1&a=2"
577		url2 = "https://gitlab.com/"
578	)
579
580	RegisterResponder("GET", url, NewStringResponder(200, "body"))
581	RegisterResponder("POST", "=~gitlab", NewStringResponder(200, "body"))
582
583	_, err := http.Get(url)
584	if err != nil {
585		t.Fatal(err)
586	}
587
588	buff := new(bytes.Buffer)
589	json.NewEncoder(buff).Encode("{}") // nolint: errcheck
590	_, err = http.Post(url2, "application/json", buff)
591	if err != nil {
592		t.Fatal(err)
593	}
594
595	_, err = http.Get(url)
596	if err != nil {
597		t.Fatal(err)
598	}
599
600	totalCallCount := GetTotalCallCount()
601	if totalCallCount != 3 {
602		t.Fatalf("did not track the total count of calls correctly. expected it to be 3, but it was %v", totalCallCount)
603	}
604
605	info := GetCallCountInfo()
606	expectedInfo := map[string]int{
607		"GET " + url: 2,
608		// Regexp match generates 2 entries:
609		"POST " + url2:  1, // the matched call
610		"POST =~gitlab": 1, // the regexp responder
611	}
612
613	if !reflect.DeepEqual(info, expectedInfo) {
614		t.Fatalf("did not correctly track the call count info. expected it to be \n %+v \n but it was \n %+v \n", expectedInfo, info)
615	}
616
617	Reset()
618
619	afterResetTotalCallCount := GetTotalCallCount()
620	if afterResetTotalCallCount != 0 {
621		t.Fatalf("did not reset the total count of calls correctly. expected it to be 0 after reset, but it was %v", afterResetTotalCallCount)
622	}
623}
624
625func TestRegisterResponderWithQuery(t *testing.T) {
626	// Just in case a panic occurs
627	defer DeactivateAndReset()
628
629	// create a custom http client w/ custom Roundtripper
630	client := &http.Client{
631		Transport: &http.Transport{
632			Proxy: http.ProxyFromEnvironment,
633			Dial: (&net.Dialer{
634				Timeout:   60 * time.Second,
635				KeepAlive: 30 * time.Second,
636			}).Dial,
637			TLSHandshakeTimeout: 60 * time.Second,
638		},
639	}
640
641	body := "hello world!"
642	testURLPath := "http://acme.test/api"
643
644	for _, test := range []struct {
645		URL     string
646		Queries []interface{}
647		URLs    []string
648	}{
649		{
650			Queries: []interface{}{
651				map[string]string{"a": "1", "b": "2"},
652				"a=1&b=2",
653				"b=2&a=1",
654				url.Values{"a": []string{"1"}, "b": []string{"2"}},
655			},
656			URLs: []string{
657				"http://acme.test/api?a=1&b=2",
658				"http://acme.test/api?b=2&a=1",
659			},
660		},
661		{
662			Queries: []interface{}{
663				url.Values{
664					"a": []string{"3", "2", "1"},
665					"b": []string{"4", "2"},
666					"c": []string{""}, // is the net/url way to record params without values
667					// Test:
668					//   u, _ := url.Parse("/hello/world?query")
669					//   fmt.Printf("%d<%s>\n", len(u.Query()["query"]), u.Query()["query"][0])
670					//   // prints "1<>"
671				},
672				"a=1&b=2&a=3&c&b=4&a=2",
673				"b=2&a=1&c=&b=4&a=2&a=3",
674				nil,
675			},
676			URLs: []string{
677				testURLPath + "?a=1&b=2&a=3&c&b=4&a=2",
678				testURLPath + "?a=1&b=2&a=3&c=&b=4&a=2",
679				testURLPath + "?b=2&a=1&c=&b=4&a=2&a=3",
680				testURLPath + "?b=2&a=1&c&b=4&a=2&a=3",
681			},
682		},
683	} {
684		for _, query := range test.Queries {
685			ActivateNonDefault(client)
686			RegisterResponderWithQuery("GET", testURLPath, query, NewStringResponder(200, body))
687
688			for _, url := range test.URLs {
689				req, err := http.NewRequest("GET", url, nil)
690				if err != nil {
691					t.Fatal(err)
692				}
693				resp, err := client.Do(req)
694				if err != nil {
695					t.Fatal(err)
696				}
697				data, err := ioutil.ReadAll(resp.Body)
698				resp.Body.Close()
699				if err != nil {
700					t.Fatal(err)
701				}
702				if string(data) != body {
703					t.Fatalf("query=%v URL=%s: %s ≠ %s", query, url, string(data), body)
704				}
705			}
706
707			DeactivateAndReset()
708		}
709	}
710}
711
712func TestRegisterResponderWithQueryPanic(t *testing.T) {
713	resp := NewStringResponder(200, "hello world!")
714
715	for _, test := range []struct {
716		Path        string
717		Query       interface{}
718		PanicPrefix string
719	}{
720		{
721			Path:        "foobar",
722			Query:       "%",
723			PanicPrefix: "RegisterResponderWithQuery bad query string: ",
724		},
725		{
726			Path:        "foobar",
727			Query:       1234,
728			PanicPrefix: "RegisterResponderWithQuery bad query type int. Only url.Values, map[string]string and string are allowed",
729		},
730		{
731			Path:        `=~regexp.*\z`,
732			Query:       "",
733			PanicPrefix: `path begins with "=~", RegisterResponder should be used instead of RegisterResponderWithQuery`,
734		},
735	} {
736		var (
737			didntPanic bool
738			panicVal   interface{}
739		)
740		func() {
741			defer func() {
742				panicVal = recover()
743			}()
744
745			RegisterResponderWithQuery("GET", test.Path, test.Query, resp)
746			didntPanic = true
747		}()
748
749		if didntPanic {
750			t.Fatalf("RegisterResponderWithQuery + query=%v did not panic", test.Query)
751		}
752
753		panicStr, ok := panicVal.(string)
754		if !ok || !strings.HasPrefix(panicStr, test.PanicPrefix) {
755			t.Fatalf(`RegisterResponderWithQuery + query=%v panic="%v" expected prefix="%v"`,
756				test.Query, panicVal, test.PanicPrefix)
757		}
758	}
759}
760
761func TestRegisterRegexpResponder(t *testing.T) {
762	Activate()
763	defer DeactivateAndReset()
764
765	rx := regexp.MustCompile("ex.mple")
766
767	RegisterRegexpResponder("GET", rx, NewStringResponder(200, "first"))
768	// Overwrite responder
769	RegisterRegexpResponder("GET", rx, NewStringResponder(200, "second"))
770
771	resp, err := http.Get(testURL)
772	if err != nil {
773		t.Fatalf("expected request %s to succeed", testURL)
774	}
775
776	data, err := ioutil.ReadAll(resp.Body)
777	if err != nil {
778		t.Fatalf("%s error: %s", testURL, err)
779	}
780
781	if string(data) != "second" {
782		t.Fatalf("expected body of %s to be 'hello world'", testURL)
783	}
784}
785
786func TestSubmatches(t *testing.T) {
787	req, err := http.NewRequest("GET", "/foo/bar", nil)
788	if err != nil {
789		t.Fatal(err)
790	}
791
792	var req2 *http.Request
793
794	t.Run("setSubmatches", func(t *testing.T) {
795		req2 = setSubmatches(req, nil)
796		if req2 != req {
797			t.Error("setSubmatches(req, nil) should return the same request")
798		}
799
800		req2 = setSubmatches(req, []string{})
801		if req2 != req {
802			t.Error("setSubmatches(req, []string{}) should return the same request")
803		}
804
805		req2 = setSubmatches(req, []string{"foo", "123", "-123", "12.3"})
806		if req2 == req {
807			t.Error("setSubmatches(req, []string{...}) should NOT return the same request")
808		}
809	})
810
811	t.Run("GetSubmatch", func(t *testing.T) {
812		_, err := GetSubmatch(req, 1)
813		if err != ErrSubmatchNotFound {
814			t.Errorf("Submatch should not be found in req: %v", err)
815		}
816
817		_, err = GetSubmatch(req2, 5)
818		if err != ErrSubmatchNotFound {
819			t.Errorf("Submatch #5 should not be found in req2: %v", err)
820		}
821
822		s, err := GetSubmatch(req2, 1)
823		if err != nil {
824			t.Errorf("GetSubmatch(req2, 1) failed: %v", err)
825		}
826		if s != "foo" {
827			t.Errorf("GetSubmatch(req2, 1) failed, got: %v, expected: foo", s)
828		}
829
830		s, err = GetSubmatch(req2, 4)
831		if err != nil {
832			t.Errorf("GetSubmatch(req2, 4) failed: %v", err)
833		}
834		if s != "12.3" {
835			t.Errorf("GetSubmatch(req2, 4) failed, got: %v, expected: 12.3", s)
836		}
837
838		s = MustGetSubmatch(req2, 4)
839		if s != "12.3" {
840			t.Errorf("GetSubmatch(req2, 4) failed, got: %v, expected: 12.3", s)
841		}
842	})
843
844	t.Run("GetSubmatchAsInt", func(t *testing.T) {
845		_, err := GetSubmatchAsInt(req, 1)
846		if err != ErrSubmatchNotFound {
847			t.Errorf("Submatch should not be found in req: %v", err)
848		}
849
850		_, err = GetSubmatchAsInt(req2, 4) // not an int
851		if err == nil || err == ErrSubmatchNotFound {
852			t.Errorf("Submatch should not be an int64: %v", err)
853		}
854
855		i, err := GetSubmatchAsInt(req2, 3)
856		if err != nil {
857			t.Errorf("GetSubmatchAsInt(req2, 3) failed: %v", err)
858		}
859		if i != -123 {
860			t.Errorf("GetSubmatchAsInt(req2, 3) failed, got: %d, expected: -123", i)
861		}
862
863		i = MustGetSubmatchAsInt(req2, 3)
864		if i != -123 {
865			t.Errorf("MustGetSubmatchAsInt(req2, 3) failed, got: %d, expected: -123", i)
866		}
867	})
868
869	t.Run("GetSubmatchAsUint", func(t *testing.T) {
870		_, err := GetSubmatchAsUint(req, 1)
871		if err != ErrSubmatchNotFound {
872			t.Errorf("Submatch should not be found in req: %v", err)
873		}
874
875		_, err = GetSubmatchAsUint(req2, 3) // not a uint
876		if err == nil || err == ErrSubmatchNotFound {
877			t.Errorf("Submatch should not be an uint64: %v", err)
878		}
879
880		u, err := GetSubmatchAsUint(req2, 2)
881		if err != nil {
882			t.Errorf("GetSubmatchAsUint(req2, 2) failed: %v", err)
883		}
884		if u != 123 {
885			t.Errorf("GetSubmatchAsUint(req2, 2) failed, got: %d, expected: 123", u)
886		}
887
888		u = MustGetSubmatchAsUint(req2, 2)
889		if u != 123 {
890			t.Errorf("MustGetSubmatchAsUint(req2, 2) failed, got: %d, expected: 123", u)
891		}
892	})
893
894	t.Run("GetSubmatchAsFloat", func(t *testing.T) {
895		_, err := GetSubmatchAsFloat(req, 1)
896		if err != ErrSubmatchNotFound {
897			t.Errorf("Submatch should not be found in req: %v", err)
898		}
899
900		_, err = GetSubmatchAsFloat(req2, 1) // not a float
901		if err == nil || err == ErrSubmatchNotFound {
902			t.Errorf("Submatch should not be an float64: %v", err)
903		}
904
905		f, err := GetSubmatchAsFloat(req2, 4)
906		if err != nil {
907			t.Errorf("GetSubmatchAsFloat(req2, 4) failed: %v", err)
908		}
909		if f != 12.3 {
910			t.Errorf("GetSubmatchAsFloat(req2, 4) failed, got: %f, expected: 12.3", f)
911		}
912
913		f = MustGetSubmatchAsFloat(req2, 4)
914		if f != 12.3 {
915			t.Errorf("MustGetSubmatchAsFloat(req2, 4) failed, got: %f, expected: 12.3", f)
916		}
917	})
918
919	t.Run("GetSubmatch* panics", func(t *testing.T) {
920		for _, test := range []struct {
921			Name        string
922			Fn          func()
923			PanicPrefix string
924		}{
925			{
926				Name:        "GetSubmatch & n < 1",
927				Fn:          func() { GetSubmatch(req, 0) }, // nolint: errcheck
928				PanicPrefix: "getting submatches starts at 1, not 0",
929			},
930			{
931				Name:        "MustGetSubmatch",
932				Fn:          func() { MustGetSubmatch(req, 1) },
933				PanicPrefix: "GetSubmatch failed: " + ErrSubmatchNotFound.Error(),
934			},
935			{
936				Name:        "MustGetSubmatchAsInt",
937				Fn:          func() { MustGetSubmatchAsInt(req2, 4) }, // not an int
938				PanicPrefix: "GetSubmatchAsInt failed: ",
939			},
940			{
941				Name:        "MustGetSubmatchAsUint",
942				Fn:          func() { MustGetSubmatchAsUint(req2, 3) }, // not a uint
943				PanicPrefix: "GetSubmatchAsUint failed: ",
944			},
945			{
946				Name:        "GetSubmatchAsFloat",
947				Fn:          func() { MustGetSubmatchAsFloat(req2, 1) }, // not a float
948				PanicPrefix: "GetSubmatchAsFloat failed: ",
949			},
950		} {
951			var (
952				didntPanic bool
953				panicVal   interface{}
954			)
955			func() {
956				defer func() { panicVal = recover() }()
957				test.Fn()
958				didntPanic = true
959			}()
960
961			if didntPanic {
962				t.Errorf("%s did not panic", test.Name)
963			}
964
965			panicStr, ok := panicVal.(string)
966			if !ok || !strings.HasPrefix(panicStr, test.PanicPrefix) {
967				t.Errorf(`%s panic="%v" expected prefix="%v"`, test.Name, panicVal, test.PanicPrefix)
968			}
969		}
970	})
971
972	t.Run("Full test", func(t *testing.T) {
973		Activate()
974		defer DeactivateAndReset()
975
976		var (
977			id       uint64
978			delta    float64
979			deltaStr string
980			inc      int64
981		)
982		RegisterResponder("GET", `=~^/id/(\d+)\?delta=(\d+(?:\.\d*)?)&inc=(-?\d+)\z`,
983			func(req *http.Request) (*http.Response, error) {
984				id = MustGetSubmatchAsUint(req, 1)
985				delta = MustGetSubmatchAsFloat(req, 2)
986				deltaStr = MustGetSubmatch(req, 2)
987				inc = MustGetSubmatchAsInt(req, 3)
988
989				return NewStringResponse(http.StatusOK, "OK"), nil
990			})
991
992		resp, err := http.Get("http://example.tld/id/123?delta=1.2&inc=-5")
993		if err != nil {
994			t.Fatal(err)
995		}
996		assertBody(t, resp, "OK")
997
998		// Check submatches
999		if id != 123 {
1000			t.Errorf("seems MustGetSubmatchAsUint failed, got: %d, expected: 123", id)
1001		}
1002		if delta != 1.2 {
1003			t.Errorf("seems MustGetSubmatchAsFloat failed, got: %f, expected: 1.2", delta)
1004		}
1005		if deltaStr != "1.2" {
1006			t.Errorf("seems MustGetSubmatch failed, got: %v, expected: 1.2", deltaStr)
1007		}
1008		if inc != -5 {
1009			t.Errorf("seems MustGetSubmatchAsInt failed, got: %d, expected: 123", inc)
1010		}
1011	})
1012}
1013
1014func TestCheckStackTracer(t *testing.T) {
1015	req, err := http.NewRequest("GET", "http://foo.bar/", nil)
1016	if err != nil {
1017		t.Fatal(err)
1018	}
1019
1020	// no error
1021	gotErr := checkStackTracer(req, nil)
1022	if gotErr != nil {
1023		t.Errorf(`checkStackTracer(nil) should return nil, not %v`, gotErr)
1024	}
1025
1026	// Classic error
1027	err = errors.New("error")
1028	gotErr = checkStackTracer(req, err)
1029	if err != gotErr {
1030		t.Errorf(`checkStackTracer(err) should return %v, not %v`, err, gotErr)
1031	}
1032
1033	// stackTracer without customFn
1034	origErr := errors.New("foo")
1035	errTracer := stackTracer{
1036		err: origErr,
1037	}
1038	gotErr = checkStackTracer(req, errTracer)
1039	if gotErr != origErr {
1040		t.Errorf(`Returned error mismatch, expected: %v, got: %v`, origErr, gotErr)
1041	}
1042
1043	// stackTracer with nil error & without customFn
1044	errTracer = stackTracer{}
1045	gotErr = checkStackTracer(req, errTracer)
1046	if gotErr != nil {
1047		t.Errorf(`Returned error mismatch, expected: nil, got: %v`, gotErr)
1048	}
1049
1050	// stackTracer
1051	var mesg string
1052	errTracer = stackTracer{
1053		err: origErr,
1054		customFn: func(args ...interface{}) {
1055			mesg = args[0].(string)
1056		},
1057	}
1058	gotErr = checkStackTracer(req, errTracer)
1059	if !strings.HasPrefix(mesg, "foo\nCalled from ") || strings.HasSuffix(mesg, "\n") {
1060		t.Errorf(`mesg does not match "^foo\nCalled from .*[^\n]\z", it is "` + mesg + `"`)
1061	}
1062	if gotErr != origErr {
1063		t.Errorf(`Returned error mismatch, expected: %v, got: %v`, origErr, gotErr)
1064	}
1065
1066	// stackTracer with nil error but customFn
1067	mesg = ""
1068	errTracer = stackTracer{
1069		customFn: func(args ...interface{}) {
1070			mesg = args[0].(string)
1071		},
1072	}
1073	gotErr = checkStackTracer(req, errTracer)
1074	if !strings.HasPrefix(mesg, "GET http://foo.bar/\nCalled from ") || strings.HasSuffix(mesg, "\n") {
1075		t.Errorf(`mesg does not match "^foo\nCalled from .*[^\n]\z", it is "` + mesg + `"`)
1076	}
1077	if gotErr != nil {
1078		t.Errorf(`Returned error mismatch, expected: nil, got: %v`, gotErr)
1079	}
1080
1081	// Full test using Trace() Responder
1082	Activate()
1083	defer Deactivate()
1084
1085	const url = "https://foo.bar/"
1086	mesg = ""
1087	RegisterResponder("GET", url,
1088		NewStringResponder(200, "{}").
1089			Trace(func(args ...interface{}) { mesg = args[0].(string) }))
1090
1091	resp, err := http.Get(url)
1092	if err != nil {
1093		t.Fatal(err)
1094	}
1095	defer resp.Body.Close()
1096
1097	data, err := ioutil.ReadAll(resp.Body)
1098	if err != nil {
1099		t.Fatal(err)
1100	}
1101
1102	if string(data) != "{}" {
1103		t.FailNow()
1104	}
1105
1106	// Check that first frame is the net/http.Get() call
1107	if !strings.HasPrefix(mesg, "GET https://foo.bar/\nCalled from net/http.Get()\n    at ") ||
1108		strings.HasSuffix(mesg, "\n") {
1109		t.Errorf("Bad mesg: <%v>", mesg)
1110	}
1111}
1112