From dfbe58e381fd971faf77112348423ef56cf2510d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Va=C5=A1ko?= Date: Mon, 23 Oct 2023 14:25:06 +0200 Subject: [PATCH] assert: Add HTTP builder to combine request x response using builder. --- assert/assertion_format.go | 12 +++++++ assert/assertion_forward.go | 24 +++++++++++++ assert/http_assertions.go | 48 +++++++++++++++++++++++++ assert/http_assertions_test.go | 25 +++++++++++++ assert/http_options.go | 65 ++++++++++++++++++++++++++++++++++ require/require.go | 30 ++++++++++++++++ require/require_forward.go | 24 +++++++++++++ 7 files changed, 228 insertions(+) create mode 100644 assert/http_options.go diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 53f9c8e8b..c950d8085 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -264,6 +264,18 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) } +// HTTPf asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// assert.HTTPf(t, myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c"))), "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, options ...HttpOption) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTP(t, handler, method, url, values, options...) +} + // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 5a2974beb..7214b6280 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -520,6 +520,18 @@ func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args . return Greaterf(a.t, e1, e2, msg, args...) } +// HTTP asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// a.HTTP(myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c")))) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTP(handler http.HandlerFunc, method string, url string, values url.Values, options ...HttpOption) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTP(a.t, handler, method, url, values, options...) +} + // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // @@ -668,6 +680,18 @@ func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url s return HTTPSuccessf(a.t, handler, method, url, values, msg, args...) } +// HTTPf asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// a.HTTPf(myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c"))), "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPf(handler http.HandlerFunc, method string, url string, values url.Values, options ...HttpOption) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPf(a.t, handler, method, url, values, options...) +} + // Implements asserts that an object is implemented by the specified interface. // // a.Implements((*MyInterface)(nil), new(MyObject)) diff --git a/assert/http_assertions.go b/assert/http_assertions.go index 8b82ca8d2..75f11b3d9 100644 --- a/assert/http_assertions.go +++ b/assert/http_assertions.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "reflect" "strings" ) @@ -163,3 +164,50 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin return !contains } + +// HTTP asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// assert.HTTP(t, myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c")))) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTP(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, options ...HttpOption) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + b := &builder{} + for _, option := range options { + err := option(b) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build http options, got error: %s", err)) + } + } + + w := httptest.NewRecorder() + req, err := http.NewRequest(method, url, b.body) + if b.err == nil && err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) + } else if b.err != nil { + return err == b.err + } + + req.Header = b.requestHeader + req.URL.RawQuery = values.Encode() + handler(w, req) + if w.Code != b.code { + Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), w.Code)) + return false + } + + if b.responseHeader != nil && !reflect.DeepEqual(w.HeaderMap, b.responseHeader) { + Fail(t, fmt.Sprintf("Expected HTTP header to be equal for %q but received %v", url+"?"+values.Encode(), w.HeaderMap)) + return false + } + + contains := strings.Contains(w.Body.String(), b.expectedBody.String()) + if !contains { + Fail(t, fmt.Sprintf("Expected HTTP body to be equal for %q but received %s", url+"?"+values.Encode(), w.Body.String())) + } + + return contains +} diff --git a/assert/http_assertions_test.go b/assert/http_assertions_test.go index 8da117e28..315132826 100644 --- a/assert/http_assertions_test.go +++ b/assert/http_assertions_test.go @@ -1,6 +1,7 @@ package assert import ( + "bytes" "fmt" "io" "net/http" @@ -192,3 +193,27 @@ func TestHttpBodyWrappers(t *testing.T) { assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) } + +func TestHTTPBuilder(t *testing.T) { + assert := New(t) + mockAssert := New(new(testing.T)) + + // Test status codes + assert.Equal(mockAssert.HTTP(httpOK, "GET", "/", nil, WithCode(200)), true) + assert.Equal(mockAssert.HTTP(httpRedirect, "GET", "/", nil, WithCode(200)), false) + assert.Equal(mockAssert.HTTP(httpError, "GET", "/", nil, WithCode(200)), false) + + assert.Equal(mockAssert.HTTP(httpOK, "GET", "/", nil, WithCode(200)), true) + assert.Equal(mockAssert.HTTP(httpRedirect, "GET", "/", nil, WithCode(307)), true) + assert.Equal(mockAssert.HTTP(httpError, "GET", "/", nil, WithCode(500)), true) + + // Test codes and body + assert.True(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithExpectedBody(*bytes.NewBufferString("Hello, World!")))) + assert.True(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithExpectedBody(*bytes.NewBufferString("World")))) + assert.False(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithExpectedBody(*bytes.NewBufferString("world")))) + + // Test codes headers and body + assert.True(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithResponseHeader(http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}), WithExpectedBody(*bytes.NewBufferString("Hello, World!")))) + assert.True(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithResponseHeader(http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}), WithExpectedBody(*bytes.NewBufferString("World")))) + assert.False(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithResponseHeader(http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}), WithExpectedBody(*bytes.NewBufferString("world")))) +} diff --git a/assert/http_options.go b/assert/http_options.go new file mode 100644 index 000000000..422bc5409 --- /dev/null +++ b/assert/http_options.go @@ -0,0 +1,65 @@ +package assert + +import ( + "bytes" + "errors" + "io" + http "net/http" +) + +type builder struct { + code int + body io.ReadCloser + expectedBody bytes.Buffer + requestHeader http.Header + responseHeader http.Header + err error +} + +type HttpOption func(*builder) error + +func WithCode(code int) HttpOption { + return func(b *builder) error { + if code < 100 || code > 511 { + return errors.New("Given HTTP code is outside range of possible values assignement") + } + + b.code = code + return nil + } +} + +func WithErr(err error) HttpOption { + return func(b *builder) error { + b.err = err + return nil + } +} + +func WithBody(body io.ReadCloser) HttpOption { + return func(b *builder) error { + b.body = body + return nil + } +} + +func WithExpectedBody(expectedBody bytes.Buffer) HttpOption { + return func(b *builder) error { + b.expectedBody = expectedBody + return nil + } +} + +func WithRequestHeader(requestHeader http.Header) HttpOption { + return func(b *builder) error { + b.requestHeader = requestHeader + return nil + } +} + +func WithResponseHeader(responseHeader http.Header) HttpOption { + return func(b *builder) error { + b.responseHeader = responseHeader + return nil + } +} diff --git a/require/require.go b/require/require.go index fa3792b56..1e5cd6c01 100644 --- a/require/require.go +++ b/require/require.go @@ -653,6 +653,21 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in t.FailNow() } +// HTTP asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// assert.HTTP(t, myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c")))) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTP(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, options ...assert.HttpOption) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTP(t, handler, method, url, values, options...) { + return + } + t.FailNow() +} + // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // @@ -837,6 +852,21 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin t.FailNow() } +// HTTPf asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// assert.HTTPf(t, myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c"))), "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, options ...assert.HttpOption) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPf(t, handler, method, url, values, options...) { + return + } + t.FailNow() +} + // Implements asserts that an object is implemented by the specified interface. // // assert.Implements(t, (*MyInterface)(nil), new(MyObject)) diff --git a/require/require_forward.go b/require/require_forward.go index 99e19ea9a..2d7ce954c 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -521,6 +521,18 @@ func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args . Greaterf(a.t, e1, e2, msg, args...) } +// HTTP asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// a.HTTP(myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c")))) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTP(handler http.HandlerFunc, method string, url string, values url.Values, options ...assert.HttpOption) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTP(a.t, handler, method, url, values, options...) +} + // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // @@ -669,6 +681,18 @@ func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url s HTTPSuccessf(a.t, handler, method, url, values, msg, args...) } +// HTTPf asserts that a specfied handler returns set of expected values given by HttpOptions. +// +// a.HTTPf(myHandler, "www.google.com", nil, WithCode(200), WithBody("I'm Feeling Lucky"), WithRequestHeader(http.Header{"a": []string{"b"}}, WithExpectedBody(bytes.NewBuffer("c"))), "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPf(handler http.HandlerFunc, method string, url string, values url.Values, options ...assert.HttpOption) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPf(a.t, handler, method, url, values, options...) +} + // Implements asserts that an object is implemented by the specified interface. // // a.Implements((*MyInterface)(nil), new(MyObject))