From b1c767b8f785ec3bf2810fd751d0938af7b0a941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Tue, 21 Dec 2021 18:36:12 +0100 Subject: [PATCH] feat: registering a nil responder unregisters it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit and add some doc Signed-off-by: Maxime Soulé --- transport.go | 90 +++++++++++++++++++++++++++++++++++++++-------- transport_test.go | 46 +++++++++++++++++++++++- 2 files changed, 121 insertions(+), 15 deletions(-) diff --git a/transport.go b/transport.go index 4523f0a..29a041f 100644 --- a/transport.go +++ b/transport.go @@ -364,7 +364,14 @@ func (m *MockTransport) checkMethod(method string) { // GetCallCountInfo(). As 2 regexps can match the same URL, the regexp // responders are tested in the order they are registered. Registering // an already existing regexp responder (same method & same regexp -// string) replaces its responder but does not change its position. +// string) replaces its responder, but does not change its position. +// +// Registering an already existing responder resets the corresponding +// statistics as returned by GetCallCountInfo(). +// +// Registering a nil Responder removes the existing one and the +// corresponding statistics as returned by GetCallCountInfo(). It does +// nothing if it does not already exist. // // See RegisterRegexpResponder() to directly pass a *regexp.Regexp. // @@ -410,31 +417,49 @@ func (m *MockTransport) RegisterResponder(method, url string, responder Responde } m.mu.Lock() - m.responders[key] = responder - m.callCountInfo[key] = 0 + if responder == nil { + delete(m.responders, key) + delete(m.callCountInfo, key) + } else { + m.responders[key] = responder + m.callCountInfo[key] = 0 + } m.mu.Unlock() } -func (m *MockTransport) registerRegexpResponder(regexpResponder regexpResponder) { +func (m *MockTransport) registerRegexpResponder(rxResp regexpResponder) { m.mu.Lock() defer m.mu.Unlock() found: for { for i, rr := range m.regexpResponders { - if rr.method == regexpResponder.method && rr.origRx == regexpResponder.origRx { - m.regexpResponders[i] = regexpResponder + if rr.method == rxResp.method && rr.origRx == rxResp.origRx { + if rxResp.responder == nil { + copy(m.regexpResponders[:i], m.regexpResponders[i+1:]) + m.regexpResponders[len(m.regexpResponders)-1] = regexpResponder{} + m.regexpResponders = m.regexpResponders[:len(m.regexpResponders)-1] + } else { + m.regexpResponders[i] = rxResp + } break found } } - m.regexpResponders = append(m.regexpResponders, regexpResponder) + if rxResp.responder != nil { + m.regexpResponders = append(m.regexpResponders, rxResp) + } break // nolint: staticcheck } - m.callCountInfo[internal.RouteKey{ - Method: regexpResponder.method, - URL: regexpResponder.origRx, - }] = 0 + key := internal.RouteKey{ + Method: rxResp.method, + URL: rxResp.origRx, + } + if rxResp.responder == nil { + delete(m.callCountInfo, key) + } else { + m.callCountInfo[key] = 0 + } } // RegisterRegexpResponder adds a new responder, associated with a given @@ -446,7 +471,12 @@ found: // As 2 regexps can match the same URL, the regexp responders are // tested in the order they are registered. Registering an already // existing regexp responder (same method & same regexp string) -// replaces its responder but does not change its position. +// replaces its responder, but does not change its position, and +// resets the corresponding statistics as returned by GetCallCountInfo(). +// +// Registering a nil Responder removes the existing one and the +// corresponding statistics as returned by GetCallCountInfo(). It does +// nothing if it does not already exist. // // A "=~" prefix is added to the stringified regexp in the statistics // returned by GetCallCountInfo(). @@ -483,6 +513,13 @@ func (m *MockTransport) RegisterRegexpResponder(method string, urlRegexp *regexp // Unlike RegisterResponder, path cannot be prefixed by "=~" to say it // is a regexp. If it is, a panic occurs. // +// Registering an already existing responder resets the corresponding +// statistics as returned by GetCallCountInfo(). +// +// Registering a nil Responder removes the existing one and the +// corresponding statistics as returned by GetCallCountInfo(). It does +// nothing if it does not already exist. +// // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible // mistake. This panic can be disabled by setting m.DontCheckMethod to @@ -581,6 +618,9 @@ func sortedQuery(m url.Values) string { // at /go/src/testing/testing.go:865 // testing.tRunner() // at /go/src/runtime/asm_amd64.s:1337 +// +// If responder is passed as nil, the default behavior +// (httpmock.ConnectionFailure) is re-enabled. func (m *MockTransport) RegisterNoResponder(responder Responder) { m.mu.Lock() m.noResponder = responder @@ -812,7 +852,14 @@ func DeactivateAndReset() { // GetCallCountInfo(). As 2 regexps can match the same URL, the regexp // responders are tested in the order they are registered. Registering // an already existing regexp responder (same method & same regexp -// string) replaces its responder but does not change its position. +// string) replaces its responder, but does not change its position. +// +// Registering an already existing responder resets the corresponding +// statistics as returned by GetCallCountInfo(). +// +// Registering a nil Responder removes the existing one and the +// corresponding statistics as returned by GetCallCountInfo(). It does +// nothing if it does not already exist. // // See RegisterRegexpResponder() to directly pass a *regexp.Regexp. // @@ -852,7 +899,12 @@ func RegisterResponder(method, url string, responder Responder) { // As 2 regexps can match the same URL, the regexp responders are // tested in the order they are registered. Registering an already // existing regexp responder (same method & same regexp string) -// replaces its responder but does not change its position. +// replaces its responder, but does not change its position, and +// resets the corresponding statistics as returned by GetCallCountInfo(). +// +// Registering a nil Responder removes the existing one and the +// corresponding statistics as returned by GetCallCountInfo(). It does +// nothing if it does not already exist. // // A "=~" prefix is added to the stringified regexp in the statistics // returned by GetCallCountInfo(). @@ -879,6 +931,16 @@ func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder // If the query type is not recognized or the string cannot be parsed // using net/url.ParseQuery, a panic() occurs. // +// Unlike RegisterResponder, path cannot be prefixed by "=~" to say it +// is a regexp. If it is, a panic occurs. +// +// Registering an already existing responder resets the corresponding +// statistics as returned by GetCallCountInfo(). +// +// Registering a nil Responder removes the existing one and the +// corresponding statistics as returned by GetCallCountInfo(). It does +// nothing if it does not already exist. +// // Example using a net/url.Values: // func TestFetchArticles(t *testing.T) { // httpmock.Activate() diff --git a/transport_test.go b/transport_test.go index dce9b65..8bebf43 100644 --- a/transport_test.go +++ b/transport_test.go @@ -153,7 +153,7 @@ func TestMockTransportReset(t *testing.T) { t.Fatal("expected no responders at this point") } - RegisterResponder("GET", testURL, nil) + RegisterResponder("GET", testURL, NewStringResponder(200, "hey")) if DefaultTransport.NumResponders() != 1 { t.Fatal("expected one responder") @@ -687,9 +687,25 @@ func TestMockTransportCallCountZero(t *testing.T) { if !reflect.DeepEqual(info, expectedInfo) { t.Fatalf("did not correctly reset the call count info. expected it to be \n %+v\n but it was \n %+v", expectedInfo, info) } + + // Unregister each responder + RegisterResponder("GET", url, nil) + RegisterResponder("POST", "=~gitlab", nil) + + info = GetCallCountInfo() + expectedInfo = map[string]int{ + // this one remains as it is not directly related to a registered + // responder but a consequence of a regexp match + "POST " + url2: 0, + } + if !reflect.DeepEqual(info, expectedInfo) { + t.Fatalf("did not correctly reset the call count info. expected it to be \n %+v\n but it was \n %+v", expectedInfo, info) + } } func TestRegisterResponderWithQuery(t *testing.T) { + Reset() + // Just in case a panic occurs defer DeactivateAndReset() @@ -768,6 +784,34 @@ func TestRegisterResponderWithQuery(t *testing.T) { assertBody(t, resp, body) } + if info := GetCallCountInfo(); len(info) != 1 { + t.Fatalf("%s: len(GetCallCountInfo()) should be 1 but contains %+v", testURLPath, info) + } + + // Remove... + RegisterResponderWithQuery("GET", testURLPath, query, nil) + if info := GetCallCountInfo(); len(info) != 0 { + t.Fatalf("did not correctly reset the call count info, it still contains %+v", info) + } + + for _, url := range test.URLs { + t.Logf("query=%v URL=%s", query, url) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatal(err) + } + + _, err = client.Do(req) + if err == nil { + t.Fatalf("No error occurred for %s", url) + } + + if !strings.HasSuffix(err.Error(), "no responder found") { + t.Errorf("Not expected error suffix: %s", err) + } + } + DeactivateAndReset() } }