diff --git a/api/tests/unit_tests/controllers/console/app/test_generator_api.py b/api/tests/unit_tests/controllers/console/app/test_generator_api.py index c0bcf61ab87..b028bd15761 100644 --- a/api/tests/unit_tests/controllers/console/app/test_generator_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_generator_api.py @@ -2,7 +2,6 @@ from __future__ import annotations import json from inspect import unwrap -from types import SimpleNamespace from unittest.mock import MagicMock import pytest @@ -21,6 +20,7 @@ from controllers.console.app.generator import ( ) from core.errors.error import ProviderTokenNotInitError from models.model import App, AppMode +from models.workflow import Workflow, WorkflowType def _persist_app(session: Session, *, tenant_id: str = "t1") -> App: @@ -43,6 +43,17 @@ def _model_config_payload(): return {"provider": "openai", "name": "gpt-4o", "mode": "chat", "completion_params": {}} +def _workflow(graph_dict: dict[str, object]) -> Workflow: + return Workflow( + id="workflow-1", + tenant_id="t1", + app_id="app-1", + type=WorkflowType.WORKFLOW, + version=Workflow.VERSION_DRAFT, + graph=json.dumps(graph_dict), + ) + + def _install_workflow_service(monkeypatch: pytest.MonkeyPatch, workflow): class _Service: app_model = None @@ -154,7 +165,7 @@ def test_instruction_generate_node_missing( app_model = _persist_app(sqlite_session) - workflow = SimpleNamespace(graph_dict={"nodes": []}) + workflow = _workflow({"nodes": []}) _install_workflow_service(monkeypatch, workflow=workflow) with app.test_request_context( @@ -182,8 +193,8 @@ def test_instruction_generate_code_node(app: Flask, monkeypatch: pytest.MonkeyPa app_model = _persist_app(sqlite_session) - workflow = SimpleNamespace( - graph_dict={ + workflow = _workflow( + { "nodes": [ {"id": "node-1", "data": {"type": "code"}}, ] diff --git a/api/tests/unit_tests/repositories/test_app_definition_query_repository_agent.py b/api/tests/unit_tests/repositories/test_app_definition_query_repository_agent.py index 7cab92eedec..d483a43c59d 100644 --- a/api/tests/unit_tests/repositories/test_app_definition_query_repository_agent.py +++ b/api/tests/unit_tests/repositories/test_app_definition_query_repository_agent.py @@ -1,9 +1,8 @@ -from types import SimpleNamespace +import json from typing import Any from uuid import NAMESPACE_URL, uuid5 import pytest -from sqlalchemy import select from sqlalchemy.orm import Session, sessionmaker from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict @@ -17,7 +16,7 @@ from models.agent import ( AgentSource, AgentStatus, ) -from models.model import App, AppAnnotationSetting, AppMode +from models.model import App, AppAnnotationSetting, AppMode, AppModelConfig from repositories.app_definition_query_repository import ( AppDefinitionQueryRepository, _get_public_agent_parameter_config, @@ -28,24 +27,13 @@ def _stable_uuid(value: str) -> str: return str(uuid5(NAMESPACE_URL, value)) -def _app_model(*, tenant_id: str, bound_agent_id: str | None, app_model_config: object | None = None): - return SimpleNamespace( +def _app_model(*, tenant_id: str, app_model_config: AppModelConfig | None = None) -> App: + return App( id=_stable_uuid(f"app:{tenant_id}"), tenant_id=tenant_id, - agent_app_binding_with_session=lambda *, session: ( - session.scalar( - select(Agent) - .where( - Agent.id == bound_agent_id, - Agent.tenant_id == tenant_id, - Agent.status == AgentStatus.ACTIVE, - ) - .limit(1) - ) - if bound_agent_id - else None - ), - app_model_config_with_session=lambda *, session: app_model_config, + name="Agent App", + mode=AppMode.AGENT, + app_model_config_id=app_model_config.id if app_model_config else None, ) @@ -175,33 +163,28 @@ def test_get_public_parameter_config_loads_agent_snapshot( @pytest.mark.parametrize( "sqlite_session", - [(Agent, AgentConfigSnapshot, AgentConfigRevision, AppAnnotationSetting)], + [(Agent, AgentConfigSnapshot, AgentConfigRevision, AppAnnotationSetting, AppModelConfig)], indirect=True, ) def test_published_agent_app_parameters_use_soul_file_upload(sqlite_session: Session): tenant_id = _stable_uuid("tenant:one") agent_id = _stable_uuid("agent:one") snapshot_id = _stable_uuid("snapshot:one") - app_model_config = SimpleNamespace( - to_dict=lambda **_kwargs: { - "opening_statement": "Hi from legacy presentation config", - "file_upload": { - "enabled": False, - "image": {"enabled": False}, - }, - } - ) - app_model = _app_model( - tenant_id=tenant_id, - bound_agent_id=agent_id, - app_model_config=app_model_config, + app_model_config = AppModelConfig( + app_id=_stable_uuid(f"app:{tenant_id}"), + opening_statement="Hi from legacy presentation config", + file_upload=json.dumps({"enabled": False, "image": {"enabled": False}}), ) + sqlite_session.add(app_model_config) + sqlite_session.commit() + app_model = _app_model(tenant_id=tenant_id, app_model_config=app_model_config) _persist_agent( sqlite_session, tenant_id=tenant_id, agent_id=agent_id, active_config_snapshot_id=snapshot_id, active_config_is_published=True, + app_id=app_model.id, ) _persist_snapshot( sqlite_session, @@ -244,23 +227,45 @@ def test_published_agent_app_parameters_use_soul_file_upload(sqlite_session: Ses @pytest.mark.parametrize("sqlite_session", [(Agent, AgentConfigSnapshot, AgentConfigRevision)], indirect=True) def test_published_agent_app_parameters_requires_bound_agent(sqlite_session: Session): tenant_id = _stable_uuid("tenant:unbound") - app_model = _app_model(tenant_id=tenant_id, bound_agent_id=None) + app_model = _app_model(tenant_id=tenant_id) with pytest.raises(AgentAppGeneratorError, match="no bound Agent"): _get_public_agent_parameter_config(app_model, session=sqlite_session) @pytest.mark.parametrize("sqlite_session", [(Agent, AgentConfigSnapshot, AgentConfigRevision)], indirect=True) -def test_published_agent_app_parameters_requires_published_agent(sqlite_session: Session): - tenant_id = _stable_uuid("tenant:published") - agent_id = _stable_uuid("agent:published") - app_model = _app_model(tenant_id=tenant_id, bound_agent_id=agent_id) +def test_published_agent_app_parameters_requires_existing_active_agent(sqlite_session: Session): + requested_tenant_id = _stable_uuid("tenant:requested") + agent_id = _stable_uuid("agent:cross-tenant") + app_model = _app_model(tenant_id=requested_tenant_id) + _persist_agent( + sqlite_session, + tenant_id=_stable_uuid("tenant:other"), + agent_id=agent_id, + active_config_snapshot_id=None, + active_config_is_published=False, + app_id=app_model.id, + ) + + with pytest.raises(AgentAppGeneratorError, match="no bound Agent"): + _get_public_agent_parameter_config(app_model, session=sqlite_session) + + +@pytest.mark.parametrize("active_config_is_published", [True, False]) +@pytest.mark.parametrize("sqlite_session", [(Agent, AgentConfigSnapshot, AgentConfigRevision)], indirect=True) +def test_published_agent_app_parameters_requires_published_agent( + active_config_is_published: bool, sqlite_session: Session +): + tenant_id = _stable_uuid(f"tenant:published:{active_config_is_published}") + agent_id = _stable_uuid(f"agent:published:{active_config_is_published}") + app_model = _app_model(tenant_id=tenant_id) _persist_agent( sqlite_session, tenant_id=tenant_id, agent_id=agent_id, active_config_snapshot_id=None, - active_config_is_published=True, + active_config_is_published=active_config_is_published, + app_id=app_model.id, ) with pytest.raises(AgentAppNotPublishedError, match="not been published"): @@ -272,13 +277,14 @@ def test_published_agent_app_parameters_allows_unpublished_draft_with_active_sna tenant_id = _stable_uuid("tenant:unpublished-draft") agent_id = _stable_uuid("agent:unpublished-draft") snapshot_id = _stable_uuid("snapshot:unpublished-draft") - app_model = _app_model(tenant_id=tenant_id, bound_agent_id=agent_id) + app_model = _app_model(tenant_id=tenant_id) _persist_agent( sqlite_session, tenant_id=tenant_id, agent_id=agent_id, active_config_snapshot_id=snapshot_id, active_config_is_published=False, + app_id=app_model.id, ) _persist_snapshot( sqlite_session, @@ -302,13 +308,14 @@ def test_published_agent_app_parameters_rejects_seeded_unpublished_snapshot(sqli tenant_id = _stable_uuid("tenant:never-published") agent_id = _stable_uuid("agent:never-published") snapshot_id = _stable_uuid("snapshot:never-published") - app_model = _app_model(tenant_id=tenant_id, bound_agent_id=agent_id) + app_model = _app_model(tenant_id=tenant_id) _persist_agent( sqlite_session, tenant_id=tenant_id, agent_id=agent_id, active_config_snapshot_id=snapshot_id, active_config_is_published=False, + app_id=app_model.id, ) _persist_snapshot( sqlite_session, @@ -327,13 +334,14 @@ def test_published_agent_app_parameters_rejects_seeded_unpublished_snapshot(sqli def test_published_agent_app_parameters_requires_published_snapshot(sqlite_session: Session): tenant_id = _stable_uuid("tenant:missing-snapshot") agent_id = _stable_uuid("agent:missing-snapshot") - app_model = _app_model(tenant_id=tenant_id, bound_agent_id=agent_id) + app_model = _app_model(tenant_id=tenant_id) _persist_agent( sqlite_session, tenant_id=tenant_id, agent_id=agent_id, active_config_snapshot_id=_stable_uuid("snapshot:missing"), active_config_is_published=True, + app_id=app_model.id, ) _persist_publish_revision( sqlite_session, @@ -351,13 +359,14 @@ def test_published_agent_app_parameters_allows_missing_legacy_app_model_config(s tenant_id = _stable_uuid("tenant:no-legacy-config") agent_id = _stable_uuid("agent:no-legacy-config") snapshot_id = _stable_uuid("snapshot:no-legacy-config") - app_model = _app_model(tenant_id=tenant_id, bound_agent_id=agent_id) + app_model = _app_model(tenant_id=tenant_id) _persist_agent( sqlite_session, tenant_id=tenant_id, agent_id=agent_id, active_config_snapshot_id=snapshot_id, active_config_is_published=True, + app_id=app_model.id, ) _persist_snapshot( sqlite_session,