diff --git a/internal/api/api_test.go b/internal/api/api_test.go index c7bf992..0a48ee0 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -76,6 +76,41 @@ func TestApplyDefaultsPruneFalse(t *testing.T) { } } +func TestApplyEmptyBodyOK(t *testing.T) { + a, m := newTestAPI() + router := NewRouter(a) + + did := uuid.New().String() + req := httptest.NewRequest(http.MethodPost, + "/api/v1/projects/00000000-0000-0000-0000-000000000002/domains/"+did+"/apply", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status %d body %s", w.Code, w.Body.String()) + } + if m.lastReq.ApplyPrunes != false { + t.Fatalf("expected ApplyPrunes=false for empty body, got %+v", m.lastReq) + } +} + +func TestApplyMalformedBody(t *testing.T) { + a, _ := newTestAPI() + router := NewRouter(a) + + did := uuid.New().String() + body := `{"applyUpdates":` + req := httptest.NewRequest(http.MethodPost, + "/api/v1/projects/00000000-0000-0000-0000-000000000002/domains/"+did+"/apply", + strings.NewReader(body)) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Fatalf("expected 400 for malformed body, got %d body %s", w.Code, w.Body.String()) + } +} + func TestApplyBadUUID(t *testing.T) { a, _ := newTestAPI() router := NewRouter(a) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 4890618..2815dec 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -2,6 +2,9 @@ package api import ( "encoding/json" + "errors" + "io" + "log" "net/http" "github.com/go-chi/chi/v5" @@ -28,7 +31,8 @@ func (a *API) handleCheck(w http.ResponseWriter, r *http.Request) { } cs, err := a.Svc.Check(r.Context(), did) if err != nil { - writeErr(w, http.StatusInternalServerError, err.Error()) + log.Printf("api: check failed: %v", err) + writeErr(w, http.StatusInternalServerError, "internal error") return } writeJSON(w, http.StatusOK, toChangesetResponse(cs)) @@ -42,14 +46,20 @@ func (a *API) handleApply(w http.ResponseWriter, r *http.Request) { } var req applyRequest if r.Body != nil { - // пустое тело допустимо → значения по умолчанию (prune=false) - _ = json.NewDecoder(r.Body).Decode(&req) + r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB + // пустое тело допустимо → значения по умолчанию (prune=false); + // любая другая ошибка decode (битый JSON, неверные типы) → 400 + if err := json.NewDecoder(r.Body).Decode(&req); err != nil && !errors.Is(err, io.EOF) { + writeErr(w, http.StatusBadRequest, "invalid request body") + return + } } cs, err := a.Svc.Apply(r.Context(), did, service.ApplyRequest{ ApplyUpdates: req.ApplyUpdates, ApplyPrunes: req.ApplyPrunes, }) if err != nil { - writeErr(w, http.StatusInternalServerError, err.Error()) + log.Printf("api: apply failed: %v", err) + writeErr(w, http.StatusInternalServerError, "internal error") return } writeJSON(w, http.StatusOK, toChangesetResponse(cs))