fix: return field-level validation errors on POST /habits
Previously all validation failures in CreateHabitHandler returned a
generic {"error":"invalid input"}, making it impossible to tell
whether type, frequency, specific_days or specific_dates was the
problem. Errors are now wrapped with field + i18n key and the HTTP
layer replies via respondValidationErrorI18n, matching the pattern
already used by auth endpoints.
This commit is contained in:
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -97,8 +98,8 @@ func (h *HabitHandlers) CreateHabit(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
habitID, err := h.createHandler.Handle(r.Context(), cmd)
|
||||
if err != nil {
|
||||
if err == errors.ErrInvalidInput {
|
||||
respondError(w, http.StatusBadRequest, err.Error())
|
||||
if stderrors.Is(err, errors.ErrInvalidInput) {
|
||||
respondValidationErrorI18n(w, r, h.translator, err)
|
||||
return
|
||||
}
|
||||
respondErrorI18n(w, r, h.translator, http.StatusInternalServerError, "failed_create_habit")
|
||||
|
||||
@@ -123,6 +123,47 @@ func TestHabitCRUDFlow(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Create habit with missing type returns field-level error", func(t *testing.T) {
|
||||
token := registerAndLogin(t, *ts.Router, "validationuser@example.com", "Password123!")
|
||||
|
||||
reqBody := map[string]any{"name": "No Type"}
|
||||
rr := makeRequest(t, *ts.Router, "POST", "/api/v1/habits", reqBody, token)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("Expected 400, got %d. Body: %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
|
||||
var resp ValidationErrorResponse
|
||||
decodeResponse(t, rr, &resp)
|
||||
if resp.Field != "type" {
|
||||
t.Errorf("Expected field 'type', got %q. Body: %s", resp.Field, rr.Body.String())
|
||||
}
|
||||
if resp.Error == "" {
|
||||
t.Errorf("Expected non-empty translated error message. Body: %s", rr.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Create weekly habit without specific_days returns field-level error", func(t *testing.T) {
|
||||
token := registerAndLogin(t, *ts.Router, "weeklyuser@example.com", "Password123!")
|
||||
|
||||
reqBody := CreateHabitRequest{
|
||||
Name: "Cut nails",
|
||||
Type: "BOOLEAN",
|
||||
Frequency: "WEEKLY",
|
||||
}
|
||||
rr := makeRequest(t, *ts.Router, "POST", "/api/v1/habits", reqBody, token)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("Expected 400, got %d. Body: %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
|
||||
var resp ValidationErrorResponse
|
||||
decodeResponse(t, rr, &resp)
|
||||
if resp.Field != "specific_days" {
|
||||
t.Errorf("Expected field 'specific_days', got %q. Body: %s", resp.Field, rr.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Access other user's habit", func(t *testing.T) {
|
||||
otherToken := registerAndLogin(t, *ts.Router, "otheruser@example.com", "Password123!")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user