diff --git a/backend/internal/api/handlers/import_handler_coverage_test.go b/backend/internal/api/handlers/import_handler_coverage_test.go index 42881d79..418d487d 100644 --- a/backend/internal/api/handlers/import_handler_coverage_test.go +++ b/backend/internal/api/handlers/import_handler_coverage_test.go @@ -340,6 +340,11 @@ func TestCommitAndCancel_InvalidSessionUUID(t *testing.T) { r.ServeHTTP(wCommit, reqCommit) assert.Equal(t, http.StatusBadRequest, wCommit.Code) + wCancelMissing := httptest.NewRecorder() + reqCancelMissing, _ := http.NewRequest(http.MethodDelete, "/api/v1/import/cancel", http.NoBody) + r.ServeHTTP(wCancelMissing, reqCancelMissing) + assert.Equal(t, http.StatusBadRequest, wCancelMissing.Code) + wCancel := httptest.NewRecorder() reqCancel, _ := http.NewRequest(http.MethodDelete, "/api/v1/import/cancel?session_uuid=.", http.NoBody) r.ServeHTTP(wCancel, reqCancel) diff --git a/backend/internal/api/handlers/json_import_handler.go b/backend/internal/api/handlers/json_import_handler.go index 9c549680..a2ffae1b 100644 --- a/backend/internal/api/handlers/json_import_handler.go +++ b/backend/internal/api/handlers/json_import_handler.go @@ -310,6 +310,11 @@ func (h *JSONImportHandler) Cancel(c *gin.Context) { return } + if strings.TrimSpace(req.SessionUUID) == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "session_uuid required"}) + return + } + // Clean up session if it exists jsonImportSessionsMu.Lock() delete(jsonImportSessions, req.SessionUUID) diff --git a/backend/internal/api/handlers/json_import_handler_test.go b/backend/internal/api/handlers/json_import_handler_test.go index 1ae7a230..3345dd2a 100644 --- a/backend/internal/api/handlers/json_import_handler_test.go +++ b/backend/internal/api/handlers/json_import_handler_test.go @@ -497,6 +497,62 @@ func TestJSONImportHandler_ConflictDetection(t *testing.T) { assert.Contains(t, conflictDetails, "conflict.com") } +func TestJSONImportHandler_Cancel_RequiresValidJSONBody(t *testing.T) { + db := setupJSONTestDB(t) + handler := NewJSONImportHandler(db) + + gin.SetMode(gin.TestMode) + router := gin.New() + api := router.Group("/api/v1") + handler.RegisterRoutes(api) + + t.Run("missing body", func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/import/json/cancel", http.NoBody) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("invalid json", func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/import/json/cancel", bytes.NewBufferString("{")) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("empty object payload", func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/import/json/cancel", bytes.NewBufferString("{}")) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + + var resp map[string]string + err := json.Unmarshal(w.Body.Bytes(), &resp) + require.NoError(t, err) + assert.Equal(t, "session_uuid required", resp["error"]) + }) + + t.Run("missing session_uuid payload", func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/import/json/cancel", bytes.NewBufferString(`{"foo":"bar"}`)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + + var resp map[string]string + err := json.Unmarshal(w.Body.Bytes(), &resp) + require.NoError(t, err) + assert.Equal(t, "session_uuid required", resp["error"]) + }) +} + func TestJSONImportHandler_IsCharonFormat(t *testing.T) { db := setupJSONTestDB(t) handler := NewJSONImportHandler(db)