diff --git a/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py b/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py index 250a776299d..eb96d33989b 100644 --- a/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py +++ b/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py @@ -10,7 +10,7 @@ from sqlalchemy.orm import Session import core.rag.datasource.keyword.jieba.jieba as jieba_module from core.rag.datasource.keyword.jieba.jieba import Jieba, dumps_with_sets, set_orjson_default from core.rag.models.document import Document -from models.dataset import DatasetKeywordTable, DocumentSegment +from models.dataset import Dataset, DatasetKeywordTable, DocumentSegment class _DummyLock: @@ -21,21 +21,26 @@ class _DummyLock: return False -def _dataset_keyword_table(data_source_type: str = "database", keyword_table_dict: dict[str, Any] | None = None): - return SimpleNamespace( +def _dataset_keyword_table( + data_source_type: str = "database", keyword_table_dict: dict[str, Any] | None = None +) -> DatasetKeywordTable: + keyword_table = DatasetKeywordTable( + dataset_id="dataset-1", data_source_type=data_source_type, - get_keyword_table_dict=MagicMock(return_value=keyword_table_dict), keyword_table="", ) + keyword_table.get_keyword_table_dict = MagicMock(return_value=keyword_table_dict) + return keyword_table -def _dataset(dataset_keyword_table=None, keyword_number=None): - return SimpleNamespace( +def _dataset(dataset_keyword_table: DatasetKeywordTable | None = None, keyword_number: int | None = None) -> Dataset: + dataset = Dataset( id="dataset-1", tenant_id="tenant-1", keyword_number=keyword_number, - get_dataset_keyword_table=MagicMock(return_value=dataset_keyword_table), ) + dataset.get_dataset_keyword_table = MagicMock(return_value=dataset_keyword_table) + return dataset @pytest.fixture @@ -368,8 +373,12 @@ def test_multi_create_segment_keywords_uses_provided_and_extracted_keywords( monkeypatch.setattr(keyword, "_get_dataset_keyword_table", MagicMock(return_value={})) monkeypatch.setattr(keyword, "_save_dataset_keyword_table", MagicMock()) - first_segment = SimpleNamespace(index_node_id="node-1", content="first content", keywords=None) - second_segment = SimpleNamespace(index_node_id="node-2", content="second content", keywords=None) + first_segment = _segment(index_node_id="node-1") + first_segment.content = "first content" + first_segment.keywords = None + second_segment = _segment(index_node_id="node-2") + second_segment.content = "second content" + second_segment.keywords = None keyword.multi_create_segment_keywords( [ diff --git a/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py b/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py index 847acee37e5..520b23faa19 100644 --- a/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py +++ b/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py @@ -1,3 +1,4 @@ +import json from collections import UserString from datetime import datetime from types import SimpleNamespace @@ -19,12 +20,27 @@ from graphon.node_events import NodeRunResult from graphon.nodes import BuiltinNodeTypes from graphon.runtime import VariablePool from graphon.variables.variables import StringVariable +from models.workflow import Workflow, WorkflowType def _build_typed_node_config(node_type: NodeType): return {"id": "node-id", "data": BaseNodeData(type=node_type)} +def _workflow() -> Workflow: + """Build a real transient workflow for single-step orchestration tests.""" + return Workflow( + id="workflow-id", + tenant_id="tenant-id", + app_id="app-id", + type=WorkflowType.WORKFLOW, + version=Workflow.VERSION_DRAFT, + graph=json.dumps({"nodes": [], "edges": []}), + _features="{}", + created_by="user-id", + ) + + def _build_minimal_workflow_entry( monkeypatch: pytest.MonkeyPatch, *, @@ -222,11 +238,12 @@ class TestWorkflowEntryRun: class TestWorkflowEntrySingleStepRun: @pytest.mark.parametrize("node_type", [BuiltinNodeTypes.LOOP, BuiltinNodeTypes.ITERATION]) def test_rejects_container_nodes(self, node_type): - workflow = SimpleNamespace( - get_node_config_by_id=lambda _node_id: _build_typed_node_config(node_type), - ) + workflow = _workflow() - with pytest.raises(ValueError, match="engine-backed debug endpoints"): + with ( + patch.object(workflow, "get_node_config_by_id", return_value=_build_typed_node_config(node_type)), + pytest.raises(ValueError, match="engine-backed debug endpoints"), + ): workflow_entry.WorkflowEntry.single_step_run( workflow=workflow, node_id="node-id", @@ -258,8 +275,14 @@ class TestWorkflowEntrySingleStepRun: selector=["sys", "conversation_id"], ) ] + workflow = _workflow() + node_config = { + "id": "node-id", + "data": BaseNodeData(type=BuiltinNodeTypes.LLM, version="1", memory=object()), + } with ( + patch.object(workflow, "get_node_config_by_id", return_value=node_config), patch.object(workflow_entry, "DifyGraphInitContext", return_value=sentinel.graph_init_context), patch.object( workflow_entry, @@ -284,17 +307,6 @@ class TestWorkflowEntrySingleStepRun: return FakeLLMNode() dify_node_factory.return_value.create_node.side_effect = _create_node - workflow = SimpleNamespace( - tenant_id="tenant-id", - app_id="app-id", - id="workflow-id", - graph_dict={"nodes": [], "edges": []}, - get_node_config_by_id=lambda _node_id: { - "id": "node-id", - "data": SimpleNamespace(type=BuiltinNodeTypes.LLM, version="1", memory=object()), - }, - ) - node, generator = workflow_entry.WorkflowEntry.single_step_run( workflow=workflow, node_id="node-id", @@ -322,7 +334,13 @@ class TestWorkflowEntrySingleStepRun: def extract_variable_selector_to_variable_mapping(**_kwargs): raise NotImplementedError + workflow = _workflow() with ( + patch.object( + workflow, + "get_node_config_by_id", + return_value=_build_typed_node_config(BuiltinNodeTypes.START), + ), patch.object(workflow_entry, "DifyGraphInitContext", return_value=sentinel.graph_init_context), patch.object(workflow_entry, "GraphRuntimeState", return_value=sentinel.graph_runtime_state), patch.object(workflow_entry, "build_dify_run_context", return_value={"_dify": "context"}), @@ -342,14 +360,6 @@ class TestWorkflowEntrySingleStepRun: ), ): dify_node_factory.return_value.create_node.return_value = FakeNode() - workflow = SimpleNamespace( - tenant_id="tenant-id", - app_id="app-id", - id="workflow-id", - graph_dict={"nodes": [], "edges": []}, - get_node_config_by_id=lambda _node_id: _build_typed_node_config(BuiltinNodeTypes.START), - ) - node, generator = workflow_entry.WorkflowEntry.single_step_run( workflow=workflow, node_id="node-id", @@ -391,7 +401,13 @@ class TestWorkflowEntrySingleStepRun: def extract_variable_selector_to_variable_mapping(**_kwargs): return {"question": ["node", "question"]} + workflow = _workflow() with ( + patch.object( + workflow, + "get_node_config_by_id", + return_value=_build_typed_node_config(BuiltinNodeTypes.DATASOURCE), + ), patch.object(workflow_entry, "DifyGraphInitContext", return_value=sentinel.graph_init_context), patch.object(workflow_entry, "GraphRuntimeState", return_value=sentinel.graph_runtime_state), patch.object(workflow_entry, "build_dify_run_context", return_value={"_dify": "context"}), @@ -411,14 +427,6 @@ class TestWorkflowEntrySingleStepRun: ), ): dify_node_factory.return_value.create_node.return_value = FakeDatasourceNode() - workflow = SimpleNamespace( - tenant_id="tenant-id", - app_id="app-id", - id="workflow-id", - graph_dict={"nodes": [], "edges": []}, - get_node_config_by_id=lambda _node_id: _build_typed_node_config(BuiltinNodeTypes.DATASOURCE), - ) - node, generator = workflow_entry.WorkflowEntry.single_step_run( workflow=workflow, node_id="node-id", @@ -451,7 +459,13 @@ class TestWorkflowEntrySingleStepRun: def version(): return "1" + workflow = _workflow() with ( + patch.object( + workflow, + "get_node_config_by_id", + return_value=_build_typed_node_config(BuiltinNodeTypes.START), + ), patch.object(workflow_entry, "DifyGraphInitContext", return_value=sentinel.graph_init_context), patch.object(workflow_entry, "GraphRuntimeState", return_value=sentinel.graph_runtime_state), patch.object(workflow_entry, "build_dify_run_context", return_value={"_dify": "context"}), @@ -468,14 +482,6 @@ class TestWorkflowEntrySingleStepRun: ), ): dify_node_factory.return_value.create_node.return_value = FakeNode() - workflow = SimpleNamespace( - tenant_id="tenant-id", - app_id="app-id", - id="workflow-id", - graph_dict={"nodes": [], "edges": []}, - get_node_config_by_id=lambda _node_id: _build_typed_node_config(BuiltinNodeTypes.START), - ) - with pytest.raises(WorkflowNodeRunFailedError): workflow_entry.WorkflowEntry.single_step_run( workflow=workflow, diff --git a/api/tests/unit_tests/services/workflow/test_node_output_inspector_service.py b/api/tests/unit_tests/services/workflow/test_node_output_inspector_service.py index 2c659b2621d..8cd907ed705 100644 --- a/api/tests/unit_tests/services/workflow/test_node_output_inspector_service.py +++ b/api/tests/unit_tests/services/workflow/test_node_output_inspector_service.py @@ -9,7 +9,6 @@ from __future__ import annotations import json from collections.abc import Sequence from datetime import UTC, datetime -from types import SimpleNamespace from typing import Any, Protocol from unittest.mock import MagicMock, patch @@ -17,13 +16,26 @@ import pytest from sqlalchemy.orm import Session from core.workflow.file_reference import build_file_reference +from core.workflow.nodes.agent_v2.binding_resolver import WorkflowAgentBindingBundle from graphon.enums import WorkflowExecutionStatus, WorkflowNodeExecutionStatus +from models.agent import ( + Agent, + AgentConfigSnapshot, + AgentScope, + AgentSource, + AgentStatus, + WorkflowAgentBindingType, + WorkflowAgentNodeBinding, +) from models.agent_config_entities import ( + AgentSoulConfig, DeclaredArrayItem, DeclaredOutputConfig, DeclaredOutputType, + WorkflowNodeJobConfig, ) from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom +from models.model import App from models.workflow import ( WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom, @@ -43,8 +55,8 @@ from services.workflow.node_output_inspector_service import ( # ────────────────────────────────────────────────────────────────────────────── -def _app_model(*, tenant_id: str = "tenant-1", app_id: str = "app-1"): - return SimpleNamespace(tenant_id=tenant_id, id=app_id) +def _app_model(*, tenant_id: str = "tenant-1", app_id: str = "app-1") -> App: + return App(id=app_id, tenant_id=tenant_id) def _workflow_run( @@ -168,16 +180,35 @@ def session_for(sqlite_session: Session) -> SessionFor: def _stub_binding_resolver(*, declared_outputs: list[DeclaredOutputConfig]): """Build a fake ``WorkflowAgentBindingResolver`` whose ``.resolve`` returns a binding with ``node_job_config_dict.declared_outputs``.""" - binding = SimpleNamespace( + binding = WorkflowAgentNodeBinding( id="binding-1", - node_job_config_dict={ - "workflow_prompt": "stub", - "declared_outputs": [o.model_dump() for o in declared_outputs], - }, + tenant_id="tenant-1", + app_id="app-1", + workflow_id="workflow-1", + workflow_version="draft", + node_id="agent-node-1", + binding_type=WorkflowAgentBindingType.INLINE_AGENT, + agent_id="agent-1", + current_snapshot_id="snapshot-1", + node_job_config=WorkflowNodeJobConfig(workflow_prompt="stub", declared_outputs=declared_outputs), + ) + agent = Agent( + id="agent-1", + tenant_id="tenant-1", + name="Agent", + scope=AgentScope.WORKFLOW_ONLY, + source=AgentSource.WORKFLOW, + status=AgentStatus.ACTIVE, + ) + snapshot = AgentConfigSnapshot( + id="snapshot-1", + tenant_id="tenant-1", + agent_id="agent-1", + version=1, + config_snapshot=AgentSoulConfig(), ) - bundle = SimpleNamespace(binding=binding, agent=None, snapshot=None) resolver = MagicMock() - resolver.resolve.return_value = bundle + resolver.resolve.return_value = WorkflowAgentBindingBundle(binding=binding, agent=agent, snapshot=snapshot) return resolver