From 08a693a0a017261d081a27e3b9821fea415d80f7 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Fri, 11 Apr 2025 20:39:09 +0900 Subject: [PATCH] fix: published workflow(tool) can be deleted. (#17900) Signed-off-by: -LAN- --- api/services/workflow_service.py | 5 ++ api/test_workflow_deletion.py | 117 +++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 api/test_workflow_deletion.py diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 53977d23ef..992942fc70 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -515,5 +515,10 @@ class WorkflowService: # Cannot delete a workflow that's currently in use by an app raise WorkflowInUseError(f"Cannot delete workflow that is currently in use by app '{app.name}'") + # Check if this workflow is published as a tool + if workflow.tool_published: + # Cannot delete a workflow that's published as a tool + raise WorkflowInUseError("Cannot delete workflow that is published as a tool") + session.delete(workflow) return True diff --git a/api/test_workflow_deletion.py b/api/test_workflow_deletion.py new file mode 100644 index 0000000000..56efcccc78 --- /dev/null +++ b/api/test_workflow_deletion.py @@ -0,0 +1,117 @@ +from unittest.mock import MagicMock + +import pytest +from sqlalchemy.orm import Session + +from models.model import App +from models.workflow import Workflow +from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService + + +@pytest.fixture +def workflow_setup(): + workflow_service = WorkflowService() + session = MagicMock(spec=Session) + tenant_id = "test-tenant-id" + workflow_id = "test-workflow-id" + + # Mock workflow + workflow = MagicMock(spec=Workflow) + workflow.id = workflow_id + workflow.tenant_id = tenant_id + workflow.version = "1.0" # Not a draft + workflow.tool_published = False # Not published as a tool by default + + # Mock app + app = MagicMock(spec=App) + app.id = "test-app-id" + app.name = "Test App" + app.workflow_id = None # Not used by an app by default + + return { + "workflow_service": workflow_service, + "session": session, + "tenant_id": tenant_id, + "workflow_id": workflow_id, + "workflow": workflow, + "app": app, + } + + +def test_delete_workflow_success(workflow_setup): + # Setup mocks + workflow_setup["session"].scalar = MagicMock( + side_effect=[workflow_setup["workflow"], None] + ) # Return workflow first, then None for app + + # Call the method + result = workflow_setup["workflow_service"].delete_workflow( + session=workflow_setup["session"], + workflow_id=workflow_setup["workflow_id"], + tenant_id=workflow_setup["tenant_id"], + ) + + # Verify + assert result is True + workflow_setup["session"].delete.assert_called_once_with(workflow_setup["workflow"]) + + +def test_delete_workflow_draft_error(workflow_setup): + # Setup mocks + workflow_setup["workflow"].version = "draft" + workflow_setup["session"].scalar = MagicMock(return_value=workflow_setup["workflow"]) + + # Call the method and verify exception + with pytest.raises(DraftWorkflowDeletionError): + workflow_setup["workflow_service"].delete_workflow( + session=workflow_setup["session"], + workflow_id=workflow_setup["workflow_id"], + tenant_id=workflow_setup["tenant_id"], + ) + + # Verify + workflow_setup["session"].delete.assert_not_called() + + +def test_delete_workflow_in_use_by_app_error(workflow_setup): + # Setup mocks + workflow_setup["app"].workflow_id = workflow_setup["workflow_id"] + workflow_setup["session"].scalar = MagicMock( + side_effect=[workflow_setup["workflow"], workflow_setup["app"]] + ) # Return workflow first, then app + + # Call the method and verify exception + with pytest.raises(WorkflowInUseError) as excinfo: + workflow_setup["workflow_service"].delete_workflow( + session=workflow_setup["session"], + workflow_id=workflow_setup["workflow_id"], + tenant_id=workflow_setup["tenant_id"], + ) + + # Verify error message contains app name + assert "Cannot delete workflow that is currently in use by app" in str(excinfo.value) + + # Verify + workflow_setup["session"].delete.assert_not_called() + + +def test_delete_workflow_published_as_tool_error(workflow_setup): + # Setup mocks + workflow_setup["workflow"].tool_published = True + workflow_setup["session"].scalar = MagicMock( + side_effect=[workflow_setup["workflow"], None] + ) # Return workflow first, then None for app + + # Call the method and verify exception + with pytest.raises(WorkflowInUseError) as excinfo: + workflow_setup["workflow_service"].delete_workflow( + session=workflow_setup["session"], + workflow_id=workflow_setup["workflow_id"], + tenant_id=workflow_setup["tenant_id"], + ) + + # Verify error message + assert "Cannot delete workflow that is published as a tool" in str(excinfo.value) + + # Verify + workflow_setup["session"].delete.assert_not_called()