From e5b3157e589667f4e753ee708d1401d5d7987a00 Mon Sep 17 00:00:00 2001 From: George Blue Date: Thu, 19 Aug 2021 21:34:08 +0100 Subject: [PATCH] feat: formatter for HTTP responses (#461) 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. --- matchers/have_http_status_matcher.go | 38 ++++++++++++++++++++-- matchers/have_http_status_matcher_test.go | 39 ++++++++++++++++++++--- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/matchers/have_http_status_matcher.go b/matchers/have_http_status_matcher.go index 3ce4800b7..6dfb35fa9 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) + 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..741f8d359 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/ioutil" "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: ioutil.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: ioutil.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]) }) }) })