diff --git a/backend/internal/api/handlers/npm_import_handler.go b/backend/internal/api/handlers/npm_import_handler.go index 8f124eca..9de3b6f7 100644 --- a/backend/internal/api/handlers/npm_import_handler.go +++ b/backend/internal/api/handlers/npm_import_handler.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "sync" "github.com/gin-gonic/gin" @@ -293,6 +294,11 @@ func (h *NPMImportHandler) 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 npmImportSessionsMu.Lock() delete(npmImportSessions, req.SessionUUID) diff --git a/backend/internal/api/handlers/npm_import_handler_test.go b/backend/internal/api/handlers/npm_import_handler_test.go index 74d7be78..e9fcc9aa 100644 --- a/backend/internal/api/handlers/npm_import_handler_test.go +++ b/backend/internal/api/handlers/npm_import_handler_test.go @@ -453,6 +453,62 @@ func TestNPMImportHandler_Cancel(t *testing.T) { assert.Equal(t, http.StatusNotFound, commitW.Code) } +func TestNPMImportHandler_Cancel_RequiresValidJSONBody(t *testing.T) { + db := setupNPMTestDB(t) + handler := NewNPMImportHandler(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/npm/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/npm/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/npm/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/npm/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 TestNPMImportHandler_ConvertNPMToImportResult(t *testing.T) { db := setupNPMTestDB(t) handler := NewNPMImportHandler(db)