diff --git a/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py b/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py index cd97fa2e53b..62c4b456c63 100644 --- a/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py +++ b/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py @@ -1,9 +1,11 @@ +"""Tests for human-input delivery with a persisted draft workflow.""" + +import json import uuid -from types import SimpleNamespace from unittest.mock import MagicMock import pytest -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import Session, sessionmaker from core.workflow.human_input_adapter import ( EmailDeliveryConfig, @@ -14,12 +16,42 @@ from core.workflow.human_input_adapter import ( ) from core.workflow.nodes.human_input.entities import HumanInputNodeData from graphon.enums import BuiltinNodeTypes +from models import Account +from models.enums import AppStatus +from models.model import App, AppMode +from models.workflow import Workflow, WorkflowType from services import workflow_service as workflow_service_module from services.workflow_service import WorkflowService +TENANT_ID = "11111111-1111-1111-1111-111111111111" +APP_ID = "22222222-2222-2222-2222-222222222222" +ACCOUNT_ID = "33333333-3333-3333-3333-333333333333" -def _make_service() -> WorkflowService: - return WorkflowService(session_maker=sessionmaker()) + +def _make_service(sqlite_session: Session) -> WorkflowService: + return WorkflowService( + session_maker=sessionmaker(bind=sqlite_session.get_bind(), expire_on_commit=False), + ) + + +def _app() -> App: + return App( + id=APP_ID, + tenant_id=TENANT_ID, + name="Test App", + description="", + mode=AppMode.WORKFLOW, + status=AppStatus.NORMAL, + enable_site=False, + enable_api=False, + max_active_requests=None, + ) + + +def _account() -> Account: + account = Account(name="Test User", email="test@example.com") + account.id = ACCOUNT_ID + return account def _build_node_config(delivery_methods: list[EmailDeliveryMethod]) -> dict[str, object]: @@ -36,6 +68,37 @@ def _build_node_config(delivery_methods: list[EmailDeliveryMethod]) -> dict[str, } +def _persist_workflow(sqlite_session: Session, delivery_methods: list[EmailDeliveryMethod]) -> None: + node_config = _build_node_config(delivery_methods) + node_data = node_config["data"] + assert isinstance(node_data, HumanInputNodeData) + workflow = Workflow.new( + tenant_id=TENANT_ID, + app_id=APP_ID, + type=WorkflowType.WORKFLOW, + version=Workflow.VERSION_DRAFT, + graph=json.dumps( + { + "nodes": [ + { + "id": node_config["id"], + "data": node_data.model_dump(mode="json"), + } + ], + "edges": [], + } + ), + features="{}", + created_by=ACCOUNT_ID, + environment_variables=[], + conversation_variables=[], + rag_pipeline_variables=[], + ) + sqlite_session.add(workflow) + sqlite_session.commit() + sqlite_session.expunge_all() + + def _make_email_method(enabled: bool = True, debug_mode: bool = False) -> EmailDeliveryMethod: return EmailDeliveryMethod( id=uuid.uuid4(), @@ -52,11 +115,11 @@ def _make_email_method(enabled: bool = True, debug_mode: bool = False) -> EmailD ) -def test_human_input_delivery_requires_draft_workflow(): - service = _make_service() - service.get_draft_workflow = MagicMock(return_value=None) # type: ignore[method-assign] - app_model = SimpleNamespace(tenant_id="tenant-1", id="app-1") - account = SimpleNamespace(id="account-1") +@pytest.mark.parametrize("sqlite_session", [(Workflow,)], indirect=True) +def test_human_input_delivery_requires_draft_workflow(sqlite_session: Session): + service = _make_service(sqlite_session) + app_model = _app() + account = _account() with pytest.raises(ValueError, match="Workflow not initialized"): service.test_human_input_delivery( @@ -64,17 +127,19 @@ def test_human_input_delivery_requires_draft_workflow(): account=account, node_id="node-1", delivery_method_id="delivery-1", - session=MagicMock(), + session=sqlite_session, ) + assert sqlite_session.in_transaction() -def test_human_input_delivery_allows_disabled_method(monkeypatch: pytest.MonkeyPatch): - service = _make_service() +@pytest.mark.parametrize("sqlite_session", [(Workflow,)], indirect=True) +def test_human_input_delivery_allows_disabled_method( + monkeypatch: pytest.MonkeyPatch, + sqlite_session: Session, +): + service = _make_service(sqlite_session) delivery_method = _make_email_method(enabled=False) - node_config = _build_node_config([delivery_method]) - workflow = MagicMock() - workflow.get_node_config_by_id.return_value = node_config - service.get_draft_workflow = MagicMock(return_value=workflow) # type: ignore[method-assign] + _persist_workflow(sqlite_session, [delivery_method]) service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined] node_stub = MagicMock() node_stub.render_form_content_before_submission.return_value = "rendered" @@ -91,27 +156,29 @@ def test_human_input_delivery_allows_disabled_method(monkeypatch: pytest.MonkeyP MagicMock(return_value=test_service_instance), ) - app_model = SimpleNamespace(tenant_id="tenant-1", id="app-1") - account = SimpleNamespace(id="account-1") + app_model = _app() + account = _account() service.test_human_input_delivery( app_model=app_model, account=account, node_id="node-1", delivery_method_id=str(delivery_method.id), - session=MagicMock(), + session=sqlite_session, ) test_service_instance.send_test.assert_called_once() + assert sqlite_session.in_transaction() -def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.MonkeyPatch): - service = _make_service() +@pytest.mark.parametrize("sqlite_session", [(Workflow,)], indirect=True) +def test_human_input_delivery_dispatches_to_test_service( + monkeypatch: pytest.MonkeyPatch, + sqlite_session: Session, +): + service = _make_service(sqlite_session) delivery_method = _make_email_method(enabled=True) - node_config = _build_node_config([delivery_method]) - workflow = MagicMock() - workflow.get_node_config_by_id.return_value = node_config - service.get_draft_workflow = MagicMock(return_value=workflow) # type: ignore[method-assign] + _persist_workflow(sqlite_session, [delivery_method]) service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined] node_stub = MagicMock() node_stub.render_form_content_before_submission.return_value = "rendered" @@ -128,8 +195,8 @@ def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.Mon MagicMock(return_value=test_service_instance), ) - app_model = SimpleNamespace(tenant_id="tenant-1", id="app-1") - account = SimpleNamespace(id="account-1") + app_model = _app() + account = _account() service.test_human_input_delivery( app_model=app_model, @@ -137,21 +204,23 @@ def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.Mon node_id="node-1", delivery_method_id=str(delivery_method.id), inputs={"#node-1.output#": "value"}, - session=MagicMock(), + session=sqlite_session, ) pool_args = service._build_human_input_variable_pool.call_args.kwargs assert pool_args["manual_inputs"] == {"#node-1.output#": "value"} test_service_instance.send_test.assert_called_once() + assert sqlite_session.in_transaction() -def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytest.MonkeyPatch): - service = _make_service() +@pytest.mark.parametrize("sqlite_session", [(Workflow,)], indirect=True) +def test_human_input_delivery_debug_mode_overrides_recipients( + monkeypatch: pytest.MonkeyPatch, + sqlite_session: Session, +): + service = _make_service(sqlite_session) delivery_method = _make_email_method(enabled=True, debug_mode=True) - node_config = _build_node_config([delivery_method]) - workflow = MagicMock() - workflow.get_node_config_by_id.return_value = node_config - service.get_draft_workflow = MagicMock(return_value=workflow) # type: ignore[method-assign] + _persist_workflow(sqlite_session, [delivery_method]) service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined] node_stub = MagicMock() node_stub.render_form_content_before_submission.return_value = "rendered" @@ -168,15 +237,15 @@ def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytes MagicMock(return_value=test_service_instance), ) - app_model = SimpleNamespace(tenant_id="tenant-1", id="app-1") - account = SimpleNamespace(id="account-1") + app_model = _app() + account = _account() service.test_human_input_delivery( app_model=app_model, account=account, node_id="node-1", delivery_method_id=str(delivery_method.id), - session=MagicMock(), + session=sqlite_session, ) test_service_instance.send_test.assert_called_once() @@ -188,3 +257,4 @@ def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytes recipient = sent_method.config.recipients.items[0] assert isinstance(recipient, MemberRecipient) assert recipient.reference_id == account.id + assert sqlite_session.in_transaction()