Skip to content

Commit

Permalink
Updated mock handler (#5)
Browse files Browse the repository at this point in the history
* Added function to writer CORS headers

* Added Content-Type type resolving

* Added correct response and header writing

* Replace headers to constatant

* Fixed issues
  • Loading branch information
Evgeny Abramovich authored Nov 1, 2022
1 parent 1fdf4fd commit 78369b9
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 56 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
atomicgo.dev/keyboard v0.2.8 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
github.com/gookit/color v1.5.2 // indirect
github.com/lithammer/fuzzysearch v1.1.5 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
Expand Down
13 changes: 13 additions & 0 deletions internal/infrastructure/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package infrastructure

import (
"net/http"

"github.com/go-http-utils/headers"
)

func WriteCorsHeaders(header http.Header) {
header.Set(headers.AccessControlAllowOrigin, "*")
header.Set(headers.AccessControlAllowCredentials, "true")
header.Set(headers.AccessControlAllowMethods, "GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS")
}
80 changes: 80 additions & 0 deletions internal/infrastructure/cors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package infrastructure_test

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/evg4b/uncors/internal/infrastructure"
"github.com/evg4b/uncors/testing/testutils"
"github.com/go-http-utils/headers"
"github.com/stretchr/testify/assert"
)

func TestWriteCorsHeaders(t *testing.T) {
tests := []struct {
name string
recorderFactory func() *httptest.ResponseRecorder
expected http.Header
}{
{
name: "should append data in empty writer",
recorderFactory: httptest.NewRecorder,
expected: map[string][]string{
headers.AccessControlAllowOrigin: {"*"},
headers.AccessControlAllowCredentials: {"true"},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
{
name: "should append data in filled writer",
recorderFactory: func() *httptest.ResponseRecorder {
writer := httptest.NewRecorder()
writer.Header().Set("Test-Header", "true")
writer.Header().Set("X-Hey-Header", "123")

return writer
},
expected: map[string][]string{
"Test-Header": {"true"},
"X-Hey-Header": {"123"},
headers.AccessControlAllowOrigin: {"*"},
headers.AccessControlAllowCredentials: {"true"},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
{
name: "should override same headers",
recorderFactory: func() *httptest.ResponseRecorder {
writer := httptest.NewRecorder()
writer.Header().Set("Test-Header", "true")
writer.Header().Set(headers.AccessControlAllowOrigin, "localhost:3000")

return writer
},
expected: map[string][]string{
"Test-Header": {"true"},
headers.AccessControlAllowOrigin: {"*"},
headers.AccessControlAllowCredentials: {"true"},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
resp := testCase.recorderFactory()
infrastructure.WriteCorsHeaders(resp.Header())

response := resp.Result()
defer testutils.CheckNoError(t, response.Body.Close())

assert.Equal(t, response.Header, testCase.expected)
})
}
}
39 changes: 26 additions & 13 deletions internal/mock/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"net/http"

"github.com/evg4b/uncors/internal/contracts"
"github.com/evg4b/uncors/internal/infrastructure"
"github.com/go-http-utils/headers"
)

type Handler struct {
mock Mock
logger contracts.Logger
response Response
logger contracts.Logger
}

func NewMockHandler(options ...HandlerOption) *Handler {
Expand All @@ -23,21 +25,32 @@ func NewMockHandler(options ...HandlerOption) *Handler {
}

func (handler *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
updateRequest(request)
writer.WriteHeader(handler.mock.Response.Code)
fmt.Fprint(writer, handler.mock.Response.RawContent)
response := handler.response
header := writer.Header()
infrastructure.WriteCorsHeaders(header)
for key, value := range response.Headers {
header.Set(key, value)
}
if len(header.Get(headers.ContentType)) == 0 {
contentType := http.DetectContentType([]byte(response.RawContent))
header.Set(headers.ContentType, contentType)
}

writer.WriteHeader(normaliseCode(response.Code))
if _, err := fmt.Fprint(writer, response.RawContent); err != nil {
return // TODO: add error handler
}

handler.logger.PrintResponse(&http.Response{
Request: request,
StatusCode: handler.mock.Response.Code,
StatusCode: response.Code,
})
}

func updateRequest(request *http.Request) {
request.URL.Host = request.Host

if request.TLS != nil {
request.URL.Scheme = "https"
} else {
request.URL.Scheme = "http"
func normaliseCode(code int) int {
if code == 0 {
return http.StatusOK
}

return code
}
205 changes: 205 additions & 0 deletions internal/mock/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package mock_test

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/evg4b/uncors/internal/mock"
"github.com/evg4b/uncors/testing/mocks"
"github.com/evg4b/uncors/testing/testutils"
"github.com/go-http-utils/headers"
"github.com/stretchr/testify/assert"
)

const textPlain = "text/plain; charset=utf-8"

func TestHandler(t *testing.T) {
logger := mocks.NewNoopLogger(t)

t.Run("content type setting", func(t *testing.T) {
tests := []struct {
name string
body string
expected string
}{
{
name: "plain text",
body: `status: ok`,
expected: textPlain,
},
{
name: "json",
body: `{ "status": "ok" }`,
expected: textPlain,
},
{
name: "html",
body: `<html></html>`,
expected: "text/html; charset=utf-8",
},
{
name: "xml",
body: `<?xml />`,
expected: "text/xml; charset=utf-8",
},
{
name: "png",
body: "\x89PNG\x0D\x0A\x1A\x0A",
expected: "image/png",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
handler := mock.NewMockHandler(mock.WithLogger(logger), mock.WithResponse(mock.Response{
Code: 200,
RawContent: testCase.body,
}))

recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "/", nil)
handler.ServeHTTP(recorder, request)

header := testutils.ReadHeader(t, recorder)
assert.EqualValues(t, testCase.expected, header.Get(headers.ContentType))
})
}
})

t.Run("headers settings", func(t *testing.T) {
tests := []struct {
name string
response mock.Response
expected http.Header
}{
{
name: "should put default CORS headers",
response: mock.Response{
Code: 200,
RawContent: "test content",
},
expected: map[string][]string{
headers.AccessControlAllowOrigin: {"*"},
headers.AccessControlAllowCredentials: {"true"},
headers.ContentType: {textPlain},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
{
name: "should set response code",
response: mock.Response{
Code: 200,
RawContent: "test content",
},
expected: map[string][]string{
headers.AccessControlAllowOrigin: {"*"},
headers.AccessControlAllowCredentials: {"true"},
headers.ContentType: {textPlain},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
{
name: "should set custom headers",
response: mock.Response{
Code: 200,
Headers: map[string]string{
"X-Key": "X-Key-Value",
},
RawContent: "test content",
},
expected: map[string][]string{
headers.AccessControlAllowOrigin: {"*"},
headers.AccessControlAllowCredentials: {"true"},
headers.ContentType: {textPlain},
"X-Key": {"X-Key-Value"},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
{
name: "should override default headers",
response: mock.Response{
Code: 200,
Headers: map[string]string{
headers.AccessControlAllowOrigin: "localhost",
headers.AccessControlAllowCredentials: "false",
headers.ContentType: "none",
},
RawContent: "test content",
},
expected: map[string][]string{
headers.AccessControlAllowOrigin: {"localhost"},
headers.AccessControlAllowCredentials: {"false"},
headers.ContentType: {"none"},
headers.AccessControlAllowMethods: {
"GET, PUT, POST, HEAD, TRACE, DELETE, PATCH, COPY, HEAD, LINK, OPTIONS",
},
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
handler := mock.NewMockHandler(
mock.WithResponse(testCase.response),
mock.WithLogger(logger),
)

request := httptest.NewRequest(http.MethodGet, "/", nil)
recorder := httptest.NewRecorder()

handler.ServeHTTP(recorder, request)

assert.EqualValues(t, testCase.expected, testutils.ReadHeader(t, recorder))
assert.Equal(t, 200, recorder.Code)
})
}
})

t.Run("status code", func(t *testing.T) {
tests := []struct {
name string
response mock.Response
expected int
}{
{
name: "provide 201 code",
response: mock.Response{
Code: 201,
},
expected: 201,
},
{
name: "provide 503 code",
response: mock.Response{
Code: 503,
},
expected: 503,
},
{
name: "automatically provide 200 code",
response: mock.Response{},
expected: 200,
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
handler := mock.NewMockHandler(
mock.WithResponse(testCase.response),
mock.WithLogger(logger),
)

request := httptest.NewRequest(http.MethodGet, "/", nil)
recorder := httptest.NewRecorder()

handler.ServeHTTP(recorder, request)

assert.Equal(t, testCase.expected, recorder.Code)
})
}
})
}
4 changes: 2 additions & 2 deletions internal/mock/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import "github.com/evg4b/uncors/internal/contracts"

type HandlerOption = func(*Handler)

func WithMock(mock Mock) HandlerOption {
func WithResponse(response Response) HandlerOption {
return func(handler *Handler) {
handler.mock = mock
handler.response = response
}
}

Expand Down
Loading

0 comments on commit 78369b9

Please sign in to comment.