package api import ( "net/http/httptest" "strings" "testing" ) func TestSlugFromTitle(t *testing.T) { cases := map[string]string{ "Я в сад": "ya-v-sad", "Здоровый ЖКТ": "zdorovyy-zhkt", "Мне год (1–2 года)": "mne-god-1-2-goda", " Чек-ап «Я подросток»! ": "chek-ap-ya-podrostok", "Подъезд, щука": "podezd-shchuka", "EMSELLA 2026": "emsella-2026", "!!!": "", } for title, want := range cases { if got := slugFromTitle(title); got != want { t.Errorf("slugFromTitle(%q) = %q, want %q", title, got, want) } } } func decodePromoBody(body string) (int, string) { w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/api/v1/admin/promos", strings.NewReader(body)) if _, valid := decodePromo(w, r); valid { return 200, "" } return w.Code, w.Body.String() } func TestDecodePromoRules(t *testing.T) { cases := []struct { name string body string wantCode int wantText string }{ {"valid published promo", `{"title":"Я в сад","discount_percent":20,"terms":"Скидка на программу чек-апа","ends_at":"2026-12-31","published":true}`, 200, ""}, {"draft without terms is allowed", `{"title":"Черновик","published":false}`, 200, ""}, {"no title", `{"title":" ","published":false}`, 422, "название"}, {"published without terms", `{"title":"Я в сад","published":true}`, 422, "условия"}, {"discount without end date", `{"title":"Я в сад","discount_percent":20,"terms":"x","published":true}`, 422, "последний день"}, {"discount out of range", `{"title":"Я в сад","discount_percent":150,"published":false}`, 422, "от 1 до 99"}, {"end before start", `{"title":"Я в сад","starts_at":"2026-10-01","ends_at":"2026-09-01","published":false}`, 422, "раньше начала"}, {"bad date", `{"title":"Я в сад","ends_at":"31.12.2026","published":false}`, 422, "ГГГГ-ММ-ДД"}, {"bad slug", `{"title":"Я в сад","slug":"Я в сад","published":false}`, 422, "латинские"}, } for _, tc := range cases { code, body := decodePromoBody(tc.body) if code != tc.wantCode || (tc.wantText != "" && !strings.Contains(body, tc.wantText)) { t.Errorf("%s: got %d %s, want %d containing %q", tc.name, code, body, tc.wantCode, tc.wantText) } } } func decodeCheckupBody(body string) (int, string, int) { w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/api/v1/admin/checkups", strings.NewReader(body)) in, valid := decodeCheckup(w, r) if valid { return 200, "", len(in.Items) } return w.Code, w.Body.String(), 0 } func TestDecodeCheckupRules(t *testing.T) { code, body, items := decodeCheckupBody(`{"name":"Здоровая девочка","audience":"kids","price":5000,"old_price":6000,"items":["УЗИ"," ","Ферритин"]}`) if code != 200 || items != 2 { t.Fatalf("valid checkup rejected or empty items kept: %d %s items=%d", code, body, items) } cases := []struct { name string body string wantText string }{ {"unknown audience", `{"name":"X","audience":"pets"}`, "для кого"}, {"old price not above price", `{"name":"X","audience":"adults","price":5000,"old_price":5000}`, "больше цены"}, {"old price without price", `{"name":"X","audience":"adults","old_price":5000}`, "больше цены"}, {"negative price", `{"name":"X","audience":"adults","price":-1}`, "отрицательной"}, } for _, tc := range cases { code, body, _ := decodeCheckupBody(tc.body) if code != 422 || !strings.Contains(body, tc.wantText) { t.Errorf("%s: got %d %s, want 422 containing %q", tc.name, code, body, tc.wantText) } } }