diff --git a/internal/mock/routes.go b/internal/mock/routes.go index ca006316..8614da3d 100644 --- a/internal/mock/routes.go +++ b/internal/mock/routes.go @@ -3,13 +3,28 @@ package mock import "github.com/gorilla/mux" func MakeMockedRoutes(router *mux.Router, mocks []Mock) { + var defaultMocks []Mock + for _, mock := range mocks { + if len(mock.Queries) > 0 || len(mock.Headers) > 0 || len(mock.Method) > 0 { + route := router.NewRoute() + + setPath(route, mock.Path) + setMethod(route, mock.Method) + setQueries(route, mock.Queries) + setHeaders(route, mock.Headers) + + handler := NewMockHandler(WithMock(mock)) + route.Handler(handler) + } else { + defaultMocks = append(defaultMocks, mock) + } + } + + for _, mock := range defaultMocks { route := router.NewRoute() setPath(route, mock.Path) - setMethod(route, mock.Method) - setQueries(route, mock.Queries) - setHeaders(route, mock.Headers) handler := NewMockHandler(WithMock(mock)) route.Handler(handler) diff --git a/internal/mock/routes_test.go b/internal/mock/routes_test.go index dfa00f5d..1c307690 100644 --- a/internal/mock/routes_test.go +++ b/internal/mock/routes_test.go @@ -1,7 +1,7 @@ +//nolint:maintidx package mock_test import ( - "io" "net/http" "net/http/httptest" "testing" @@ -12,11 +12,17 @@ import ( "github.com/stretchr/testify/assert" ) +var mock1Body = `mock1` +var mock2Body = `mock2` +var mock3Body = `mock3` +var mock4Body = `mock4` + func TestMakeMockedRoutes(t *testing.T) { - t.Run("Request method handling", func(t *testing.T) { + t.Run("request method handling", func(t *testing.T) { + expectedBody := `{"name": "Jon Smite"}` + t.Run("where mock method is not set allow method", func(t *testing.T) { router := mux.NewRouter() - expectedBody := `{"name": "Jon Smite"}` mock.MakeMockedRoutes(router, []mock.Mock{{ Path: "/api", Response: mock.Response{ @@ -43,88 +49,350 @@ func TestMakeMockedRoutes(t *testing.T) { router.ServeHTTP(recorder, request) - response := recorder.Result() - defer testutils.CheckNoError(t, response.Body.Close()) - - body, err := io.ReadAll(response.Body) - testutils.CheckNoError(t, err) - - assert.Equal(t, http.StatusOK, response.StatusCode) - assert.Equal(t, expectedBody, string(body)) + body := testutils.ReadBody(t, recorder) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, expectedBody, body) }) } }) t.Run("where method is set", func(t *testing.T) { + router := mux.NewRouter() + mock.MakeMockedRoutes(router, []mock.Mock{{ + Path: "/api", + Method: http.MethodPut, + Response: mock.Response{ + Code: http.StatusOK, + RawContent: expectedBody, + }, + }}) + + t.Run("method is not matched", func(t *testing.T) { + methods := []string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPatch, + http.MethodDelete, + http.MethodOptions, + http.MethodTrace, + } + for _, method := range methods { + t.Run(method, func(t *testing.T) { + request := httptest.NewRequest(method, "http://localhost/api", nil) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code) + }) + } + }) + + t.Run("method is matched", func(t *testing.T) { + request := httptest.NewRequest(http.MethodPut, "http://localhost/api", nil) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + body := testutils.ReadBody(t, recorder) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, expectedBody, body) + }) }) }) - router := mux.NewRouter() - mock.MakeMockedRoutes(router, []mock.Mock{ - { - Path: "/api/user", - Response: mock.Response{ - Code: http.StatusOK, - RawContent: `{"name": "Jon Smite"}`, - }, - }, - { - Path: "/api/bad_user", - Response: mock.Response{ - Code: http.StatusBadRequest, - RawContent: `{"error": "incorrect data"}`, - }, - }, + + t.Run("path handling", func(t *testing.T) { + router := mux.NewRouter() + + mock.MakeMockedRoutes(router, []mock.Mock{ + { + Path: "/api/user", + Response: mock.Response{ + Code: http.StatusOK, + RawContent: mock1Body, + }, + }, + { + Path: "/api/user/{id:[0-9]+}", + Response: mock.Response{ + Code: http.StatusAccepted, + RawContent: mock2Body, + }, + }, + { + Path: "/api/{single-path/demo", + Response: mock.Response{ + Code: http.StatusBadRequest, + RawContent: mock3Body, + }, + }, + { + Path: `/api/v2/{multiple-path:[a-z-\/]+}/demo`, + Response: mock.Response{ + Code: http.StatusCreated, + RawContent: mock4Body, + }, + }, + }) + + tests := []struct { + name string + url string + expected string + statusCode int + }{ + { + name: "direct path", + url: "https://localhost/api/user", + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "direct path with ending slash", + url: "https://localhost/api/user/", + expected: "404 page not found\n", + statusCode: http.StatusNotFound, + }, + { + name: "direct path with parameter", + url: "https://localhost/api/user/23", + expected: mock2Body, + statusCode: http.StatusAccepted, + }, + { + name: "direct path with incorrect parameter", + url: "https://localhost/api/user/unknow", + expected: "404 page not found\n", + statusCode: http.StatusNotFound, + }, + { + name: "path with subpath to single matching param", + url: "https://localhost/api/some-path/with-some-subpath/demo", + expected: "404 page not found\n", + statusCode: http.StatusNotFound, + }, + { + name: "path with subpath to multiple matching param", + url: "https://localhost/api/v2/some-path/with-some-subpath/demo", + expected: mock4Body, + statusCode: http.StatusCreated, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, testCase.url, nil) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + body := testutils.ReadBody(t, recorder) + assert.Equal(t, testCase.statusCode, recorder.Code) + assert.Equal(t, testCase.expected, body) + }) + } }) - tests := []struct { - name string - method string - url string - headers map[string]string - expected string - statusCode int - }{ - { - name: "http GET with status code 200", - method: http.MethodGet, - url: "http://localhost/api/user", - expected: `{"name": "Jon Smite"}`, - statusCode: http.StatusOK, - }, - { - name: "http GET with status code 400", - method: http.MethodGet, - url: "http://localhost/api/bad_user", - expected: `{"error": "incorrect data"}`, - statusCode: http.StatusBadRequest, - }, - { - name: "http POST with status code 200", - method: http.MethodPost, - url: "http://localhost/api/user", - expected: `{"name": "Jon Smite"}`, - statusCode: http.StatusOK, - }, - } - for _, testCase := range tests { - t.Run(testCase.name, func(t *testing.T) { - request := httptest.NewRequest(testCase.method, testCase.url, nil) - for key, value := range testCase.headers { - request.Header.Add(key, value) - } - recorder := httptest.NewRecorder() + t.Run("query handling", func(t *testing.T) { + router := mux.NewRouter() + mock.MakeMockedRoutes(router, []mock.Mock{ + { + Path: "/api/user", + Response: mock.Response{ + Code: http.StatusOK, + RawContent: mock1Body, + }, + }, + { + Path: "/api/user", + Queries: map[string]string{ + "id": "17", + }, + Response: mock.Response{ + Code: http.StatusCreated, + RawContent: mock2Body, + }, + }, + { + Path: "/api/user", + Queries: map[string]string{ + "id": "99", + "token": "fe145b54563d9be1b2a476f56b0a412b", + }, + Response: mock.Response{ + Code: http.StatusAccepted, + RawContent: mock3Body, + }, + }, + }) - router.ServeHTTP(recorder, request) + tests := []struct { + name string + url string + expected string + statusCode int + }{ + { + name: "queries is not set", + url: "http://localhost/api/user", + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "passed unsetted parameter", + url: "http://localhost/api/user?id=16", + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "passed parameter", + url: "http://localhost/api/user?id=17", + expected: mock2Body, + statusCode: http.StatusCreated, + }, + { + name: "passed one of multiple parameters", + url: "http://localhost/api/user?id=99", + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "passed all of multiple parameters", + url: "http://localhost/api/user?id=99&token=fe145b54563d9be1b2a476f56b0a412b", + expected: mock3Body, + statusCode: http.StatusAccepted, + }, + { + name: "passed extra parameters", + url: "http://localhost/api/user?id=99&token=fe145b54563d9be1b2a476f56b0a412b&demo=true", + expected: mock3Body, + statusCode: http.StatusAccepted, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, testCase.url, nil) + recorder := httptest.NewRecorder() - resp := recorder.Result() - defer testutils.CheckNoError(t, resp.Body.Close()) + router.ServeHTTP(recorder, request) - body, err := io.ReadAll(resp.Body) - testutils.CheckNoError(t, err) + body := testutils.ReadBody(t, recorder) + assert.Equal(t, testCase.statusCode, recorder.Code) + assert.Equal(t, testCase.expected, body) + }) + } + }) - assert.Equal(t, testCase.statusCode, resp.StatusCode) - assert.Equal(t, testCase.expected, string(body)) + t.Run("header handling", func(t *testing.T) { + router := mux.NewRouter() + mock.MakeMockedRoutes(router, []mock.Mock{ + { + Path: "/api/user", + Response: mock.Response{ + Code: http.StatusOK, + RawContent: mock1Body, + }, + }, + { + Path: "/api/user", + Headers: map[string]string{ + "Token": "de4e27987d054577b0edc0e828851724", + }, + Response: mock.Response{ + Code: http.StatusCreated, + RawContent: mock2Body, + }, + }, + { + Path: "/api/user", + Headers: map[string]string{ + "User-Id": "99", + "Token": "fe145b54563d9be1b2a476f56b0a412b", + }, + Response: mock.Response{ + Code: http.StatusAccepted, + RawContent: mock3Body, + }, + }, }) - } + + tests := []struct { + name string + url string + headers map[string]string + expected string + statusCode int + }{ + { + name: "headers is not set", + url: "https://localhost/api/user", + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "passed unsetted headers", + url: "https://localhost/api/user", + headers: map[string]string{ + "Token": "55cc413b96026e833835a2c9a3f39c21", + }, + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "passed defined header", + url: "https://localhost/api/user", + headers: map[string]string{ + "Token": "de4e27987d054577b0edc0e828851724", + }, + expected: mock2Body, + statusCode: http.StatusCreated, + }, + { + name: "passed one of multiple headers", + url: "https://localhost/api/user", + headers: map[string]string{ + "User-Id": "99", + }, + expected: mock1Body, + statusCode: http.StatusOK, + }, + { + name: "passed all of multiple headers", + url: "https://localhost/api/user", + headers: map[string]string{ + "User-Id": "99", + "Token": "fe145b54563d9be1b2a476f56b0a412b", + }, + expected: mock3Body, + statusCode: http.StatusAccepted, + }, + { + name: "passed extra headers", + url: "https://localhost/api/user", + headers: map[string]string{ + "User-Id": "99", + "Token": "fe145b54563d9be1b2a476f56b0a412b", + "Accept-Encoding": "deflate", + }, + expected: mock3Body, + statusCode: http.StatusAccepted, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, testCase.url, nil) + for key, value := range testCase.headers { + request.Header.Add(key, value) + } + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + body := testutils.ReadBody(t, recorder) + assert.Equal(t, testCase.statusCode, recorder.Code) + assert.Equal(t, testCase.expected, body) + }) + } + }) } diff --git a/testing/testutils/http_body..go b/testing/testutils/http_body..go new file mode 100644 index 00000000..4ddc420d --- /dev/null +++ b/testing/testutils/http_body..go @@ -0,0 +1,19 @@ +package testutils + +import ( + "io" + "net/http/httptest" + "testing" +) + +func ReadBody(t *testing.T, recorder *httptest.ResponseRecorder) string { + t.Helper() + + response := recorder.Result() + defer CheckNoError(t, response.Body.Close()) + + body, err := io.ReadAll(response.Body) + CheckNoError(t, err) + + return string(body) +}