From 8fb5dd85878c847b6f07abc67504288f2057f572 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Sat, 15 Aug 2026 15:15:28 +0000 Subject: [PATCH] test: migrate agent controller sessions and ORM models to SQLite (#40600) Co-authored-by: Byron Wang --- .../console/agent/test_agent_controllers.py | 168 +++++++++--------- .../console/agent/test_app_helpers.py | 9 +- .../console/app/test_agent_app_sandbox.py | 13 +- .../console/app/test_statistic_api.py | 48 +++-- 4 files changed, 134 insertions(+), 104 deletions(-) diff --git a/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py b/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py index 48135e8d35d..d91005fa377 100644 --- a/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py +++ b/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py @@ -64,9 +64,10 @@ from controllers.console.app.message import ( AgentMessageSuggestedQuestionApi, ) from core.app.entities.app_invoke_entities import InvokeFrom +from models.account import Account, TenantAccountRole from models.agent import Agent, AgentConfigDraftType, AgentScope, AgentSource, AgentStatus from models.enums import ApiTokenType, ConversationFromSource -from models.model import ApiToken, App, AppMode, Conversation, Message +from models.model import ApiToken, App, AppMode, Conversation, IconType, Message from services.entities.agent_entities import ( ComposerSavePayload, ComposerSaveStrategy, @@ -200,36 +201,39 @@ def _agent_app_composer_response() -> dict: } -def _app_detail_obj(**overrides): +def _app_detail_obj(**overrides) -> App: data = { "id": "app-1", "tenant_id": "tenant-1", "name": "Iris", "description": "Agent app", - "mode_compatible_with_agent": "agent", + "mode": AppMode.AGENT, "icon_type": "emoji", "icon": "robot", "icon_background": "#fff", "enable_site": False, "enable_api": False, - "app_model_config": None, - "workflow": None, "tracing": None, "use_icon_as_answer_icon": False, "created_by": "account-1", "created_at": None, "updated_by": "account-1", "updated_at": None, - "access_mode": None, - "tags": [], - "api_base_url": None, "max_active_requests": 0, - "deleted_tools": [], - "site": None, - "bound_agent_id": "00000000-0000-0000-0000-000000000001", } + overrides.pop("bound_agent_id", None) data.update(overrides) - return SimpleNamespace(**data) + data["icon_type"] = IconType(data["icon_type"]) + return App(**data) + + +def _account(*, account_id: str = "account-1", privileged: bool = False, timezone: str | None = None) -> Account: + account = Account(name="Agent Controller Tester", email=f"{account_id}@example.com") + account.id = account_id + account.timezone = timezone + if privileged: + account.role = TenantAccountRole.OWNER + return account def _candidates_response(variant: str) -> dict: @@ -307,7 +311,7 @@ def account_id() -> str: def test_agent_app_list_and_create_use_agent_route( - app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str + app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str, sqlite_session: Session ) -> None: captured: dict[str, object] = {} @@ -334,12 +338,11 @@ def test_agent_app_list_and_create_use_agent_route( roster_controller.AgentRosterService, "load_app_backing_agents_by_app_id", lambda _self, **kwargs: { - "app-list": SimpleNamespace( + "app-list": Agent( id="agent-list", app_id="app-list", backing_app_id=None, role="List role", - debug_conversation_id="debug-conversation-list", active_config_snapshot_id=None, ) }, @@ -347,12 +350,11 @@ def test_agent_app_list_and_create_use_agent_route( monkeypatch.setattr( roster_controller.AgentRosterService, "get_app_backing_agent", - lambda _self, **kwargs: SimpleNamespace( + lambda _self, **kwargs: Agent( id="agent-created", app_id="app-created", backing_app_id=None, role="Created role", - debug_conversation_id="debug-conversation-created", active_config_snapshot_id=None, ), ) @@ -412,7 +414,9 @@ def test_agent_app_list_and_create_use_agent_route( with app.test_request_context( "/console/api/agent?page=1&limit=10&mode=workflow&sort_by=recently_created&is_created_by_me=true" ): - listed = unwrap(AgentAppListApi.get)(AgentAppListApi(), MagicMock(), "tenant-1", SimpleNamespace(id=account_id)) + listed = unwrap(AgentAppListApi.get)( + AgentAppListApi(), sqlite_session, "tenant-1", _account(account_id=account_id) + ) assert listed["page"] == 1 assert listed["limit"] == 10 assert listed["total"] == 1 @@ -448,9 +452,9 @@ def test_agent_app_list_and_create_use_agent_route( AgentAppCreatePayload( name="Iris", description="Agent app", role="Coordinator", icon_type="emoji", icon="robot" ), - MagicMock(), + sqlite_session, "tenant-1", - SimpleNamespace(id=account_id), + _account(account_id=account_id), ) assert status == 201 assert created["id"] == "agent-created" @@ -499,7 +503,7 @@ def test_agent_app_create_omits_optional_role_as_empty_string( "_serialize_agent_app_detail", lambda _session, app_model, **_kwargs: {"id": "agent-created", "app_id": app_model.id}, ) - current_user = SimpleNamespace(id=account_id) + current_user = _account(account_id=account_id) with app.test_request_context( "/console/api/agent", json={"name": "No-role Iris", "description": "Agent app", "icon_type": "emoji", "icon": "robot"}, @@ -577,7 +581,8 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id( monkeypatch.setattr(roster_controller, "AppService", FakeAppService) session = sqlite_session - detail = unwrap(AgentAppApi.get)(AgentAppApi(), session, tenant_id, SimpleNamespace(id=account_id), agent_id) + with app.test_request_context(): + detail = unwrap(AgentAppApi.get)(AgentAppApi(), session, tenant_id, _account(account_id=account_id), agent_id) assert detail["id"] == agent_id assert detail["app_id"] == app_id assert detail["debug_conversation_id"] == "debug-conversation-detail" @@ -597,7 +602,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id( AgentAppUpdatePayload(name="Renamed", description="", role="Reviewer", icon_type="emoji", icon="R"), session, tenant_id, - SimpleNamespace(id=account_id), + _account(account_id=account_id), agent_id, ) assert updated["name"] == "Renamed" @@ -621,7 +626,7 @@ def test_agent_app_copy_uses_agent_id_and_returns_agent_detail( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" - current_user = SimpleNamespace(id=account_id) + current_user = _account(account_id=account_id) copied_app = _app_detail_obj(id="copied-app", bound_agent_id="copied-agent") captured: dict[str, object] = {} @@ -696,7 +701,7 @@ def test_agent_debug_conversation_refresh_resets_build_for_current_user( method="POST", ): response = unwrap(AgentDebugConversationRefreshApi.post)( - AgentDebugConversationRefreshApi(), MagicMock(), "tenant-1", SimpleNamespace(id=account_id), agent_id + AgentDebugConversationRefreshApi(), MagicMock(), "tenant-1", _account(account_id=account_id), agent_id ) assert response == { "debug_conversation_id": "new-debug-conversation-id", @@ -714,7 +719,7 @@ def test_agent_publish_and_build_draft_routes_call_composer_service( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" - current_user = SimpleNamespace(id=account_id) + current_user = _account(account_id=account_id) captured: dict[str, dict[str, object]] = {} def publish_agent_app_draft(**kwargs: object) -> dict[str, object]: @@ -842,17 +847,17 @@ def test_agent_publish_and_build_draft_routes_call_composer_service( def test_agent_api_access_uses_agent_id_and_returns_service_api_metadata(monkeypatch: pytest.MonkeyPatch) -> None: agent_id = "00000000-0000-0000-0000-000000000001" - app_model = SimpleNamespace( + app_model = _app_detail_obj( id="app-1", tenant_id="tenant-1", enable_api=True, - api_base_url="https://api.example.test/v1", api_rpm=60, api_rph=600, ) monkeypatch.setattr(roster_controller, "_resolve_agent_app_model", lambda _session, **kwargs: app_model) monkeypatch.setattr(roster_controller, "_agent_api_key_count", lambda _session, _app: 2) monkeypatch.setattr(roster_controller, "_agent_app_access_ready", lambda _session, _app: True) + monkeypatch.setattr("models.model.dify_config.SERVICE_API_URL", "https://api.example.test/v1") response = unwrap(AgentApiAccessApi.get)(AgentApiAccessApi(), MagicMock(), "tenant-1", agent_id) assert response == { "access_ready": True, @@ -892,11 +897,10 @@ def test_agent_api_status_and_key_routes_resolve_backing_app( ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" api_key_id = "00000000-0000-0000-0000-000000000002" - app_model = SimpleNamespace( + app_model = _app_detail_obj( id="app-1", tenant_id="tenant-1", enable_api=False, - api_base_url="https://api.example.test/v1", api_rpm=0, api_rph=0, ) @@ -920,7 +924,7 @@ def test_agent_api_status_and_key_routes_resolve_backing_app( def fake_create_api_key(self, resource_id: str, tenant_id: str, *, session: object): captured["create_key"] = {"session": session, "resource_id": resource_id, "tenant_id": tenant_id} - return SimpleNamespace(id=api_key_id, type="app", token="app-test-token", last_used_at=None, created_at=None) + return ApiToken(id=api_key_id, type="app", token="app-test-token", last_used_at=None, created_at=None) def fake_delete_api_key( self, @@ -966,7 +970,7 @@ def test_agent_api_status_and_key_routes_resolve_backing_app( "resource_id": "app-1", "tenant_id": "tenant-1", } - current_user = SimpleNamespace(id="account-1", is_admin_or_owner=True) + current_user = _account(privileged=True) deleted, delete_status = unwrap(AgentApiKeyApi.delete)( AgentApiKeyApi(), unbound_session, "tenant-1", current_user, agent_id, api_key_id ) @@ -986,7 +990,9 @@ def test_agent_api_status_and_key_routes_resolve_backing_app( ] -def test_agent_app_update_allows_empty_role(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: +def test_agent_app_update_allows_empty_role( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +) -> None: agent_id = "00000000-0000-0000-0000-000000000001" app_model = _app_detail_obj(id="app-1", bound_agent_id=agent_id) captured: dict[str, object] = {} @@ -994,12 +1000,11 @@ def test_agent_app_update_allows_empty_role(app: Flask, monkeypatch: pytest.Monk monkeypatch.setattr( roster_controller.AgentRosterService, "get_app_backing_agent", - lambda _self, **kwargs: SimpleNamespace( + lambda _self, **kwargs: Agent( id=agent_id, app_id="app-1", backing_app_id=None, role="", - debug_conversation_id="debug-conversation-detail", active_config_snapshot_id=None, ), ) @@ -1033,9 +1038,9 @@ def test_agent_app_update_allows_empty_role(app: Flask, monkeypatch: pytest.Monk updated = unwrap(AgentAppApi.put)( AgentAppApi(), AgentAppUpdatePayload(name="Renamed", description="", role="", icon_type="emoji", icon="R"), - MagicMock(), + sqlite_session, "tenant-1", - SimpleNamespace(id="account-1"), + _account(), agent_id, ) assert updated["role"] == "" @@ -1104,7 +1109,7 @@ def test_agent_versions_call_services(app: Flask, monkeypatch: pytest.MonkeyPatc assert version_detail["id"] == version_id assert version_detail["agent_id"] == agent_id restored = unwrap(AgentRosterVersionRestoreApi.post)( - AgentRosterVersionRestoreApi(), MagicMock(), "tenant-1", SimpleNamespace(id="account-1"), agent_id, version_id + AgentRosterVersionRestoreApi(), MagicMock(), "tenant-1", _account(), agent_id, version_id ) assert restored == { "result": "success", @@ -1124,7 +1129,7 @@ def test_agent_observability_routes_resolve_app_from_agent_id( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" - app_model = SimpleNamespace(id="app-1") + app_model = _app_detail_obj(id="app-1") captured: dict[str, object] = {} class FakeObservabilityService: @@ -1249,7 +1254,7 @@ def test_agent_observability_routes_resolve_app_from_agent_id( monkeypatch.setattr(roster_controller, "_resolve_agent_runtime_app_model", lambda _session, **kwargs: app_model) monkeypatch.setattr(roster_controller, "_agent_observability_service", lambda *_args: FakeObservabilityService()) - account = SimpleNamespace(id=account_id, timezone="UTC") + account = _account(account_id=account_id, timezone="UTC") with app.test_request_context( "/console/api/agent/00000000-0000-0000-0000-000000000001/logs?page=2&limit=5&keyword=hello&statuses=success&statuses=failed&sources=webapp:app-1&sources=workflow:app-2:workflow-1:v1:node-1&sort_by=created_at&sort_order=asc" ): @@ -1305,7 +1310,7 @@ def test_agent_observability_routes_resolve_app_from_agent_id( def test_workflow_composer_get_put_validate_candidates_impact_and_save( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str ) -> None: - app_model = SimpleNamespace(id="app-1") + app_model = _app_detail_obj(id="app-1") payload = { "variant": ComposerVariant.WORKFLOW.value, "save_strategy": ComposerSaveStrategy.NODE_JOB_ONLY.value, @@ -1413,7 +1418,7 @@ def test_build_draft_apply_leaves_transaction_ownership_to_service() -> None: def test_workflow_composer_copy_from_roster(app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str) -> None: - app_model = SimpleNamespace(id="app-1") + app_model = _app_detail_obj(id="app-1") captured: dict[str, object] = {} def fake_copy_from_roster(**kwargs): @@ -1484,7 +1489,7 @@ def test_workflow_impact_returns_empty_without_version(app: Flask) -> None: ), MagicMock(), "tenant-1", - SimpleNamespace(id="app-1"), + _app_detail_obj(id="app-1"), "node-1", ) assert result == {"current_snapshot_id": None, "workflow_node_count": 0, "bindings": []} @@ -1560,7 +1565,7 @@ def test_agent_chat_generate_and_stop_routes_resolve_app_from_agent_id( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str, unbound_session: Session ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" - app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode="agent") + app_model = _app_detail_obj(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT) captured: dict[str, object] = {} def resolve_agent_app_model(**kwargs: object) -> object: @@ -1590,14 +1595,14 @@ def test_agent_chat_generate_and_stop_routes_resolve_app_from_agent_id( monkeypatch.setattr(completion_controller, "_stop_chat_message", stop_chat_message) with app.test_request_context(json={"inputs": {}, "query": "hello"}): assert unwrap(AgentChatMessageApi.post)( - AgentChatMessageApi(), unbound_session, "tenant-1", SimpleNamespace(id=account_id), agent_id + AgentChatMessageApi(), unbound_session, "tenant-1", _account(account_id=account_id), agent_id ) == {"result": "generated"} assert cast(dict[str, object], captured["resolve"]) == {"tenant_id": "tenant-1", "agent_id": agent_id} assert captured["resolve_session"] is unbound_session create_call = cast(dict[str, object], captured["create"]) assert create_call["session"] is unbound_session assert create_call["app_model"] is app_model - assert cast(SimpleNamespace, create_call["current_user"]).id == account_id + assert cast(Account, create_call["current_user"]).id == account_id assert unwrap(AgentChatMessageStopApi.post)( AgentChatMessageStopApi(), unbound_session, "tenant-1", account_id, agent_id, "task-1" ) == ({"result": "success"}, 200) @@ -1656,7 +1661,7 @@ def test_agent_build_chat_finalize_route_resolves_app_from_agent_id( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str, unbound_session: Session ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" - app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode="agent") + app_model = _app_detail_obj(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT) captured: dict[str, object] = {} def resolve_agent_app_model(**kwargs: object) -> object: @@ -1675,7 +1680,7 @@ def test_agent_build_chat_finalize_route_resolves_app_from_agent_id( monkeypatch.setattr(completion_controller, "_create_build_chat_finalization_message", create_finalization_message) with app.test_request_context(): assert unwrap(AgentBuildChatFinalizeApi.post)( - AgentBuildChatFinalizeApi(), unbound_session, "tenant-1", SimpleNamespace(id=account_id), agent_id + AgentBuildChatFinalizeApi(), unbound_session, "tenant-1", _account(account_id=account_id), agent_id ) == {"result": "generated"} assert cast(dict[str, object], captured["resolve"]) == {"tenant_id": "tenant-1", "agent_id": agent_id} finalize_call = cast(dict[str, object], captured["finalize"]) @@ -1683,13 +1688,13 @@ def test_agent_build_chat_finalize_route_resolves_app_from_agent_id( assert finalize_call["app_model"] is app_model assert finalize_call["current_tenant_id"] == "tenant-1" assert finalize_call["agent_id"] == agent_id - assert cast(SimpleNamespace, finalize_call["current_user"]).id == account_id + assert cast(Account, finalize_call["current_user"]).id == account_id def test_build_chat_finalization_helper_forces_debug_build_and_push_prompt( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str, unbound_session: Session ) -> None: - app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode="agent") + app_model = _app_detail_obj(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT) captured: dict[str, object] = {} def resolve_debug_conversation(**kwargs: object) -> str: @@ -1709,16 +1714,17 @@ def test_build_chat_finalization_helper_forces_debug_build_and_push_prompt( with app.test_request_context(headers={"X-Trace-Id": "trace-1"}): result = completion_controller._create_build_chat_finalization_message( current_tenant_id="tenant-1", - current_user=SimpleNamespace(id=account_id), + current_user=_account(account_id=account_id), app_model=app_model, agent_id="agent-1", session=unbound_session, ) assert result == ({"result": "success"}, 200) - assert captured["resolve_debug_conversation"] == { + resolve_call = cast(dict[str, object], captured["resolve_debug_conversation"]) + assert resolve_call.pop("current_user") is not None + assert resolve_call == { "session": unbound_session, "current_tenant_id": "tenant-1", - "current_user": SimpleNamespace(id=account_id), "app_model": app_model, "agent_id": "agent-1", "draft_type": AgentConfigDraftType.DEBUG_BUILD, @@ -1805,8 +1811,8 @@ def test_agent_chat_helper_resolves_scoped_conversation_and_forces_streaming( expected_start_new: bool, unbound_session: Session, ) -> None: - app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode="agent") - current_user = SimpleNamespace(id=account_id) + app_model = _app_detail_obj(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT) + current_user = _account(account_id=account_id) captured: dict[str, object] = {} def generate(**kwargs: object) -> dict[str, object]: @@ -1850,8 +1856,8 @@ def test_agent_chat_helper_resolves_scoped_conversation_and_forces_streaming( def test_agent_chat_helper_ignores_private_exit_intent_payload_key( app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str, unbound_session: Session ) -> None: - app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode="agent") - current_user = SimpleNamespace(id=account_id) + app_model = _app_detail_obj(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT) + current_user = _account(account_id=account_id) captured: dict[str, object] = {} def generate(**kwargs: object) -> dict[str, object]: @@ -1907,7 +1913,7 @@ def test_agent_chat_helper_rejects_foreign_debug_conversation_before_generation( expected_draft_type: AgentConfigDraftType, unbound_session: Session, ) -> None: - app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode="agent") + app_model = _app_detail_obj(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT) generate = MagicMock() resolve_debug_conversation = MagicMock(return_value="owned-conversation") monkeypatch.setattr(completion_controller.AppGenerateService, "generate", generate) @@ -1928,7 +1934,7 @@ def test_agent_chat_helper_rejects_foreign_debug_conversation_before_generation( with pytest.raises(NotFound): completion_controller._create_chat_message( current_tenant_id="tenant-1", - current_user=SimpleNamespace(id=account_id), + current_user=_account(account_id=account_id), app_model=app_model, agent_id="agent-1", session=unbound_session, @@ -1964,14 +1970,14 @@ def test_resolve_current_user_agent_debug_conversation_uses_agent_or_backing_app def get_app_backing_agent(self, **kwargs: object) -> object: calls.append({"get_app_backing_agent": kwargs}) - return SimpleNamespace(id="backing-agent") + return Agent(id="backing-agent") monkeypatch.setattr(completion_controller, "AgentRosterService", FakeRosterService) explicit_id = completion_controller._resolve_current_user_agent_debug_conversation_id( session="session-1", # type: ignore[arg-type] current_tenant_id="tenant-1", - current_user=SimpleNamespace(id="account-1"), - app_model=SimpleNamespace(id="app-1"), + current_user=_account(), + app_model=_app_detail_obj(id="app-1"), agent_id="agent-1", draft_type=AgentConfigDraftType.DRAFT, start_new=True, @@ -1979,16 +1985,16 @@ def test_resolve_current_user_agent_debug_conversation_uses_agent_or_backing_app fallback_id = completion_controller._resolve_current_user_agent_debug_conversation_id( session="session-1", # type: ignore[arg-type] current_tenant_id="tenant-1", - current_user=SimpleNamespace(id="account-1"), - app_model=SimpleNamespace(id="app-1"), + current_user=_account(), + app_model=_app_detail_obj(id="app-1"), agent_id=None, draft_type=AgentConfigDraftType.DEBUG_BUILD, ) fallback_preview_id = completion_controller._resolve_current_user_agent_debug_conversation_id( session="session-1", # type: ignore[arg-type] current_tenant_id="tenant-1", - current_user=SimpleNamespace(id="account-1"), - app_model=SimpleNamespace(id="app-1"), + current_user=_account(), + app_model=_app_detail_obj(id="app-1"), agent_id=None, draft_type=AgentConfigDraftType.DRAFT, start_new=True, @@ -1996,8 +2002,8 @@ def test_resolve_current_user_agent_debug_conversation_uses_agent_or_backing_app current_preview_id = completion_controller._resolve_current_user_agent_debug_conversation_id( session="session-1", # type: ignore[arg-type] current_tenant_id="tenant-1", - current_user=SimpleNamespace(id="account-1"), - app_model=SimpleNamespace(id="app-1"), + current_user=_account(), + app_model=_app_detail_obj(id="app-1"), agent_id="agent-1", draft_type=AgentConfigDraftType.DRAFT, ) @@ -2070,12 +2076,12 @@ def test_agent_chat_helper_maps_generation_errors( expected: type[Exception], unbound_session: Session, ) -> None: - app_model = SimpleNamespace(id="app-1", mode="chat") + app_model = _app_detail_obj(id="app-1", mode=AppMode.CHAT) monkeypatch.setattr(completion_controller.AppGenerateService, "generate", lambda **_: (_ for _ in ()).throw(error)) with app.test_request_context(json={"inputs": {}, "query": "hello"}): with pytest.raises(expected): completion_controller._create_chat_message( - current_user=SimpleNamespace(id="account-1"), app_model=app_model, session=unbound_session + current_user=_account(), app_model=app_model, session=unbound_session ) @@ -2084,8 +2090,8 @@ def test_agent_chat_message_routes_resolve_app_from_agent_id( ) -> None: agent_id = "00000000-0000-0000-0000-000000000001" message_id = "00000000-0000-0000-0000-000000000002" - app_model = SimpleNamespace(id="app-1", mode="agent") - current_user = SimpleNamespace(id="account-1") + app_model = _app_detail_obj(id="app-1", mode=AppMode.AGENT) + current_user = _account() captured: dict[str, object] = {} resolver_calls: list[dict[str, object]] = [] @@ -2195,7 +2201,7 @@ def test_list_chat_messages_supports_first_id_pagination( f"/console/api/agent/agent-1/chat-messages?conversation_id={conversation_id}&first_id={first_message_id}&limit=1" ): result = message_controller._list_chat_messages( - session=sqlite_session, app_model=SimpleNamespace(id=app_id, mode="chat") + session=sqlite_session, app_model=_app_detail_obj(id=app_id, mode=AppMode.CHAT) ) assert result == {"data": [older_message_id], "limit": 1, "has_more": True} @@ -2213,8 +2219,8 @@ def test_list_agent_chat_messages_uses_current_user_conversation( message_id=message_id, created_at=datetime(2025, 1, 1), ) - current_user = SimpleNamespace(id="account-1") - app_model = SimpleNamespace(id=app_id, mode="agent") + current_user = _account() + app_model = _app_detail_obj(id=app_id, mode=AppMode.AGENT) captured: dict[str, object] = {} class FakeMessagePaginationResponse: @@ -2257,8 +2263,8 @@ def test_list_agent_chat_messages_rejects_foreign_conversation( with pytest.raises(NotFound): message_controller._list_chat_messages( session=unbound_session, - app_model=SimpleNamespace(id="app-1", mode="agent"), - current_user=SimpleNamespace(id="account-1"), + app_model=_app_detail_obj(id="app-1", mode=AppMode.AGENT), + current_user=_account(), ) @@ -2278,8 +2284,8 @@ def test_update_message_feedback_rejects_empty_rating_without_existing_feedback( with pytest.raises(ValueError, match="rating cannot be None"): message_controller._update_message_feedback( session=sqlite_session, - current_user=SimpleNamespace(id="account-1"), - app_model=SimpleNamespace(id=app_id), + current_user=_account(), + app_model=_app_detail_obj(id=app_id), ) assert message.admin_feedback_with_session(session=sqlite_session) is None @@ -2319,8 +2325,8 @@ def test_get_message_suggested_questions_maps_service_errors( with pytest.raises(expected): message_controller._get_message_suggested_questions( session=unbound_session, - current_user=SimpleNamespace(id="account-1"), - app_model=SimpleNamespace(id="app-1"), + current_user=_account(), + app_model=_app_detail_obj(id="app-1"), message_id="00000000-0000-0000-0000-000000000002", ) diff --git a/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py b/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py index b396c9bfd1b..b797a2956ad 100644 --- a/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py +++ b/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py @@ -5,13 +5,18 @@ import pytest from sqlalchemy.orm import Session from controllers.console.agent import app_helpers +from models.model import App, AppMode + + +def _app() -> App: + return App(id="app-1", tenant_id="tenant-1", name="Agent App", mode=AppMode.AGENT) def test_resolve_agent_app_model_reuses_caller_session( monkeypatch: pytest.MonkeyPatch, unbound_session: Session ) -> None: session = unbound_session - app = MagicMock() + app = _app() service = MagicMock() service.get_agent_app_model.return_value = app service_factory = MagicMock(return_value=service) @@ -35,7 +40,7 @@ def test_resolve_agent_runtime_app_model_reuses_caller_session( monkeypatch: pytest.MonkeyPatch, unbound_session: Session ) -> None: session = unbound_session - app = MagicMock() + app = _app() service = MagicMock() service.get_agent_runtime_app_model.return_value = app service_factory = MagicMock(return_value=service) diff --git a/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py b/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py index c534cc346b3..5d1040bc149 100644 --- a/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py +++ b/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py @@ -8,6 +8,7 @@ from dify_agent.client import DifyAgentClientError, DifyAgentHTTPError, DifyAgen from dify_agent.protocol import BindingFileListResponse, BindingFileReadResponse from controllers.console import agent_app_sandbox as module +from models.account import Account from models.model import App, AppMode, IconType from services.agent_app_sandbox_service import AgentSandboxDownload, AgentSandboxInfo, AgentSandboxInspectorError @@ -139,6 +140,12 @@ def _app_model(app_id: str = "app-1") -> App: ) +def _account() -> Account: + account = Account(name="Sandbox Tester", email="sandbox-tester@example.com") + account.id = "account-1" + return account + + @pytest.mark.parametrize( "method", [ @@ -187,7 +194,7 @@ def test_handle_maps_sandbox_and_agent_backend_errors() -> None: def test_agent_app_sandbox_resources_proxy_service(monkeypatch: pytest.MonkeyPatch) -> None: service = _AgentAppService() - account = SimpleNamespace(id="account-1") + account = _account() monkeypatch.setattr(module, "AgentAppSandboxService", lambda: service) monkeypatch.setattr( module, @@ -226,7 +233,7 @@ def test_agent_app_sandbox_resource_returns_normalized_errors(monkeypatch: pytes raise AgentSandboxInspectorError("no_active_binding", "no active binding", status_code=404) monkeypatch.setattr(module, "AgentAppSandboxService", FailingService) - account = SimpleNamespace(id="account-1") + account = _account() monkeypatch.setattr( module, "query_params_from_request", @@ -262,7 +269,7 @@ def test_workflow_agent_sandbox_resources_proxy_service(monkeypatch: pytest.Monk req_data = module.WorkflowAgentSandboxDownloadPayload.model_validate( {"node_execution_id": "execution-1", "path": "download.txt"} ) - account = SimpleNamespace(id="account-1") + account = _account() download = unwrap(module.WorkflowAgentSandboxDownloadResource.post)( object(), req_data, "tenant-1", account, "app-1", "run-1", "agent-node" ) diff --git a/api/tests/unit_tests/controllers/console/app/test_statistic_api.py b/api/tests/unit_tests/controllers/console/app/test_statistic_api.py index 8afb4762e8c..2228069b40c 100644 --- a/api/tests/unit_tests/controllers/console/app/test_statistic_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_statistic_api.py @@ -10,6 +10,18 @@ from flask import Flask from werkzeug.exceptions import BadRequest from controllers.console.app import statistic as statistic_module +from models.account import Account +from models.model import App + + +def _account() -> Account: + account = Account(name="Statistics Tester", email="statistics-tester@example.com", timezone="UTC") + account.id = "account-1" + return account + + +def _app_model() -> App: + return App(id="app-1", tenant_id="tenant-1", name="Statistics App") class _ConnContext: @@ -56,8 +68,8 @@ def test_daily_message_statistic_returns_rows(app: Flask, monkeypatch: pytest.Mo response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) assert _json_payload(response) == {"data": [{"date": "2024-01-01", "message_count": 3}]} @@ -75,8 +87,8 @@ def test_daily_conversation_statistic_returns_rows(app: Flask, monkeypatch: pyte response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]} @@ -94,8 +106,8 @@ def test_daily_token_cost_statistic_returns_rows(app: Flask, monkeypatch: pytest response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) data = _json_payload(response) @@ -117,8 +129,8 @@ def test_daily_terminals_statistic_returns_rows(app: Flask, monkeypatch: pytest. response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) assert _json_payload(response) == {"data": [{"date": "2024-01-04", "terminal_count": 7}]} @@ -149,8 +161,8 @@ def test_daily_message_statistic_with_invalid_time_range(app: Flask, monkeypatch method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) @@ -170,8 +182,8 @@ def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.M response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) data = _json_payload(response) @@ -189,8 +201,8 @@ def test_daily_message_statistic_empty_result(app: Flask, monkeypatch: pytest.Mo response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) assert _json_payload(response) == {"data": []} @@ -213,8 +225,8 @@ def test_daily_conversation_statistic_with_time_range(app: Flask, monkeypatch: p response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]} @@ -235,8 +247,8 @@ def test_daily_token_cost_with_multiple_currencies(app: Flask, monkeypatch: pyte response = method( api, SimpleNamespace(start=None, end=None), - SimpleNamespace(timezone="UTC"), - app_model=SimpleNamespace(id="app-1"), + _account(), + app_model=_app_model(), ) data = _json_payload(response)