axe/internal/handlers/list_test.go
2026-10-18 22:07:25 +03:00

168 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"building-mgmt/internal/models"
"building-mgmt/internal/repository"
)
// Наборы — чистые функции разбора query, поэтому здесь живут и тестируются
// без БД и без роутера: контексту достаточно Request с query-строкой.
func newQueryContext(t *testing.T, rawQuery string) (*gin.Context, *httptest.ResponseRecorder) {
t.Helper()
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/list?"+rawQuery, nil)
return c, w
}
// errorBody — {"error": ...} из ответа для сообщений об ошибках.
func errorBody(t *testing.T, w *httptest.ResponseRecorder) string {
t.Helper()
var body struct {
Error string `json:"error"`
}
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("разбор ответа %q: %v", w.Body.String(), err)
}
return body.Error
}
func TestParsePageDefaults(t *testing.T) {
c, w := newQueryContext(t, "")
page, ok := parsePage(c, repository.TicketSorts)
if !ok {
t.Fatalf("ok = false, не должно быть ошибки; ответ: %s", w.Body.String())
}
if page.Limit != repository.DefaultLimit || page.Offset != 0 || page.Sort != "" || page.Order != "" {
t.Errorf("page = %+v, want дефолт limit=%d", page, repository.DefaultLimit)
}
}
func TestParsePageValid(t *testing.T) {
c, _ := newQueryContext(t, "limit=100&offset=50&sort=priority&order=asc")
page, ok := parsePage(c, repository.TicketSorts)
if !ok {
t.Fatal("ok = false, валидные параметры не должны падать")
}
if page.Limit != 100 || page.Offset != 50 || page.Sort != "priority" || page.Order != "asc" {
t.Errorf("page = %+v", page)
}
}
func TestParsePageRejects(t *testing.T) {
cases := []struct {
name string
query string
want string
}{
{"лимит нулевой", "limit=0", "invalid limit"},
{"лимит за потолком", "limit=101", "invalid limit"},
{"лимит не число", "limit=abc", "invalid limit"},
{"лимит отрицательный", "limit=-5", "invalid limit"},
{"сдвиг отрицательный", "offset=-1", "invalid offset"},
{"сдвиг не число", "offset=1.5", "invalid offset"},
{"порядок не из списка", "order=up", "invalid order"},
{"сортировка не из списка", "sort=password_hash", "invalid sort"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
c, w := newQueryContext(t, tc.query)
if _, ok := parsePage(c, repository.TicketSorts); ok {
t.Fatal("ok = true, ожидался отказ")
}
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want 400", w.Code)
}
if got := errorBody(t, w); got != tc.want {
t.Errorf("error = %q, want %q", got, tc.want)
}
})
}
}
func TestCheckFilter(t *testing.T) {
t.Run("подходит", func(t *testing.T) {
c, _ := newQueryContext(t, "status=closed")
if !checkFilter(c, "status", models.TicketStatuses) {
t.Error("ok = false, допустимое значение отклонено")
}
})
t.Run("параметр не задан", func(t *testing.T) {
c, _ := newQueryContext(t, "")
if !checkFilter(c, "status", models.TicketStatuses) {
t.Error("ok = false, пустой фильтр должен проходить")
}
})
t.Run("мусор", func(t *testing.T) {
c, w := newQueryContext(t, "status=nope")
if checkFilter(c, "status", models.TicketStatuses) {
t.Fatal("ok = true, мусорное значение должно отклоняться")
}
if w.Code != http.StatusBadRequest || errorBody(t, w) != "invalid status" {
t.Errorf("status=%d error=%q, want 400/invalid status", w.Code, errorBody(t, w))
}
})
}
func TestDateRange(t *testing.T) {
t.Run("оба пусты", func(t *testing.T) {
c, _ := newQueryContext(t, "")
from, to, ok := dateRange(c)
if !ok || from != nil || to != nil {
t.Errorf("from=%v to=%v ok=%v, want nil/nil/true", from, to, ok)
}
})
t.Run("date_to включает весь свой день", func(t *testing.T) {
c, _ := newQueryContext(t, "date_from=2026-10-01&date_to=2026-10-01")
from, to, ok := dateRange(c)
if !ok {
t.Fatal("ok = false")
}
wantFrom := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)
if !from.Equal(wantFrom) {
t.Errorf("from = %v, want %v", from, wantFrom)
}
// Верхняя граница исключительная — сутки после date_to.
if want := wantFrom.AddDate(0, 0, 1); !to.Equal(want) {
t.Errorf("to = %v, want %v (весь день целиком)", to, want)
}
})
t.Run("формат не тот", func(t *testing.T) {
c, w := newQueryContext(t, "date_from=01.10.2026")
if _, _, ok := dateRange(c); ok {
t.Fatal("ok = false ожидался")
}
if w.Code != http.StatusBadRequest || errorBody(t, w) != "invalid date_from" {
t.Errorf("status=%d error=%q", w.Code, errorBody(t, w))
}
})
t.Run("диапазон наоборот", func(t *testing.T) {
c, w := newQueryContext(t, "date_from=2026-10-05&date_to=2026-10-01")
if _, _, ok := dateRange(c); ok {
t.Fatal("ok = false ожидался")
}
if w.Code != http.StatusBadRequest || errorBody(t, w) != "invalid date range" {
t.Errorf("status=%d error=%q", w.Code, errorBody(t, w))
}
})
}