tests for GetRecipeHandler

This commit is contained in:
yyasha 2024-01-29 13:46:58 +03:00
parent 2685baa3e7
commit 606cc3763a
2 changed files with 181 additions and 0 deletions

View File

@ -0,0 +1,141 @@
package recipe_test
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"recipes/internal/domain/models"
"recipes/internal/http-server/handlers/recipe"
"recipes/internal/http-server/handlers/recipe/mocks"
"recipes/internal/lib/logger/handlers/slogdiscard"
"recipes/internal/storage"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetRecipeHandler(t *testing.T) {
// cases
cases := []struct {
name string // name of test
inputJsonPattern string // input json
recipe_id uint // recipe id to get
recipe models.Recipe // recipe data
wantRespStatus string // expected status of response
wantBody string // expected response body
respError string // expected response error
storageMockError error // mock error to return
storageTimes int // count of storage calls
}{
{
name: "Success",
recipe_id: 1,
recipe: models.Recipe{
ID: 1,
Title: "title",
Description: "description",
Image: "img.jpg",
CookingTime: "1 час",
ServingsNum: 1,
Calories: "1 kCal",
Ingredients: []models.RecipeIngredients{
{
Title: "Ингредиенты",
Ingredients: []string{"Ингредиент 1", "Ингредиент 2"},
},
},
Recipe_steps: []string{
"Шаг 1",
"Шаг 2",
},
Advices: []string{
"1. совет",
"2. совет",
},
Categories: []string{
"Категория 1",
"Категория 2",
},
},
wantBody: "{\"status\":\"OK\",\"recipe\":{\"id\":1,\"title\":\"title\",\"desc\":\"description\",\"img\":\"img.jpg\",\"ctime\":\"1 час\",\"snum\":1,\"cal\":\"1 kCal\",\"ingredients\":[{\"title\":\"Ингредиенты\",\"ingredients\":[\"Ингредиент 1\",\"Ингредиент 2\"]}],\"recipe_steps\":[\"Шаг 1\",\"Шаг 2\"],\"advices\":[\"1. совет\",\"2. совет\"],\"categories\":[\"Категория 1\",\"Категория 2\"]}}\n",
storageTimes: 1,
wantRespStatus: "OK",
inputJsonPattern: `{"recipe_id": %d}`,
},
{
name: "Broken JSON",
respError: "failed to decode request",
inputJsonPattern: `{"recipe_id: %d}`,
wantRespStatus: "Error",
wantBody: "{\"status\":\"Error\",\"error\":\"failed to decode request\"}\n",
storageTimes: 0,
},
{
name: "Recipe not exists",
respError: "recipe not found",
inputJsonPattern: `{"recipe_id": %d}`,
recipe_id: 1,
wantRespStatus: "Error",
storageMockError: storage.ErrRecipeNotFound,
storageTimes: 1,
wantBody: "{\"status\":\"Error\",\"error\":\"recipe not found\"}\n",
},
{
name: "Storage error",
respError: "failed to get recipe",
inputJsonPattern: `{"recipe_id": %d}`,
recipe_id: 1,
wantRespStatus: "Error",
storageMockError: errors.New("SOME ERROR"),
storageTimes: 1,
wantBody: "{\"status\":\"Error\",\"error\":\"failed to get recipe\"}\n",
},
{
name: "Miss recipe_id",
inputJsonPattern: "{}",
respError: "field RecipeId is a required field",
wantRespStatus: "Error",
wantBody: "{\"status\":\"Error\",\"error\":\"field RecipeId is a required field\"}\n",
storageTimes: 0,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// create storage mock
recipeProviderMock := mocks.NewRecipeProvider(t)
if tc.storageTimes > 0 {
recipeProviderMock.On("GetRecipe", context.Background(), tc.recipe_id).Return(tc.recipe, tc.storageMockError).Times(tc.storageTimes)
}
// create handler
handler := recipe.New(slogdiscard.NewDiscardLogger(), recipeProviderMock)
//
input := fmt.Sprintf(tc.inputJsonPattern, 1)
// http request
req, err := http.NewRequest(http.MethodGet, "/recipe", bytes.NewReader([]byte(input)))
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 recipe.Response
require.NoError(t, json.Unmarshal([]byte(body), &resp))
assert.Equal(t, tc.respError, resp.Error)
assert.Equal(t, tc.wantRespStatus, resp.Status)
assert.Equal(t, tc.recipe, resp.Recipe)
})
}
}

View File

@ -0,0 +1,40 @@
package slogdiscard
/*
Logger for tests with ignoring input
*/
import (
"context"
"log/slog"
)
func NewDiscardLogger() *slog.Logger {
return slog.New(NewDiscardHandler())
}
type DiscardHandler struct{}
func NewDiscardHandler() *DiscardHandler {
return &DiscardHandler{}
}
func (h *DiscardHandler) Handle(_ context.Context, _ slog.Record) error {
// Просто игнорируем запись журнала
return nil
}
func (h *DiscardHandler) WithAttrs(_ []slog.Attr) slog.Handler {
// Возвращает тот же обработчик, так как нет атрибутов для сохранения
return h
}
func (h *DiscardHandler) WithGroup(_ string) slog.Handler {
// Возвращает тот же обработчик, так как нет группы для сохранения
return h
}
func (h *DiscardHandler) Enabled(_ context.Context, _ slog.Level) bool {
// Всегда возвращает false, так как запись журнала игнорируется
return false
}