package recipes_test import ( "bytes" "context" "encoding/json" "errors" "net/http" "net/http/httptest" "recipes/internal/domain/models" "recipes/internal/http-server/handlers/recipes" "recipes/internal/http-server/handlers/recipes/mocks" "recipes/internal/lib/logger/handlers/slogdiscard" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetRecipesHandler(t *testing.T) { // cases cases := []struct { name string // name of test page uint // number of page to get inputBody string // input json wantBody string // expected output json wantRespError string // expected error wantRespStatus string // expected status storageMockError error // mock error to return storageTimes int // count of storage calls recipes []models.Recipe // returned data }{ { name: "Success", page: 1, inputBody: `{"page": 1}`, wantBody: "{\"status\":\"OK\",\"recipes\":[{\"id\":1,\"title\":\"title 1\",\"img\":\"1.jpg\",\"ctime\":\"1 час\",\"cal\":\"100 kCal\"},{\"id\":2,\"title\":\"title 2\",\"img\":\"2.jpg\",\"ctime\":\"2 часа\",\"cal\":\"200 kCal\"}]}\n", wantRespStatus: "OK", storageTimes: 1, recipes: []models.Recipe{ { ID: 1, Title: "title 1", Image: "1.jpg", CookingTime: "1 час", Calories: "100 kCal", }, { ID: 2, Title: "title 2", Image: "2.jpg", CookingTime: "2 часа", Calories: "200 kCal", }, }, }, { name: "Empty page", page: 10, inputBody: `{"page": 10}`, wantBody: "{\"status\":\"OK\",\"recipes\":null}\n", wantRespStatus: "OK", storageTimes: 1, }, { name: "Bad data", inputBody: `{"page": 0}`, wantBody: "{\"status\":\"Error\",\"error\":\"field Page is a required field\"}\n", wantRespStatus: "Error", wantRespError: "field Page is a required field", storageTimes: 0, }, { name: "Broken JSON", inputBody: `{"page" 1}`, wantBody: "{\"status\":\"Error\",\"error\":\"failed to decode request\"}\n", wantRespError: "failed to decode request", wantRespStatus: "Error", storageTimes: 0, }, { name: "Storage error", inputBody: `{"page": 1}`, page: 1, wantBody: "{\"status\":\"Error\",\"error\":\"failed to get recipes\"}\n", wantRespError: "failed to get recipes", wantRespStatus: "Error", storageMockError: errors.New("SOME ERROR"), storageTimes: 1, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() // create storage mock recipeProviderMock := mocks.NewRecipesProvider(t) if tc.storageTimes > 0 { recipeProviderMock.On("GetRecipes", context.Background(), recipes.GetRecipesLimit*(int(tc.page)-1), recipes.GetRecipesLimit).Return(tc.recipes, tc.storageMockError).Times(tc.storageTimes) } // create handler handler := recipes.New(slogdiscard.NewDiscardLogger(), recipeProviderMock) // http request req, err := http.NewRequest(http.MethodGet, "/recipes_page", bytes.NewReader([]byte(tc.inputBody))) require.NoError(t, err) // create request rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) // compare expected and actual assert.Equal(t, http.StatusOK, rr.Code) body := rr.Body.String() assert.Equal(t, tc.wantBody, body) var resp recipes.Response require.NoError(t, json.Unmarshal([]byte(body), &resp)) assert.Equal(t, tc.wantRespError, resp.Error) assert.Equal(t, tc.wantRespStatus, resp.Status) assert.Equal(t, tc.recipes, resp.Recipes) }) } }