From 8786502e727a36c20c7bfd90f35e386e5ef30630 Mon Sep 17 00:00:00 2001 From: George Blue Date: Fri, 13 Aug 2021 10:55:55 +0100 Subject: [PATCH] feat: formatter for HTTP responses Previously when the HaveHTTPStatus() matcher failed, the whole HTTP response object was printed out, and any message was rendered in bytes rather than as a string. This introduces a formatter for HTTP responses, so that the key data is presented in a helpful format. --- go.sum | 2 -- matchers/have_http_status_matcher.go | 38 ++++++++++++++++++++-- matchers/have_http_status_matcher_test.go | 39 ++++++++++++++++++++--- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/go.sum b/go.sum index 177d5e876..a685c9050 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -67,7 +66,6 @@ golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/matchers/have_http_status_matcher.go b/matchers/have_http_status_matcher.go index 3ce4800b7..f7fc4e67a 100644 --- a/matchers/have_http_status_matcher.go +++ b/matchers/have_http_status_matcher.go @@ -2,8 +2,11 @@ package matchers import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" + "reflect" + "strings" "github.com/onsi/gomega/format" ) @@ -34,9 +37,40 @@ func (matcher *HaveHTTPStatusMatcher) Match(actual interface{}) (success bool, e } func (matcher *HaveHTTPStatusMatcher) FailureMessage(actual interface{}) (message string) { - return format.Message(actual, "to have HTTP status", matcher.Expected) + return fmt.Sprintf("Expected\n%s\n%s\n%s", formatHttpResponse(actual), "to have HTTP status", format.Object(matcher.Expected, 1)) } func (matcher *HaveHTTPStatusMatcher) NegatedFailureMessage(actual interface{}) (message string) { - return format.Message(actual, "not to have HTTP status", matcher.Expected) + return fmt.Sprintf("Expected\n%s\n%s\n%s", formatHttpResponse(actual), "not to have HTTP status", format.Object(matcher.Expected, 1)) +} + +func formatHttpResponse(input interface{}) string { + var resp *http.Response + switch r := input.(type) { + case *http.Response: + resp = r + case *httptest.ResponseRecorder: + resp = r.Result() + default: + return "cannot format invalid HTTP response" + } + + body := "" + if resp.Body != nil { + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) // ioutil is deprecated as of Go 1.16, but we still need to work with Go 1.15 + if err != nil { + data = []byte("") + } + body = format.Object(string(data), 0) + } + + var s strings.Builder + s.WriteString(fmt.Sprintf("%s<%s>: {\n", format.Indent, reflect.TypeOf(input))) + s.WriteString(fmt.Sprintf("%s%sStatus: %s\n", format.Indent, format.Indent, format.Object(resp.Status, 0))) + s.WriteString(fmt.Sprintf("%s%sStatusCode: %s\n", format.Indent, format.Indent, format.Object(resp.StatusCode, 0))) + s.WriteString(fmt.Sprintf("%s%sBody: %s\n", format.Indent, format.Indent, body)) + s.WriteString(fmt.Sprintf("%s}", format.Indent)) + + return s.String() } diff --git a/matchers/have_http_status_matcher_test.go b/matchers/have_http_status_matcher_test.go index 9e46a5650..a450bde7c 100644 --- a/matchers/have_http_status_matcher_test.go +++ b/matchers/have_http_status_matcher_test.go @@ -1,8 +1,10 @@ package matchers_test import ( + "io" "net/http" "net/http/httptest" + "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -17,6 +19,7 @@ var _ = Describe("HaveHTTPStatus", func() { Expect(resp).NotTo(HaveHTTPStatus(http.StatusNotFound)) }) }) + When("EXPECTED is string", func() { It("matches the Status", func() { resp := &http.Response{Status: "200 OK"} @@ -24,6 +27,7 @@ var _ = Describe("HaveHTTPStatus", func() { Expect(resp).NotTo(HaveHTTPStatus("404 Not Found")) }) }) + When("EXPECTED is anything else", func() { It("does not match", func() { failures := InterceptGomegaFailures(func() { @@ -43,6 +47,7 @@ var _ = Describe("HaveHTTPStatus", func() { Expect(resp).NotTo(HaveHTTPStatus(http.StatusNotFound)) }) }) + When("EXPECTED is string", func() { It("matches the Status", func() { resp := &httptest.ResponseRecorder{Code: http.StatusOK} @@ -50,6 +55,7 @@ var _ = Describe("HaveHTTPStatus", func() { Expect(resp).NotTo(HaveHTTPStatus("404 Not Found")) }) }) + When("EXPECTED is anything else", func() { It("does not match", func() { failures := InterceptGomegaFailures(func() { @@ -73,19 +79,44 @@ var _ = Describe("HaveHTTPStatus", func() { Describe("FailureMessage", func() { It("returns message", func() { failures := InterceptGomegaFailures(func() { - resp := &http.Response{StatusCode: http.StatusBadGateway} + resp := &http.Response{ + StatusCode: http.StatusBadGateway, + Status: "502 Bad Gateway", + Body: io.NopCloser(strings.NewReader("did not like it")), + } Expect(resp).To(HaveHTTPStatus(http.StatusOK)) }) - Expect(failures).To(ConsistOf(MatchRegexp("Expected(.|\n)*StatusCode: 502(.|\n)*to have HTTP status\n : 200"))) + Expect(failures).To(HaveLen(1)) + Expect(failures[0]).To(Equal(`Expected + <*http.Response>: { + Status: : "502 Bad Gateway" + StatusCode: : 502 + Body: : "did not like it" + } +to have HTTP status + : 200`), failures[0]) }) }) + Describe("NegatedFailureMessage", func() { It("returns message", func() { failures := InterceptGomegaFailures(func() { - resp := &http.Response{StatusCode: http.StatusOK} + resp := &http.Response{ + StatusCode: http.StatusOK, + Status: "200 OK", + Body: io.NopCloser(strings.NewReader("got it!")), + } Expect(resp).NotTo(HaveHTTPStatus(http.StatusOK)) }) - Expect(failures).To(ConsistOf(MatchRegexp("Expected(.|\n)*StatusCode: 200(.|\n)*not to have HTTP status\n : 200"))) + Expect(failures).To(HaveLen(1)) + Expect(failures[0]).To(Equal(`Expected + <*http.Response>: { + Status: : "200 OK" + StatusCode: : 200 + Body: : "got it!" + } +not to have HTTP status + : 200`), failures[0]) }) }) })