package api import ( "encoding/json" "net/http/httptest" "strings" "testing" "time" ) func TestDecodePostRules(t *testing.T) { cases := []struct { name string body string wantCode int wantText string }{ {"published news", `{"title":"День открытых дверей","html":"

Текст

","published_at":"2026-09-17","published":true}`, 200, ""}, {"draft without text", `{"title":"Черновик","published":false}`, 200, ""}, {"picture-only post", `{"title":"Афиша","html":"

","published":true}`, 200, ""}, {"no title", `{"title":" ","published":false}`, 422, "заголовок"}, {"published without text", `{"title":"Пусто","html":"

 

","published":true}`, 422, "добавьте текст"}, {"bad date", `{"title":"Дата","published_at":"17.09.2026"}`, 422, "ГГГГ-ММ-ДД"}, {"bad slug", `{"title":"Адрес","slug":"новость"}`, 422, "латинские"}, // адреса со старого сайта: хвостовой дефис, подчёркивание — запись должна сохраняться {"legacy slug", `{"title":"Старая","slug":"v-klinike-dana-","published":false}`, 200, ""}, {"legacy underscore", `{"title":"Старая","slug":"lech_bol","published":false}`, 200, ""}, {"leading dash", `{"title":"Адрес","slug":"-dana"}`, 422, "латинские"}, } for _, tc := range cases { w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/api/v1/admin/news", strings.NewReader(tc.body)) _, valid := decodePost(w, r, "news") code := 200 if !valid { code = w.Code } if code != tc.wantCode || (tc.wantText != "" && !strings.Contains(w.Body.String(), tc.wantText)) { t.Errorf("%s: got %d %s, want %d containing %q", tc.name, code, w.Body.String(), tc.wantCode, tc.wantText) } } } func TestDecodePostStoresHTMLBlockAndDate(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/", strings.NewReader(`{"title":"Статья","html":"

Раздел

","published_at":"2025-05-28"}`)) in, valid := decodePost(w, r, "article") if !valid { t.Fatal(w.Body.String()) } var blocks []map[string]string if err := json.Unmarshal(in.Blocks, &blocks); err != nil || len(blocks) != 1 || blocks[0]["html"] != "

Раздел

" { t.Errorf("blocks = %s", in.Blocks) } if in.Slug != "statya" { t.Errorf("slug = %s", in.Slug) } if got := in.PublishedAt.UTC().Format(time.RFC3339); got != "2025-05-28T09:00:00Z" { t.Errorf("published_at = %s, want midday Moscow", got) } // неизменённая дата возвращается полной меткой времени и сохраняется как есть w = httptest.NewRecorder() r = httptest.NewRequest("POST", "/", strings.NewReader(`{"title":"Статья","published_at":"2024-01-13T14:03:41+03:00"}`)) in, _ = decodePost(w, r, "article") if got := in.PublishedAt.UTC().Format(time.RFC3339); got != "2024-01-13T11:03:41Z" { t.Errorf("kept published_at = %s", got) } } func TestDecodeVacancy(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/", strings.NewReader(`{"title":"Администратор","requirements":"- грамотная речь\r\n\r\n• ответственность ","published":true}`)) in, valid := decodeVacancy(w, r) if !valid { t.Fatal(w.Body.String()) } if in.Requirements != "грамотная речь\nответственность" || in.Slug != "administrator" || in.Sort != 100 { t.Errorf("got %+v", in) } w = httptest.NewRecorder() r = httptest.NewRequest("POST", "/", strings.NewReader(`{"title":"Пустая","published":true}`)) if _, valid := decodeVacancy(w, r); valid || !strings.Contains(w.Body.String(), "опишите") { t.Errorf("empty published vacancy accepted: %d %s", w.Code, w.Body.String()) } }