From 9e8733413afaebc59882ee2f319ab3c7278e60bc Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Thu, 30 Jul 2026 12:07:43 +0900 Subject: [PATCH] test: use SQLite sessions in controllers console app (#39095) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../controllers/console/app/test_workflow.py | 35 ++--- .../app/test_workflow_pause_details_api.py | 138 ++++++++++++++---- 2 files changed, 120 insertions(+), 53 deletions(-) diff --git a/api/tests/unit_tests/controllers/console/app/test_workflow.py b/api/tests/unit_tests/controllers/console/app/test_workflow.py index fd07c42f752..c90f62f1cf9 100644 --- a/api/tests/unit_tests/controllers/console/app/test_workflow.py +++ b/api/tests/unit_tests/controllers/console/app/test_workflow.py @@ -10,6 +10,8 @@ from unittest.mock import Mock import pytest from flask import Flask from pydantic import ValidationError +from sqlalchemy import Engine +from sqlalchemy.orm import Session from werkzeug.exceptions import HTTPException, NotFound from controllers.common.errors import InvalidArgumentError @@ -331,45 +333,34 @@ def test_restore_published_workflow_to_draft_returns_400_for_invalid_structure( def test_get_published_workflows_serializes_items_before_session_closes( - app: Flask, monkeypatch: pytest.MonkeyPatch + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine ) -> None: api = workflow_module.PublishedAllWorkflowApi() handler = inspect.unwrap(api.get) - session_state = {"open": False} - - class _SessionContext: - def __enter__(self): - session_state["open"] = True - return object() - - def __exit__(self, exc_type, exc, tb): - session_state["open"] = False - return False - - class _SessionMaker: - def begin(self): - return _SessionContext() - base_workflow = _make_workflow() class _Workflow: + def __init__(self, session: Session) -> None: + self._session = session + def __getattr__(self, name): return getattr(base_workflow, name) @property def id(self): - assert session_state["open"] is True + assert self._session.in_transaction() return "w1" - monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object())) - monkeypatch.setattr(workflow_module, "sessionmaker", lambda *_args, **_kwargs: _SessionMaker()) + def get_all_published_workflow(*, session: Session, **_kwargs): + assert isinstance(session, Session) + return [_Workflow(session)], False + + monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=sqlite_engine)) monkeypatch.setattr( workflow_module, "WorkflowService", - lambda: SimpleNamespace( - get_all_published_workflow=lambda **_kwargs: ([_Workflow()], False), - ), + lambda: SimpleNamespace(get_all_published_workflow=get_all_published_workflow), ) with app.test_request_context( diff --git a/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py b/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py index 502fa99fd75..88aed318fb2 100644 --- a/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py @@ -1,19 +1,68 @@ +"""Console workflow pause-detail tests backed by persisted workflow execution state.""" + from __future__ import annotations import inspect +from dataclasses import dataclass from datetime import datetime -from types import SimpleNamespace from unittest.mock import Mock +from uuid import uuid4 import pytest from flask import Flask +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session from controllers.common.errors import NotFoundError from controllers.console.app import workflow_run as workflow_run_module from core.workflow.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig from core.workflow.nodes.human_input.pause_reason import HumanInputRequired from graphon.enums import WorkflowExecutionStatus -from models.workflow import WorkflowRun +from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom +from models.workflow import WorkflowPause, WorkflowRun, WorkflowType + + +@dataclass(frozen=True) +class _Database: + engine: Engine + session: Session + + +def _persist_run( + session: Session, + *, + run_id: str, + tenant_id: str, + status: WorkflowExecutionStatus, + paused: bool = False, +) -> WorkflowRun: + workflow_id = str(uuid4()) + workflow_run = WorkflowRun( + id=run_id, + tenant_id=tenant_id, + app_id=str(uuid4()), + workflow_id=workflow_id, + type=WorkflowType.WORKFLOW, + triggered_from=WorkflowRunTriggeredFrom.DEBUGGING, + version="draft", + graph="{}", + inputs="{}", + status=status, + created_by_role=CreatorUserRole.ACCOUNT, + created_by=str(uuid4()), + created_at=datetime(2024, 1, 1, 12, 0, 0), + ) + session.add(workflow_run) + if paused: + session.add( + WorkflowPause( + workflow_id=workflow_id, + workflow_run_id=run_id, + state_object_key="workflow-pauses/state.json", + ) + ) + session.commit() + return workflow_run class _PauseEntity: @@ -25,15 +74,25 @@ class _PauseEntity: return self._reasons -def test_pause_details_returns_backstage_input_url(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: +def test_pause_details_returns_backstage_input_url( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +) -> None: monkeypatch.setattr(workflow_run_module.dify_config, "APP_WEB_URL", "https://web.example.com") - workflow_run = Mock(spec=WorkflowRun) - workflow_run.tenant_id = "tenant-123" - workflow_run.status = WorkflowExecutionStatus.PAUSED - workflow_run.created_at = datetime(2024, 1, 1, 12, 0, 0) - fake_db = SimpleNamespace(engine=Mock(), session=SimpleNamespace(get=lambda *_: workflow_run)) - monkeypatch.setattr(workflow_run_module, "db", fake_db) + tenant_id = str(uuid4()) + run_id = str(uuid4()) + _persist_run( + sqlite_session, + run_id=run_id, + tenant_id=tenant_id, + status=WorkflowExecutionStatus.PAUSED, + paused=True, + ) + monkeypatch.setattr( + workflow_run_module, + "db", + _Database(engine=sqlite_session.get_bind(), session=sqlite_session), + ) reason = HumanInputRequired( form_id="form-1", @@ -58,12 +117,12 @@ def test_pause_details_returns_backstage_input_url(app: Flask, monkeypatch: pyte lambda _form_ids: {"form-1": "backstage-token"}, ) - with app.test_request_context("/console/api/workflow/run-1/pause-details", method="GET"): + with app.test_request_context(f"/console/api/workflow/{run_id}/pause-details", method="GET"): handler = inspect.unwrap(workflow_run_module.ConsoleWorkflowPauseDetailsApi.get) response, status = handler( workflow_run_module.ConsoleWorkflowPauseDetailsApi(), - "tenant-123", - workflow_run_id="run-1", + tenant_id, + workflow_run_id=run_id, ) assert status == 200 @@ -77,39 +136,56 @@ def test_pause_details_returns_backstage_input_url(app: Flask, monkeypatch: pyte assert "pending_human_inputs" not in response -def test_pause_details_tenant_isolation(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: +def test_pause_details_tenant_isolation(app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None: monkeypatch.setattr(workflow_run_module.dify_config, "APP_WEB_URL", "https://web.example.com") - workflow_run = Mock(spec=WorkflowRun) - workflow_run.tenant_id = "tenant-456" - workflow_run.status = WorkflowExecutionStatus.PAUSED - workflow_run.created_at = datetime(2024, 1, 1, 12, 0, 0) - fake_db = SimpleNamespace(engine=Mock(), session=SimpleNamespace(get=lambda *_: workflow_run)) - monkeypatch.setattr(workflow_run_module, "db", fake_db) + run_id = str(uuid4()) + _persist_run( + sqlite_session, + run_id=run_id, + tenant_id=str(uuid4()), + status=WorkflowExecutionStatus.PAUSED, + paused=True, + ) + monkeypatch.setattr( + workflow_run_module, + "db", + _Database(engine=sqlite_session.get_bind(), session=sqlite_session), + ) handler = inspect.unwrap(workflow_run_module.ConsoleWorkflowPauseDetailsApi.get) - with app.test_request_context("/console/api/workflow/run-1/pause-details", method="GET"): + with app.test_request_context(f"/console/api/workflow/{run_id}/pause-details", method="GET"): with pytest.raises(NotFoundError): handler( workflow_run_module.ConsoleWorkflowPauseDetailsApi(), - "tenant-123", - workflow_run_id="run-1", + str(uuid4()), + workflow_run_id=run_id, ) -def test_pause_details_returns_empty_response_for_non_paused_run(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - workflow_run = Mock(spec=WorkflowRun) - workflow_run.tenant_id = "tenant-123" - workflow_run.status = WorkflowExecutionStatus.RUNNING - fake_db = SimpleNamespace(engine=Mock(), session=SimpleNamespace(get=lambda *_: workflow_run)) - monkeypatch.setattr(workflow_run_module, "db", fake_db) +def test_pause_details_returns_empty_response_for_non_paused_run( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +) -> None: + tenant_id = str(uuid4()) + run_id = str(uuid4()) + _persist_run( + sqlite_session, + run_id=run_id, + tenant_id=tenant_id, + status=WorkflowExecutionStatus.RUNNING, + ) + monkeypatch.setattr( + workflow_run_module, + "db", + _Database(engine=sqlite_session.get_bind(), session=sqlite_session), + ) - with app.test_request_context("/console/api/workflow/run-1/pause-details", method="GET"): + with app.test_request_context(f"/console/api/workflow/{run_id}/pause-details", method="GET"): handler = inspect.unwrap(workflow_run_module.ConsoleWorkflowPauseDetailsApi.get) response, status = handler( workflow_run_module.ConsoleWorkflowPauseDetailsApi(), - "tenant-123", - workflow_run_id="run-1", + tenant_id, + workflow_run_id=run_id, ) assert status == 200