test: migrate Service API app sessions and ORM models to SQLite (#40558)

This commit is contained in:
Asuka Minato authored and GitHub committed 2026-09-10 04:13:36 +00:00
1 parent e7cfe142bc
commit c529c3b2f1
6 files changed
+388 -227

No files matched your search

@@ -14,13 +14,13 @@ Note: API endpoint tests for annotation controllers are complex due to:
import uuid
from inspect import unwrap
from types import SimpleNamespace
from unittest.mock import MagicMock, Mock
from unittest.mock import Mock
import pytest
from flask import Flask, request
from flask_restx.api import HTTPStatus
from pydantic import ValidationError
from sqlalchemy.orm import Session
from controllers.service_api.app.annotation import (
AnnotationCreatePayload,
@@ -32,10 +32,34 @@ from controllers.service_api.app.annotation import (
AnnotationUpdateDeleteApi,
)
from extensions.ext_redis import redis_client
from models.model import App
from models.model import App, AppMode, MessageAnnotation
from services.annotation_service import AppAnnotationService
def _app(app_id: str = "app", tenant_id: str = "tenant") -> App:
return App(
id=app_id,
tenant_id=tenant_id,
name="Service API app",
description="",
mode=AppMode.CHAT,
enable_site=True,
enable_api=True,
max_active_requests=0,
)
def _annotation() -> MessageAnnotation:
annotation = MessageAnnotation(
app_id="app",
question="q",
content="a",
account_id="account",
)
annotation.id = "a1"
return annotation
class TestAnnotationCreatePayload:
"""Test suite for AnnotationCreatePayload Pydantic model."""
@@ -164,7 +188,7 @@ class TestAnnotationReplyActionApi:
monkeypatch.setattr(AppAnnotationService, "enable_app_annotation", enable_mock)
api = AnnotationReplyActionApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(id="app", tenant_id="tenant")
app_model = _app()
with app.test_request_context(
"/apps/annotation-reply/enable",
method="POST",
@@ -181,7 +205,7 @@ class TestAnnotationReplyActionApi:
monkeypatch.setattr(AppAnnotationService, "disable_app_annotation", disable_mock)
api = AnnotationReplyActionApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(id="app", tenant_id="tenant")
app_model = _app()
with app.test_request_context(
"/apps/annotation-reply/disable",
method="POST",
@@ -199,7 +223,7 @@ class TestAnnotationReplyActionStatusApi:
monkeypatch.setattr(redis_client, "get", lambda *_args, **_kwargs: None)
api = AnnotationReplyActionStatusApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app")
app_model = _app()
with pytest.raises(ValueError):
handler(api, app_model=app_model, job_id="j1", action="enable")
@@ -213,7 +237,7 @@ class TestAnnotationReplyActionStatusApi:
monkeypatch.setattr(redis_client, "get", _get)
api = AnnotationReplyActionStatusApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app")
app_model = _app()
response, status = handler(api, app_model=app_model, job_id="j1", action="enable")
assert status == 200
assert response["job_status"] == "error"
@@ -221,42 +245,47 @@ class TestAnnotationReplyActionStatusApi:
class TestAnnotationListApi:
def test_get_uses_defaults(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
annotation = SimpleNamespace(id="a1", question="q", content="a", created_at=0)
def test_get_uses_defaults(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None:
annotation = _annotation()
get_mock = Mock(return_value=([annotation], 1))
monkeypatch.setattr(AppAnnotationService, "get_annotation_list_by_app_id", get_mock)
api = AnnotationListApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app")
app_model = _app()
with app.test_request_context("/apps/annotations", method="GET"):
query = AnnotationListQuery.model_validate(request.args.to_dict(flat=True))
response = handler(api, query, MagicMock(), app_model=app_model)
response = handler(api, query, sqlite_session, app_model=app_model)
assert response["page"] == 1
assert response["limit"] == 20
session = get_mock.call_args.args[-1]
assert isinstance(session, MagicMock)
assert session is sqlite_session
assert get_mock.call_args.args == ("app", 1, 20, "", session)
def test_get_accepts_valid_numeric_strings(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
annotation = SimpleNamespace(id="a1", question="q", content="a", created_at=0)
def test_get_accepts_valid_numeric_strings(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
) -> None:
annotation = _annotation()
get_mock = Mock(return_value=([annotation], 1))
monkeypatch.setattr(AppAnnotationService, "get_annotation_list_by_app_id", get_mock)
api = AnnotationListApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app")
app_model = _app()
with app.test_request_context("/apps/annotations?page=2&limit=5&keyword=refund", method="GET"):
query = AnnotationListQuery.model_validate(request.args.to_dict(flat=True))
response = handler(api, query, MagicMock(), app_model=app_model)
response = handler(api, query, sqlite_session, app_model=app_model)
assert response["total"] == 1
assert response["page"] == 2
assert response["limit"] == 5
session = get_mock.call_args.args[-1]
assert isinstance(session, MagicMock)
assert session is sqlite_session
assert get_mock.call_args.args == ("app", 2, 5, "refund", session)
@pytest.mark.parametrize("query_string", ["page=abc&limit=5", "page=1&limit=abc", "page=&limit=5", "limit=0"])
def test_get_rejects_invalid_explicit_pagination_value(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, query_string: str
self,
app: Flask,
monkeypatch: pytest.MonkeyPatch,
query_string: str,
) -> None:
get_mock = Mock(return_value=([], 0))
monkeypatch.setattr(AppAnnotationService, "get_annotation_list_by_app_id", get_mock)
@@ -269,24 +298,24 @@ class TestAnnotationListApi:
assert unwrap(api.get) is not api.get
get_mock.assert_not_called()
def test_create(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
annotation = SimpleNamespace(id="a1", question="q", content="a", created_at=0)
def test_create(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None:
annotation = _annotation()
monkeypatch.setattr(
AppAnnotationService, "insert_app_annotation_directly", lambda *_args, **_kwargs: annotation
)
api = AnnotationListApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(id="app")
app_model = _app()
with app.test_request_context("/apps/annotations", method="POST", json={"question": "q", "answer": "a"}):
payload = AnnotationCreatePayload.model_validate(request.get_json() or {})
response, status = handler(api, payload, MagicMock(), app_model=app_model)
response, status = handler(api, payload, sqlite_session, app_model=app_model)
assert status == HTTPStatus.CREATED
assert response["question"] == "q"
class TestAnnotationUpdateDeleteApi:
def test_update_delete(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
annotation = SimpleNamespace(id="a1", question="q", content="a", created_at=0)
def test_update_delete(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None:
annotation = _annotation()
monkeypatch.setattr(
AppAnnotationService, "update_app_annotation_directly", lambda *_args, **_kwargs: annotation
)
@@ -295,12 +324,12 @@ class TestAnnotationUpdateDeleteApi:
api = AnnotationUpdateDeleteApi()
put_handler = unwrap(api.put)
delete_handler = unwrap(api.delete)
app_model = SimpleNamespace(id="app", tenant_id="tenant")
app_model = _app()
with app.test_request_context("/apps/annotations/1", method="PUT", json={"question": "q", "answer": "a"}):
payload = AnnotationCreatePayload.model_validate(request.get_json() or {})
response = put_handler(api, payload, MagicMock(), app_model=app_model, annotation_id="1")
response = put_handler(api, payload, sqlite_session, app_model=app_model, annotation_id="1")
assert response["answer"] == "a"
with app.test_request_context("/apps/annotations/1", method="DELETE"):
response, status = delete_handler(api, MagicMock(), app_model=app_model, annotation_id="1")
response, status = delete_handler(api, sqlite_session, app_model=app_model, annotation_id="1")
assert status == 204
delete_mock.assert_called_once()
@@ -45,7 +45,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom
from fields._value_type_serializer import serialize_value_type
from graphon.variables import StringSegment
from graphon.variables.types import SegmentType
from models.enums import ConversationFromSource
from models.enums import ConversationFromSource, EndUserType
from models.model import App, AppMode, Conversation, EndUser
from services.conversation_service import ConversationService
from services.errors.conversation import (
@@ -55,11 +55,29 @@ from services.errors.conversation import (
)
def _end_user(user_id: str = "end-user-1") -> EndUser:
end_user = EndUser(
def _app(*, app_id: str = "app-1", mode: AppMode = AppMode.CHAT) -> App:
return App(
id=app_id,
tenant_id="tenant-1",
name="Service API app",
description="",
mode=mode,
enable_site=True,
enable_api=True,
max_active_requests=0,
)
def _end_user(user_id: str = "end-user-1", app_id: str = "app-1") -> EndUser:
return EndUser(
id=user_id,
tenant_id="tenant-1",
app_id=app_id,
type=EndUserType.SERVICE_API,
external_user_id="external-user-1",
name="Service API user",
session_id="session-1",
)
return end_user
def _conversation(
@@ -482,8 +500,8 @@ class TestConversationService:
mock_pagination.return_value = mock_result
result = ConversationService.pagination_by_last_id(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
last_id=None,
limit=20,
invoke_from=Mock(),
@@ -501,9 +519,7 @@ class TestConversationService:
sqlite_session.add(conversation)
sqlite_session.commit()
app_model = App(
id="app-1",
)
app_model = _app()
end_user = _end_user()
result = ConversationService.rename(
@@ -534,8 +550,8 @@ class TestConversationApiController:
def test_list_not_chat(self, app: Flask) -> None:
api = ConversationApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.COMPLETION)
end_user = SimpleNamespace()
app_model = _app(mode=AppMode.COMPLETION)
end_user = _end_user()
with app.test_request_context("/conversations", method="GET"):
with pytest.raises(NotChatAppError):
@@ -563,7 +579,7 @@ class TestConversationApiController:
api = ConversationApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", mode=AppMode.CHAT)
app_model = _app()
end_user = _end_user()
with app.test_request_context(
@@ -583,8 +599,8 @@ class TestConversationDetailApiController:
def test_delete_not_chat(self, app: Flask) -> None:
api = ConversationDetailApi()
handler = unwrap(api.delete)
app_model = SimpleNamespace(mode=AppMode.COMPLETION)
end_user = SimpleNamespace()
app_model = _app(mode=AppMode.COMPLETION)
end_user = _end_user()
with app.test_request_context("/conversations/1", method="DELETE"):
with pytest.raises(NotChatAppError):
@@ -604,8 +620,8 @@ class TestConversationDetailApiController:
api = ConversationDetailApi()
handler = unwrap(api.delete)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context("/conversations/1", method="DELETE"):
with pytest.raises(NotFound):
@@ -627,8 +643,8 @@ class TestConversationRenameApiController:
api = ConversationRenameApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/conversations/1/name",
@@ -650,8 +666,8 @@ class TestConversationVariablesApiController:
def test_not_chat(self, app: Flask) -> None:
api = ConversationVariablesApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.COMPLETION)
end_user = SimpleNamespace()
app_model = _app(mode=AppMode.COMPLETION)
end_user = _end_user()
with app.test_request_context("/conversations/1/variables", method="GET"):
with pytest.raises(NotChatAppError):
@@ -672,8 +688,8 @@ class TestConversationVariablesApiController:
api = ConversationVariablesApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/conversations/1/variables?limit=20",
@@ -711,8 +727,8 @@ class TestConversationVariablesApiController:
api = ConversationVariablesApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/conversations/1/variables?limit=20",
@@ -743,8 +759,8 @@ class TestConversationVariableDetailApiController:
api = ConversationVariableDetailApi()
handler = unwrap(api.put)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/conversations/1/variables/2",
@@ -771,8 +787,8 @@ class TestConversationVariableDetailApiController:
api = ConversationVariableDetailApi()
handler = unwrap(api.put)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/conversations/1/variables/2",
@@ -807,8 +823,8 @@ class TestConversationVariableDetailApiController:
api = ConversationVariableDetailApi()
handler = unwrap(api.put)
app_model = SimpleNamespace(mode=AppMode.CHAT)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/conversations/1/variables/2",
@@ -20,6 +20,7 @@ from sqlalchemy.orm import Session, sessionmaker
import services.app_generate_service as ags_module
from controllers.service_api.app.workflow_events import WorkflowEventsApi
from core.app.app_config.entities import AppAdditionalFeatures, WorkflowUIBasedAppConfig
from core.app.apps.advanced_chat.generate_task_pipeline import ConversationSnapshot, MessageSnapshot, WorkflowSnapshot
from core.app.apps.common import workflow_response_converter
from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter
from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom, WorkflowAppGenerateEntity
@@ -42,10 +43,10 @@ from graphon.enums import WorkflowExecutionStatus, WorkflowNodeExecutionStatus
from graphon.runtime import GraphRuntimeState, VariablePool
from libs.datetime_utils import to_utc_timestamp
from models.account import Account
from models.enums import CreatorUserRole, MessageStatus
from models.enums import CreatorUserRole, EndUserType, MessageStatus
from models.human_input import HumanInputForm
from models.model import AppMode
from models.workflow import WorkflowRun
from models.model import App, AppMode, EndUser
from models.workflow import Workflow, WorkflowRun, WorkflowType
from repositories.api_workflow_node_execution_repository import WorkflowNodeExecutionSnapshot
from repositories.entities.workflow_pause import WorkflowPauseEntity
from services.app_generate_service import AppGenerateService
@@ -84,6 +85,47 @@ def _mock_repo_for_run(monkeypatch: pytest.MonkeyPatch, workflow_run, sqlite_eng
return workflow_events_module
def _app(*, app_id: str = "app-1", tenant_id: str = "tenant-1", mode: AppMode = AppMode.WORKFLOW) -> App:
return App(
id=app_id,
tenant_id=tenant_id,
name="Service API app",
description="",
mode=mode,
enable_site=True,
enable_api=True,
max_active_requests=0,
)
def _end_user(*, user_id: str = "end-user-1", app_id: str = "app-1", tenant_id: str = "tenant-1") -> EndUser:
return EndUser(
id=user_id,
tenant_id=tenant_id,
app_id=app_id,
type=EndUserType.SERVICE_API,
external_user_id="external-user-1",
name="Service API user",
session_id="session-1",
)
def _workflow(*, workflow_id: str = "workflow-id", app_id: str = "app-id", tenant_id: str = "tenant-id") -> Workflow:
return Workflow(
id=workflow_id,
tenant_id=tenant_id,
app_id=app_id,
type=WorkflowType.WORKFLOW,
version="1",
graph=json.dumps({"nodes": [], "edges": []}),
features="{}",
created_by="owner-id",
environment_variables=[],
conversation_variables=[],
rag_pipeline_variables=[],
)
def _persist_human_input_form(
sqlite_session: Session,
*,
@@ -288,13 +330,8 @@ class TestHitlServiceApi:
monkeypatch: pytest.MonkeyPatch,
sqlite_engine: Engine,
) -> None:
workflow_run = SimpleNamespace(
id="run-1",
app_id="app-1",
created_by_role=CreatorUserRole.END_USER,
created_by="end-user-1",
finished_at=None,
)
workflow_run = _build_workflow_run(WorkflowExecutionStatus.RUNNING)
workflow_run.created_by = "end-user-1"
workflow_events_module = _mock_repo_for_run(
monkeypatch,
workflow_run=workflow_run,
@@ -309,8 +346,8 @@ class TestHitlServiceApi:
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1&continue_on_pause=true", method="GET"):
response = handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")
@@ -329,13 +366,8 @@ class TestHitlServiceApi:
monkeypatch: pytest.MonkeyPatch,
sqlite_engine: Engine,
) -> None:
workflow_run = SimpleNamespace(
id="run-1",
app_id="app-1",
created_by_role=CreatorUserRole.END_USER,
created_by="end-user-1",
finished_at=None,
)
workflow_run = _build_workflow_run(WorkflowExecutionStatus.RUNNING)
workflow_run.created_by = "end-user-1"
workflow_events_module = _mock_repo_for_run(
monkeypatch,
workflow_run=workflow_run,
@@ -351,8 +383,8 @@ class TestHitlServiceApi:
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/workflow/run-1/events?user=u1&include_state_snapshot=true&continue_on_pause=true",
@@ -384,8 +416,7 @@ class TestHitlServiceApi:
apply_config_overrides(monkeypatch, DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
monkeypatch.setattr(ags_module, "RateLimit", _DummyRateLimit)
workflow = MagicMock()
workflow.created_by = "owner-id"
workflow = _workflow()
monkeypatch.setattr(AppGenerateService, "_get_workflow", lambda *args, **kwargs: workflow)
sqlite_session_maker = sessionmaker(bind=sqlite_engine, expire_on_commit=False)
monkeypatch.setattr(ags_module.session_factory, "get_session_maker", lambda: sqlite_session_maker)
@@ -395,16 +426,8 @@ class TestHitlServiceApi:
generator_instance.convert_to_event_stream.side_effect = lambda payload: payload
monkeypatch.setattr(ags_module, "AdvancedChatAppGenerator", lambda: generator_instance)
app_model = MagicMock()
app_model.mode = AppMode.ADVANCED_CHAT
app_model.id = "app-id"
app_model.tenant_id = "tenant-id"
app_model.max_active_requests = 0
app_model.is_agent = False
app_model.is_agent_with_session.return_value = False
user = MagicMock()
user.id = "user-id"
app_model = _app(app_id="app-id", tenant_id="tenant-id", mode=AppMode.ADVANCED_CHAT)
user = _end_user(user_id="user-id", app_id="app-id", tenant_id="tenant-id")
with sqlite_session_maker() as session:
result = AppGenerateService.generate(
@@ -456,7 +479,6 @@ class TestHitlServiceApi:
def test_advanced_chat_blocking_pipeline_pause_payload_contract(self) -> None:
from core.app.app_config.entities import AppAdditionalFeatures
from core.app.apps.advanced_chat.generate_task_pipeline import AdvancedChatAppGenerateTaskPipeline
from models.model import EndUser
app_config = WorkflowUIBasedAppConfig(
tenant_id="tenant",
@@ -481,17 +503,17 @@ class TestHitlServiceApi:
)
pipeline = AdvancedChatAppGenerateTaskPipeline(
application_generate_entity=application_generate_entity,
workflow=SimpleNamespace(id="workflow-id", tenant_id="tenant", features_dict={}),
workflow=WorkflowSnapshot(id="workflow-id", tenant_id="tenant", features_dict={}),
queue_manager=SimpleNamespace(invoke_from=InvokeFrom.WEB_APP, graph_runtime_state=None),
conversation=SimpleNamespace(id="conv-id", mode=AppMode.ADVANCED_CHAT),
message=SimpleNamespace(
conversation=ConversationSnapshot(id="conv-id", mode=AppMode.ADVANCED_CHAT),
message=MessageSnapshot(
id="message-id",
query="hello",
created_at=datetime.utcnow(),
status=MessageStatus.NORMAL,
answer="",
),
user=EndUser(tenant_id="tenant", type="session", name="tester", session_id="session"),
user=_end_user(user_id="user", app_id="app", tenant_id="tenant"),
stream=False,
dialogue_count=1,
draft_var_saver_factory=lambda **kwargs: None,
@@ -574,9 +596,9 @@ class TestHitlServiceApi:
)
pipeline = WorkflowAppGenerateTaskPipeline(
application_generate_entity=application_generate_entity,
workflow=SimpleNamespace(id="workflow-id", tenant_id="tenant", features_dict={}),
workflow=_workflow(workflow_id="workflow-id", app_id="app", tenant_id="tenant"),
queue_manager=SimpleNamespace(invoke_from=InvokeFrom.WEB_APP, graph_runtime_state=None),
user=SimpleNamespace(id="user", session_id="session"),
user=_end_user(user_id="user", app_id="app", tenant_id="tenant"),
stream=False,
draft_var_saver_factory=lambda **kwargs: None,
)
@@ -11,15 +11,53 @@ from unittest.mock import Mock
import pytest
from flask import Flask, request
from sqlalchemy.engine import Engine
from werkzeug.exceptions import NotFound
from controllers.common.human_input import HumanInputFormSubmitPayload
from controllers.service_api.app.human_input_form import WorkflowHumanInputFormApi
from models.enums import EndUserType
from models.human_input import RecipientType
from models.model import App, AppMode, EndUser
def _app() -> App:
return App(
id="app-1",
tenant_id="tenant-1",
name="Service API app",
description="",
mode=AppMode.WORKFLOW,
enable_site=True,
enable_api=True,
max_active_requests=0,
)
def _end_user() -> EndUser:
return EndUser(
id="end-user-1",
tenant_id="tenant-1",
app_id="app-1",
type=EndUserType.SERVICE_API,
external_user_id="external-user-1",
name="Service API user",
session_id="session-1",
)
def _configure_service(
monkeypatch: pytest.MonkeyPatch,
service_mock: Mock,
sqlite_engine: Engine,
) -> None:
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=sqlite_engine))
class TestWorkflowHumanInputFormApi:
def test_get_success(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
def test_get_success(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine) -> None:
definition = SimpleNamespace(
model_dump=lambda **_kwargs: {
"rendered_content": "Rendered form content",
@@ -40,13 +78,11 @@ class TestWorkflowHumanInputFormApi:
service_mock.resolve_form_inputs.return_value = [
SimpleNamespace(model_dump=lambda **_kwargs: {"output_variable_name": "name"})
]
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
app_model = _app()
with app.test_request_context("/form/human_input/token-1", method="GET"):
response = handler(api, app_model=app_model, form_token="token-1")
@@ -63,7 +99,9 @@ class TestWorkflowHumanInputFormApi:
service_mock.resolve_form_inputs.assert_called_once_with(form)
service_mock.ensure_form_active.assert_called_once_with(form)
def test_get_resolves_runtime_select_values(self, app, monkeypatch: pytest.MonkeyPatch) -> None:
def test_get_resolves_runtime_select_values(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
) -> None:
definition = SimpleNamespace(
model_dump=lambda **_kwargs: {
"rendered_content": "Rendered form content",
@@ -93,13 +131,11 @@ class TestWorkflowHumanInputFormApi:
service_mock = Mock()
service_mock.get_form_by_token.return_value = form
service_mock.resolve_form_inputs.return_value = [resolved_input]
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
app_model = _app()
with app.test_request_context("/form/human_input/token-1", method="GET"):
response = handler(api, app_model=app_model, form_token="token-1")
@@ -108,7 +144,7 @@ class TestWorkflowHumanInputFormApi:
assert payload["inputs"][0]["option_source"]["value"] == ["approve", "reject"]
service_mock.resolve_form_inputs.assert_called_once_with(form)
def test_get_form_not_in_app(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
def test_get_form_not_in_app(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine) -> None:
form = SimpleNamespace(
app_id="another-app",
tenant_id="tenant-1",
@@ -116,13 +152,11 @@ class TestWorkflowHumanInputFormApi:
)
service_mock = Mock()
service_mock.get_form_by_token.return_value = form
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
app_model = _app()
with app.test_request_context("/form/human_input/token-1", method="GET"):
with pytest.raises(NotFound):
@@ -138,7 +172,11 @@ class TestWorkflowHumanInputFormApi:
],
)
def test_get_rejects_non_service_api_recipient_types(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, recipient_type: RecipientType
self,
app: Flask,
monkeypatch: pytest.MonkeyPatch,
sqlite_engine: Engine,
recipient_type: RecipientType,
) -> None:
form = SimpleNamespace(
app_id="app-1",
@@ -148,13 +186,11 @@ class TestWorkflowHumanInputFormApi:
)
service_mock = Mock()
service_mock.get_form_by_token.return_value = form
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
app_model = _app()
with app.test_request_context("/form/human_input/token-1", method="GET"):
with pytest.raises(NotFound):
@@ -162,7 +198,7 @@ class TestWorkflowHumanInputFormApi:
service_mock.ensure_form_active.assert_not_called()
def test_post_success(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
def test_post_success(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine) -> None:
form = SimpleNamespace(
app_id="app-1",
tenant_id="tenant-1",
@@ -170,14 +206,12 @@ class TestWorkflowHumanInputFormApi:
)
service_mock = Mock()
service_mock.get_form_by_token.return_value = form
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/form/human_input/token-1",
@@ -197,7 +231,9 @@ class TestWorkflowHumanInputFormApi:
submission_end_user_id="end-user-1",
)
def test_post_accepts_select_file_and_file_list_inputs(self, app, monkeypatch: pytest.MonkeyPatch) -> None:
def test_post_accepts_select_file_and_file_list_inputs(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
) -> None:
form = SimpleNamespace(
app_id="app-1",
tenant_id="tenant-1",
@@ -205,14 +241,12 @@ class TestWorkflowHumanInputFormApi:
)
service_mock = Mock()
service_mock.get_form_by_token.return_value = form
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
inputs = {
"decision": "approve",
"attachment": {
@@ -273,7 +307,11 @@ class TestWorkflowHumanInputFormApi:
],
)
def test_post_rejects_non_service_api_recipient_types(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, recipient_type: RecipientType
self,
app: Flask,
monkeypatch: pytest.MonkeyPatch,
sqlite_engine: Engine,
recipient_type: RecipientType,
) -> None:
form = SimpleNamespace(
app_id="app-1",
@@ -282,14 +320,12 @@ class TestWorkflowHumanInputFormApi:
)
service_mock = Mock()
service_mock.get_form_by_token.return_value = form
workflow_module = sys.modules["controllers.service_api.app.human_input_form"]
monkeypatch.setattr(workflow_module, "HumanInputService", lambda _engine: service_mock)
monkeypatch.setattr(workflow_module, "db", SimpleNamespace(engine=object()))
_configure_service(monkeypatch, service_mock, sqlite_engine)
api = WorkflowHumanInputFormApi()
handler = unwrap(api.post)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1")
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/form/human_input/token-1",
@@ -17,7 +17,6 @@ Focus on:
import uuid
from collections.abc import Iterator
from inspect import unwrap
from types import SimpleNamespace
from unittest.mock import Mock, patch
import pytest
@@ -36,7 +35,7 @@ from controllers.service_api.app.message import (
MessageListQuery,
MessageSuggestedApi,
)
from models.enums import FeedbackRating
from models.enums import EndUserType, FeedbackRating
from models.model import App, AppMode, EndUser
from services.errors.conversation import ConversationNotExistsError
from services.errors.message import (
@@ -47,6 +46,31 @@ from services.errors.message import (
from services.message_service import MessageService
def _app(*, mode: AppMode = AppMode.CHAT) -> App:
return App(
id="app-1",
tenant_id="tenant-1",
name="Service API app",
description="",
mode=mode,
enable_site=True,
enable_api=True,
max_active_requests=0,
)
def _end_user() -> EndUser:
return EndUser(
id="end-user-1",
tenant_id="tenant-1",
app_id="app-1",
type=EndUserType.SERVICE_API,
external_user_id="external-user-1",
name="Service API user",
session_id="session-1",
)
@pytest.fixture
def orm_session(sqlite_engine: Engine) -> Iterator[Session]:
"""Provide a real caller-owned session for MessageService interface tests."""
@@ -273,8 +297,8 @@ class TestMessageService:
mock_pagination.return_value = mock_result
result = MessageService.pagination_by_first_id(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
conversation_id=str(uuid.uuid4()),
first_id=None,
limit=20,
@@ -294,8 +318,8 @@ class TestMessageService:
with pytest.raises(services.errors.conversation.ConversationNotExistsError):
MessageService.pagination_by_first_id(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
conversation_id="invalid_id",
first_id=None,
limit=20,
@@ -309,8 +333,8 @@ class TestMessageService:
with pytest.raises(FirstMessageNotExistsError):
MessageService.pagination_by_first_id(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
conversation_id=str(uuid.uuid4()),
first_id="invalid_first_id",
limit=20,
@@ -323,9 +347,9 @@ class TestMessageService:
mock_create_feedback.return_value = None
MessageService.create_feedback(
app_model=App(),
app_model=_app(),
message_id=str(uuid.uuid4()),
user=EndUser(),
user=_end_user(),
rating=FeedbackRating.LIKE,
content="Great response!",
session=orm_session,
@@ -340,9 +364,9 @@ class TestMessageService:
with pytest.raises(MessageNotExistsError):
MessageService.create_feedback(
app_model=App(),
app_model=_app(),
message_id="invalid_message_id",
user=EndUser(),
user=_end_user(),
rating=FeedbackRating.LIKE,
content=None,
session=orm_session,
@@ -357,7 +381,7 @@ class TestMessageService:
]
mock_get_feedbacks.return_value = mock_feedbacks
result = MessageService.get_all_messages_feedbacks(app_model=App(), page=1, limit=20, session=orm_session)
result = MessageService.get_all_messages_feedbacks(app_model=_app(), page=1, limit=20, session=orm_session)
assert len(result) == 2
assert result[0]["rating"] == "like"
@@ -369,8 +393,8 @@ class TestMessageService:
mock_get_questions.return_value = mock_questions
result = MessageService.get_suggested_questions_after_answer(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
message_id=str(uuid.uuid4()),
invoke_from=Mock(),
session=orm_session,
@@ -386,8 +410,8 @@ class TestMessageService:
with pytest.raises(SuggestedQuestionsAfterAnswerDisabledError):
MessageService.get_suggested_questions_after_answer(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
message_id=str(uuid.uuid4()),
invoke_from=Mock(),
session=orm_session,
@@ -400,8 +424,8 @@ class TestMessageService:
with pytest.raises(MessageNotExistsError):
MessageService.get_suggested_questions_after_answer(
app_model=App(),
user=EndUser(),
app_model=_app(),
user=_end_user(),
message_id="invalid_message_id",
invoke_from=Mock(),
session=orm_session,
@@ -412,8 +436,8 @@ class TestMessageListApi:
def test_not_chat_app(self, app: Flask) -> None:
api = MessageListApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.COMPLETION.value)
end_user = SimpleNamespace()
app_model = _app(mode=AppMode.COMPLETION)
end_user = _end_user()
# @model_validate parses ahead of the app-mode guard, so the id has to be well-formed to
# reach the branch this test is about.
@@ -435,8 +459,8 @@ class TestMessageListApi:
api = MessageListApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/messages?conversation_id=00000000-0000-0000-0000-000000000001",
@@ -459,8 +483,8 @@ class TestMessageListApi:
api = MessageListApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/messages?conversation_id=00000000-0000-0000-0000-000000000001&first_id=00000000-0000-0000-0000-000000000002",
@@ -485,8 +509,8 @@ class TestMessageFeedbackApi:
api = MessageFeedbackApi()
handler = unwrap(api.post)
app_model = SimpleNamespace()
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context(
"/messages/m1/feedbacks",
@@ -517,7 +541,7 @@ class TestAppGetFeedbacksApi:
api = AppGetFeedbacksApi()
handler = unwrap(api.get)
app_model = SimpleNamespace()
app_model = _app()
with app.test_request_context("/app/feedbacks?page=1&limit=20", method="GET"):
response = handler(
@@ -531,8 +555,8 @@ class TestMessageSuggestedApi:
def test_not_chat(self, app: Flask) -> None:
api = MessageSuggestedApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.COMPLETION.value)
end_user = SimpleNamespace()
app_model = _app(mode=AppMode.COMPLETION)
end_user = _end_user()
with app.test_request_context("/messages/m1/suggested", method="GET"):
with pytest.raises(NotChatAppError):
@@ -547,8 +571,8 @@ class TestMessageSuggestedApi:
api = MessageSuggestedApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context("/messages/m1/suggested", method="GET"):
with pytest.raises(NotFound):
@@ -563,8 +587,8 @@ class TestMessageSuggestedApi:
api = MessageSuggestedApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context("/messages/m1/suggested", method="GET"):
with pytest.raises(BadRequest):
@@ -579,8 +603,8 @@ class TestMessageSuggestedApi:
api = MessageSuggestedApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context("/messages/m1/suggested", method="GET"):
with pytest.raises(InternalServerError):
@@ -595,8 +619,8 @@ class TestMessageSuggestedApi:
api = MessageSuggestedApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace()
app_model = _app()
end_user = _end_user()
with app.test_request_context("/messages/m1/suggested", method="GET"):
response = handler(api, app_model=app_model, end_user=end_user, message_id="m1")
@@ -11,15 +11,66 @@ from unittest.mock import Mock
import pytest
from flask import Flask
from sqlalchemy.engine import Engine
from werkzeug.exceptions import NotFound
from controllers.service_api.app.error import NotWorkflowAppError
from controllers.service_api.app.workflow_events import WorkflowEventsApi
from models.enums import CreatorUserRole
from models.model import AppMode
from graphon.enums import WorkflowExecutionStatus
from models.enums import CreatorUserRole, EndUserType, WorkflowRunTriggeredFrom
from models.model import App, AppMode, EndUser
from models.workflow import WorkflowRun, WorkflowType
def _mock_repo_for_run(monkeypatch: pytest.MonkeyPatch, workflow_run):
def _app(*, mode: AppMode = AppMode.WORKFLOW) -> App:
return App(
id="app-1",
tenant_id="tenant-1",
name="Service API app",
description="",
mode=mode,
enable_site=True,
enable_api=True,
max_active_requests=0,
)
def _end_user() -> EndUser:
return EndUser(
id="end-user-1",
tenant_id="tenant-1",
app_id="app-1",
type=EndUserType.SERVICE_API,
external_user_id="external-user-1",
name="Service API user",
session_id="session-1",
)
def _workflow_run(*, created_by_role: CreatorUserRole = CreatorUserRole.END_USER) -> WorkflowRun:
return WorkflowRun(
id="run-1",
tenant_id="tenant-1",
app_id="app-1",
workflow_id="workflow-1",
type=WorkflowType.WORKFLOW,
triggered_from=WorkflowRunTriggeredFrom.APP_RUN,
version="1",
graph=None,
inputs="{}",
status=WorkflowExecutionStatus.RUNNING,
outputs="{}",
error=None,
elapsed_time=0,
total_tokens=0,
total_steps=0,
created_by_role=created_by_role,
created_by="end-user-1" if created_by_role == CreatorUserRole.END_USER else "account-1",
created_at=datetime(2026, 1, 1, tzinfo=UTC),
)
def _mock_repo_for_run(monkeypatch: pytest.MonkeyPatch, workflow_run, sqlite_engine: Engine):
workflow_events_module = sys.modules["controllers.service_api.app.workflow_events"]
repo = SimpleNamespace(get_workflow_run_by_id_and_tenant_id=lambda **_kwargs: workflow_run)
monkeypatch.setattr(
@@ -27,7 +78,7 @@ def _mock_repo_for_run(monkeypatch: pytest.MonkeyPatch, workflow_run):
"create_api_workflow_run_repository",
lambda *_args, **_kwargs: repo,
)
monkeypatch.setattr(workflow_events_module, "db", SimpleNamespace(engine=object()))
monkeypatch.setattr(workflow_events_module, "db", SimpleNamespace(engine=sqlite_engine))
return workflow_events_module
@@ -35,51 +86,42 @@ class TestWorkflowEventsApi:
def test_wrong_app_mode(self, app: Flask) -> None:
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(mode=AppMode.CHAT.value)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app(mode=AppMode.CHAT)
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1", method="GET"):
with pytest.raises(NotWorkflowAppError):
handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")
def test_workflow_run_not_found(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
_mock_repo_for_run(monkeypatch, workflow_run=None)
def test_workflow_run_not_found(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine) -> None:
_mock_repo_for_run(monkeypatch, workflow_run=None, sqlite_engine=sqlite_engine)
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1", method="GET"):
with pytest.raises(NotFound):
handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")
def test_workflow_run_permission_denied(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
workflow_run = SimpleNamespace(
id="run-1",
app_id="app-1",
created_by_role=CreatorUserRole.ACCOUNT,
created_by="another-user",
finished_at=None,
)
_mock_repo_for_run(monkeypatch, workflow_run=workflow_run)
def test_workflow_run_permission_denied(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
) -> None:
workflow_run = _workflow_run(created_by_role=CreatorUserRole.ACCOUNT)
_mock_repo_for_run(monkeypatch, workflow_run=workflow_run, sqlite_engine=sqlite_engine)
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1", method="GET"):
with pytest.raises(NotFound):
handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")
def test_finished_run_returns_sse(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
workflow_run = SimpleNamespace(
id="run-1",
app_id="app-1",
created_by_role=CreatorUserRole.END_USER,
created_by="end-user-1",
finished_at=datetime(2099, 1, 1, tzinfo=UTC),
)
workflow_events_module = _mock_repo_for_run(monkeypatch, workflow_run=workflow_run)
def test_finished_run_returns_sse(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine) -> None:
workflow_run = _workflow_run()
workflow_run.finished_at = datetime(2099, 1, 1, tzinfo=UTC)
workflow_events_module = _mock_repo_for_run(monkeypatch, workflow_run=workflow_run, sqlite_engine=sqlite_engine)
monkeypatch.setattr(
workflow_events_module.WorkflowResponseConverter,
"workflow_run_result_to_finish_response",
@@ -91,8 +133,8 @@ class TestWorkflowEventsApi:
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1", method="GET"):
response = handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")
@@ -104,15 +146,11 @@ class TestWorkflowEventsApi:
assert payload["task_id"] == "run-1"
assert payload["event"] == "workflow_finished"
def test_running_run_streams_events(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
workflow_run = SimpleNamespace(
id="run-1",
app_id="app-1",
created_by_role=CreatorUserRole.END_USER,
created_by="end-user-1",
finished_at=None,
)
workflow_events_module = _mock_repo_for_run(monkeypatch, workflow_run=workflow_run)
def test_running_run_streams_events(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
) -> None:
workflow_run = _workflow_run()
workflow_events_module = _mock_repo_for_run(monkeypatch, workflow_run=workflow_run, sqlite_engine=sqlite_engine)
msg_generator = Mock()
msg_generator.retrieve_events.return_value = ["raw-event"]
workflow_generator = Mock()
@@ -122,8 +160,8 @@ class TestWorkflowEventsApi:
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1", method="GET"):
response = handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")
@@ -136,15 +174,11 @@ class TestWorkflowEventsApi:
)
workflow_generator.convert_to_event_stream.assert_called_once_with(["raw-event"])
def test_running_run_with_snapshot(self, app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
workflow_run = SimpleNamespace(
id="run-1",
app_id="app-1",
created_by_role=CreatorUserRole.END_USER,
created_by="end-user-1",
finished_at=None,
)
workflow_events_module = _mock_repo_for_run(monkeypatch, workflow_run=workflow_run)
def test_running_run_with_snapshot(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
) -> None:
workflow_run = _workflow_run()
workflow_events_module = _mock_repo_for_run(monkeypatch, workflow_run=workflow_run, sqlite_engine=sqlite_engine)
msg_generator = Mock()
workflow_generator = Mock()
workflow_generator.convert_to_event_stream.return_value = iter(["data: snapshot\n\n"])
@@ -155,8 +189,8 @@ class TestWorkflowEventsApi:
api = WorkflowEventsApi()
handler = unwrap(api.get)
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW)
end_user = SimpleNamespace(id="end-user-1")
app_model = _app()
end_user = _end_user()
with app.test_request_context("/workflow/run-1/events?user=u1&include_state_snapshot=true", method="GET"):
response = handler(api, app_model=app_model, end_user=end_user, workflow_run_id="run-1")