test: use real node variable pools and lifecycle events (#43179)
This commit is contained in:
@@ -5,7 +5,12 @@ import pytest
|
||||
from core.app.entities.app_invoke_entities import DifyRunContext, InvokeFrom, UserFrom
|
||||
from core.workflow.nodes.agent_v2 import workspace_retirement_layer as layer_module
|
||||
from core.workflow.nodes.agent_v2.workspace_retirement_layer import WorkflowAgentWorkspaceRetirementLayer
|
||||
from core.workflow.system_variables import build_system_variables
|
||||
from graphon.enums import BuiltinNodeTypes
|
||||
from graphon.graph_engine.command_channels import InMemoryChannel
|
||||
from graphon.graph_events import GraphRunSucceededEvent, NodeRunStartedEvent
|
||||
from graphon.runtime import GraphRuntimeState, ReadOnlyGraphRuntimeStateWrapper, VariablePool
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
||||
|
||||
def _run_context() -> DifyRunContext:
|
||||
@@ -18,7 +23,21 @@ def _run_context() -> DifyRunContext:
|
||||
)
|
||||
|
||||
|
||||
def test_terminal_event_retires_workflow_workspace(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@pytest.fixture
|
||||
def runtime_state() -> ReadOnlyGraphRuntimeStateWrapper:
|
||||
return ReadOnlyGraphRuntimeStateWrapper(
|
||||
GraphRuntimeState(
|
||||
variable_pool=VariablePool.from_bootstrap(
|
||||
system_variables=build_system_variables(workflow_execution_id="workflow-run-1")
|
||||
),
|
||||
start_at=0,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_terminal_event_retires_workflow_workspace(
|
||||
monkeypatch: pytest.MonkeyPatch, runtime_state: ReadOnlyGraphRuntimeStateWrapper
|
||||
) -> None:
|
||||
store = MagicMock()
|
||||
events: list[str] = []
|
||||
store.retire_workflow_run.side_effect = lambda **_kwargs: events.append("retire") or ["workspace-1"]
|
||||
@@ -26,10 +45,9 @@ def test_terminal_event_retires_workflow_workspace(monkeypatch: pytest.MonkeyPat
|
||||
monkeypatch.setattr(layer_module, "WorkflowAgentWorkspaceStore", MagicMock(return_value=store))
|
||||
monkeypatch.setattr(layer_module, "enqueue_agent_resource_collection", enqueue)
|
||||
layer = WorkflowAgentWorkspaceRetirementLayer(dify_run_context=_run_context())
|
||||
layer.initialize(MagicMock(), MagicMock())
|
||||
monkeypatch.setattr(layer_module, "get_system_text", lambda *_: "workflow-run-1")
|
||||
layer.initialize(runtime_state, InMemoryChannel())
|
||||
|
||||
layer.on_event(MagicMock(spec=GraphRunSucceededEvent))
|
||||
layer.on_event(GraphRunSucceededEvent())
|
||||
|
||||
store.retire_workflow_run.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
@@ -45,12 +63,22 @@ def test_non_terminal_event_does_not_retire_workspace(monkeypatch: pytest.Monkey
|
||||
monkeypatch.setattr(layer_module, "WorkflowAgentWorkspaceStore", MagicMock(return_value=store))
|
||||
layer = WorkflowAgentWorkspaceRetirementLayer(dify_run_context=_run_context())
|
||||
|
||||
layer.on_event(MagicMock(spec=NodeRunStartedEvent))
|
||||
layer.on_event(
|
||||
NodeRunStartedEvent(
|
||||
id="execution-1",
|
||||
node_id="node-1",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
node_title="Start",
|
||||
start_at=naive_utc_now(),
|
||||
)
|
||||
)
|
||||
|
||||
store.retire_workflow_run.assert_not_called()
|
||||
|
||||
|
||||
def test_terminal_retirement_failure_does_not_replace_terminal_event(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_terminal_retirement_failure_does_not_replace_terminal_event(
|
||||
monkeypatch: pytest.MonkeyPatch, runtime_state: ReadOnlyGraphRuntimeStateWrapper
|
||||
) -> None:
|
||||
store = MagicMock()
|
||||
store.retire_workflow_run.side_effect = RuntimeError("database unavailable")
|
||||
log_exception = MagicMock()
|
||||
@@ -59,10 +87,9 @@ def test_terminal_retirement_failure_does_not_replace_terminal_event(monkeypatch
|
||||
monkeypatch.setattr(layer_module.logger, "exception", log_exception)
|
||||
monkeypatch.setattr(layer_module, "enqueue_agent_resource_collection", enqueue)
|
||||
layer = WorkflowAgentWorkspaceRetirementLayer(dify_run_context=_run_context())
|
||||
layer.initialize(MagicMock(), MagicMock())
|
||||
monkeypatch.setattr(layer_module, "get_system_text", lambda *_: "workflow-run-1")
|
||||
layer.initialize(runtime_state, InMemoryChannel())
|
||||
|
||||
layer.on_event(MagicMock(spec=GraphRunSucceededEvent))
|
||||
layer.on_event(GraphRunSucceededEvent())
|
||||
|
||||
log_exception.assert_called_once_with(
|
||||
"Failed to retire Workflow Agent Workspaces",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY
|
||||
@@ -7,7 +5,7 @@ from graphon.entities import GraphInitParams
|
||||
from graphon.enums import WorkflowNodeExecutionStatus
|
||||
from graphon.nodes.list_operator.entities import ListOperatorNodeData
|
||||
from graphon.nodes.list_operator.node import ListOperatorNode
|
||||
from graphon.runtime import GraphRuntimeState
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
from graphon.variables import ArrayNumberSegment, ArrayStringSegment
|
||||
|
||||
|
||||
@@ -31,12 +29,9 @@ class TestListOperatorNode:
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def mock_graph_runtime_state(self):
|
||||
"""Create mock GraphRuntimeState."""
|
||||
mock_state = MagicMock(spec=GraphRuntimeState)
|
||||
mock_variable_pool = MagicMock()
|
||||
mock_state.variable_pool = mock_variable_pool
|
||||
return mock_state
|
||||
def graph_runtime_state(self):
|
||||
"""Create graph state backed by the real variable pool."""
|
||||
return GraphRuntimeState(variable_pool=VariablePool(), start_at=0)
|
||||
|
||||
@pytest.fixture
|
||||
def graph_init_params(self):
|
||||
@@ -57,15 +52,15 @@ class TestListOperatorNode:
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def list_operator_node_factory(self, graph_init_params, mock_graph_runtime_state):
|
||||
def list_operator_node_factory(self, graph_init_params, graph_runtime_state):
|
||||
"""Factory fixture for creating ListOperatorNode instances."""
|
||||
|
||||
def _create_node(config, mock_variable):
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_variable
|
||||
def _create_node(config, variable):
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
return self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
return _create_node
|
||||
@@ -80,15 +75,15 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["apple", "banana", "cherry"])
|
||||
node = list_operator_node_factory(config, mock_var)
|
||||
variable = ArrayStringSegment(value=["apple", "banana", "cherry"])
|
||||
node = list_operator_node_factory(config, variable)
|
||||
|
||||
result = node._run()
|
||||
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["apple", "banana", "cherry"]
|
||||
|
||||
def test_run_with_empty_array(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_empty_array(self, graph_runtime_state, graph_init_params):
|
||||
"""Test with empty array."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -98,13 +93,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=[])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=[])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -114,7 +109,7 @@ class TestListOperatorNode:
|
||||
assert result.outputs["first_record"] is None
|
||||
assert result.outputs["last_record"] is None
|
||||
|
||||
def test_run_with_filter_contains(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_filter_contains(self, graph_runtime_state, graph_init_params):
|
||||
"""Test filter with contains condition."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -124,13 +119,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["apple", "banana", "pineapple", "cherry"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["apple", "banana", "pineapple", "cherry"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -138,7 +133,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["apple", "pineapple"]
|
||||
|
||||
def test_run_with_filter_not_contains(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_filter_not_contains(self, graph_runtime_state, graph_init_params):
|
||||
"""Test filter with not contains condition."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -148,13 +143,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["apple", "banana", "pineapple", "cherry"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["apple", "banana", "pineapple", "cherry"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -162,7 +157,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["banana", "cherry"]
|
||||
|
||||
def test_run_with_number_filter_greater_than(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_number_filter_greater_than(self, graph_runtime_state, graph_init_params):
|
||||
"""Test filter with greater than condition on numbers."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -172,13 +167,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayNumberSegment(value=[1, 3, 5, 7, 9, 11])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayNumberSegment(value=[1, 3, 5, 7, 9, 11])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -186,7 +181,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == [7, 9, 11]
|
||||
|
||||
def test_run_with_order_ascending(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_order_ascending(self, graph_runtime_state, graph_init_params):
|
||||
"""Test ordering in ascending order."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -199,13 +194,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["cherry", "apple", "banana"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["cherry", "apple", "banana"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -213,7 +208,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["apple", "banana", "cherry"]
|
||||
|
||||
def test_run_with_order_descending(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_order_descending(self, graph_runtime_state, graph_init_params):
|
||||
"""Test ordering in descending order."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -226,13 +221,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["cherry", "apple", "banana"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["cherry", "apple", "banana"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -240,7 +235,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["cherry", "banana", "apple"]
|
||||
|
||||
def test_run_with_limit(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_limit(self, graph_runtime_state, graph_init_params):
|
||||
"""Test with limit enabled."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -253,13 +248,13 @@ class TestListOperatorNode:
|
||||
},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["apple", "banana", "cherry", "date"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["apple", "banana", "cherry", "date"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -267,7 +262,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["apple", "banana"]
|
||||
|
||||
def test_run_with_filter_order_and_limit(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_filter_order_and_limit(self, graph_runtime_state, graph_init_params):
|
||||
"""Test with filter, order, and limit combined."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -283,13 +278,13 @@ class TestListOperatorNode:
|
||||
},
|
||||
}
|
||||
|
||||
mock_var = ArrayNumberSegment(value=[1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayNumberSegment(value=[1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -297,7 +292,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == [9, 8, 7]
|
||||
|
||||
def test_run_with_variable_not_found(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_variable_not_found(self, graph_runtime_state, graph_init_params):
|
||||
"""Test when variable is not found."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -307,12 +302,10 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = None
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -320,7 +313,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.FAILED
|
||||
assert "Variable not found" in result.error
|
||||
|
||||
def test_run_with_first_and_last_record(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_first_and_last_record(self, graph_runtime_state, graph_init_params):
|
||||
"""Test first_record and last_record outputs."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -330,13 +323,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["first", "middle", "last"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["first", "middle", "last"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -345,7 +338,7 @@ class TestListOperatorNode:
|
||||
assert result.outputs["first_record"] == "first"
|
||||
assert result.outputs["last_record"] == "last"
|
||||
|
||||
def test_run_with_filter_startswith(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_filter_startswith(self, graph_runtime_state, graph_init_params):
|
||||
"""Test filter with startswith condition."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -355,13 +348,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["apple", "application", "banana", "apricot"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["apple", "application", "banana", "apricot"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -369,7 +362,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["apple", "application"]
|
||||
|
||||
def test_run_with_filter_endswith(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_filter_endswith(self, graph_runtime_state, graph_init_params):
|
||||
"""Test filter with endswith condition."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -379,13 +372,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayStringSegment(value=["apple", "banana", "pineapple", "table"])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayStringSegment(value=["apple", "banana", "pineapple", "table"])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -393,7 +386,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == ["apple", "pineapple", "table"]
|
||||
|
||||
def test_run_with_number_filter_equals(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_number_filter_equals(self, graph_runtime_state, graph_init_params):
|
||||
"""Test number filter with equals condition."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -403,13 +396,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayNumberSegment(value=[1, 3, 5, 5, 7, 9])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayNumberSegment(value=[1, 3, 5, 5, 7, 9])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -417,7 +410,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == [5, 5]
|
||||
|
||||
def test_run_with_number_filter_not_equals(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_number_filter_not_equals(self, graph_runtime_state, graph_init_params):
|
||||
"""Test number filter with not equals condition."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -427,13 +420,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayNumberSegment(value=[1, 3, 5, 7, 9])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayNumberSegment(value=[1, 3, 5, 7, 9])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
@@ -441,7 +434,7 @@ class TestListOperatorNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["result"].value == [1, 3, 7, 9]
|
||||
|
||||
def test_run_with_number_order_ascending(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_number_order_ascending(self, graph_runtime_state, graph_init_params):
|
||||
"""Test number ordering in ascending order."""
|
||||
config = {
|
||||
"title": "Test",
|
||||
@@ -454,13 +447,13 @@ class TestListOperatorNode:
|
||||
"limit": {"enabled": False},
|
||||
}
|
||||
|
||||
mock_var = ArrayNumberSegment(value=[9, 3, 7, 1, 5])
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_var
|
||||
variable = ArrayNumberSegment(value=[9, 3, 7, 1, 5])
|
||||
graph_runtime_state.variable_pool.add(config["variable"], variable)
|
||||
|
||||
node = self._build_node(
|
||||
data=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
)
|
||||
|
||||
result = node._run()
|
||||
|
||||
@@ -4,11 +4,10 @@ import pytest
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
|
||||
from graphon.enums import WorkflowNodeExecutionStatus
|
||||
from graphon.graph import Graph
|
||||
from graphon.nodes.base.entities import VariableSelector
|
||||
from graphon.nodes.template_transform.entities import TemplateTransformNodeData
|
||||
from graphon.nodes.template_transform.template_transform_node import TemplateTransformNode
|
||||
from graphon.runtime import GraphRuntimeState
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
from graphon.template_rendering import TemplateRenderError
|
||||
from tests.workflow_test_utils import build_test_graph_init_params
|
||||
|
||||
@@ -39,21 +38,13 @@ class TestTemplateTransformNode:
|
||||
"""Comprehensive test suite for TemplateTransformNode."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_graph_runtime_state(self):
|
||||
"""Create a mock GraphRuntimeState with variable pool."""
|
||||
mock_state = MagicMock(spec=GraphRuntimeState)
|
||||
mock_variable_pool = MagicMock()
|
||||
mock_state.variable_pool = mock_variable_pool
|
||||
return mock_state
|
||||
|
||||
@pytest.fixture
|
||||
def mock_graph(self):
|
||||
"""Create a mock Graph (kept for backward compat in other tests)."""
|
||||
return MagicMock(spec=Graph)
|
||||
def graph_runtime_state(self) -> GraphRuntimeState:
|
||||
"""Create graph state with real variable storage and conversion."""
|
||||
return GraphRuntimeState(variable_pool=VariablePool(), start_at=0)
|
||||
|
||||
@pytest.fixture
|
||||
def graph_init_params(self):
|
||||
"""Create a mock GraphInitParams."""
|
||||
"""Create validated graph initialization parameters."""
|
||||
return build_test_graph_init_params(
|
||||
workflow_id="test_workflow",
|
||||
graph_config={},
|
||||
@@ -82,7 +73,7 @@ class TestTemplateTransformNode:
|
||||
def test_node_initialization_rejects_non_positive_max_output_length(
|
||||
self,
|
||||
basic_node_data,
|
||||
mock_graph_runtime_state,
|
||||
graph_runtime_state,
|
||||
graph_init_params,
|
||||
max_output_length,
|
||||
):
|
||||
@@ -92,24 +83,22 @@ class TestTemplateTransformNode:
|
||||
_build_template_transform_node(
|
||||
node_data=basic_node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
max_output_length=max_output_length,
|
||||
)
|
||||
|
||||
def test_run_simple_template(self, basic_node_data, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_simple_template(self, basic_node_data, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with simple template transformation using injected renderer."""
|
||||
# Setup mock variable pool
|
||||
mock_name_value = MagicMock()
|
||||
mock_name_value.to_object.return_value = "Alice"
|
||||
mock_age_value = MagicMock()
|
||||
mock_age_value.to_object.return_value = 30
|
||||
name_value = "Alice"
|
||||
age_value = 30
|
||||
|
||||
variable_map = {
|
||||
("sys", "user_name"): mock_name_value,
|
||||
("sys", "user_age"): mock_age_value,
|
||||
("sys", "user_name"): name_value,
|
||||
("sys", "user_age"): age_value,
|
||||
}
|
||||
mock_graph_runtime_state.variable_pool.get.side_effect = lambda selector: variable_map.get(tuple(selector))
|
||||
for selector, value in variable_map.items():
|
||||
graph_runtime_state.variable_pool.add(selector, value)
|
||||
|
||||
# Setup mock renderer
|
||||
mock_renderer = MagicMock()
|
||||
@@ -118,7 +107,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=basic_node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -129,7 +118,7 @@ class TestTemplateTransformNode:
|
||||
assert result.inputs["name"] == "Alice"
|
||||
assert result.inputs["age"] == 30
|
||||
|
||||
def test_run_with_none_values(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_none_values(self, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with None variable values."""
|
||||
node_data = {
|
||||
"title": "Test",
|
||||
@@ -137,15 +126,13 @@ class TestTemplateTransformNode:
|
||||
"template": "Value: {{ value }}",
|
||||
}
|
||||
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = None
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "Value: "
|
||||
|
||||
node = _build_template_transform_node(
|
||||
node_data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -154,9 +141,10 @@ class TestTemplateTransformNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.inputs["value"] is None
|
||||
|
||||
def test_run_with_render_error(self, basic_node_data, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_render_error(self, basic_node_data, graph_runtime_state, graph_init_params):
|
||||
"""Test _run when template rendering fails."""
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = MagicMock()
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_name"], "Alice")
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_age"], 30)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.side_effect = TemplateRenderError("Template syntax error")
|
||||
@@ -164,7 +152,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=basic_node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -173,9 +161,10 @@ class TestTemplateTransformNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.FAILED
|
||||
assert "Template syntax error" in result.error
|
||||
|
||||
def test_run_output_length_exceeds_limit(self, basic_node_data, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_output_length_exceeds_limit(self, basic_node_data, graph_runtime_state, graph_init_params):
|
||||
"""Test _run when output exceeds maximum length."""
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = MagicMock()
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_name"], "Alice")
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_age"], 30)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "This is a very long output that exceeds the limit"
|
||||
@@ -183,7 +172,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=basic_node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
max_output_length=10,
|
||||
)
|
||||
@@ -193,10 +182,9 @@ class TestTemplateTransformNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.FAILED
|
||||
assert "Output length exceeds" in result.error
|
||||
|
||||
def test_run_output_length_equal_to_limit_succeeds(
|
||||
self, basic_node_data, mock_graph_runtime_state, graph_init_params
|
||||
):
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = MagicMock()
|
||||
def test_run_output_length_equal_to_limit_succeeds(self, basic_node_data, graph_runtime_state, graph_init_params):
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_name"], "Alice")
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_age"], 30)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "1234567890"
|
||||
@@ -204,7 +192,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=basic_node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
max_output_length=10,
|
||||
)
|
||||
@@ -214,7 +202,7 @@ class TestTemplateTransformNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["output"] == "1234567890"
|
||||
|
||||
def test_run_with_complex_jinja2_template(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_complex_jinja2_template(self, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with complex Jinja2 template including loops and conditions."""
|
||||
node_data = {
|
||||
"title": "Complex Template",
|
||||
@@ -228,16 +216,15 @@ class TestTemplateTransformNode:
|
||||
),
|
||||
}
|
||||
|
||||
mock_items = MagicMock()
|
||||
mock_items.to_object.return_value = ["apple", "banana", "orange"]
|
||||
mock_show_total = MagicMock()
|
||||
mock_show_total.to_object.return_value = True
|
||||
items = ["apple", "banana", "orange"]
|
||||
show_total = True
|
||||
|
||||
variable_map = {
|
||||
("sys", "items"): mock_items,
|
||||
("sys", "show_total"): mock_show_total,
|
||||
("sys", "items"): items,
|
||||
("sys", "show_total"): show_total,
|
||||
}
|
||||
mock_graph_runtime_state.variable_pool.get.side_effect = lambda selector: variable_map.get(tuple(selector))
|
||||
for selector, value in variable_map.items():
|
||||
graph_runtime_state.variable_pool.add(selector, value)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "apple, banana, orange (Total: 3)"
|
||||
@@ -245,7 +232,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -337,7 +324,7 @@ class TestTemplateTransformNode:
|
||||
|
||||
assert mapping == {"node_123.var1": ["sys", "input1"]}
|
||||
|
||||
def test_run_with_empty_variables(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_empty_variables(self, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with no variables (static template)."""
|
||||
node_data = {
|
||||
"title": "Static Template",
|
||||
@@ -351,7 +338,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -361,7 +348,7 @@ class TestTemplateTransformNode:
|
||||
assert result.outputs["output"] == "This is a static message."
|
||||
assert result.inputs == {}
|
||||
|
||||
def test_run_with_numeric_values(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_numeric_values(self, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with numeric variable values."""
|
||||
node_data = {
|
||||
"title": "Numeric Template",
|
||||
@@ -372,16 +359,15 @@ class TestTemplateTransformNode:
|
||||
"template": "Total: ${{ price * quantity }}",
|
||||
}
|
||||
|
||||
mock_price = MagicMock()
|
||||
mock_price.to_object.return_value = 10.5
|
||||
mock_quantity = MagicMock()
|
||||
mock_quantity.to_object.return_value = 3
|
||||
price = 10.5
|
||||
quantity = 3
|
||||
|
||||
variable_map = {
|
||||
("sys", "price"): mock_price,
|
||||
("sys", "quantity"): mock_quantity,
|
||||
("sys", "price"): price,
|
||||
("sys", "quantity"): quantity,
|
||||
}
|
||||
mock_graph_runtime_state.variable_pool.get.side_effect = lambda selector: variable_map.get(tuple(selector))
|
||||
for selector, value in variable_map.items():
|
||||
graph_runtime_state.variable_pool.add(selector, value)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "Total: $31.5"
|
||||
@@ -389,7 +375,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -398,7 +384,7 @@ class TestTemplateTransformNode:
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert result.outputs["output"] == "Total: $31.5"
|
||||
|
||||
def test_run_with_dict_values(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_dict_values(self, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with dictionary variable values."""
|
||||
node_data = {
|
||||
"title": "Dict Template",
|
||||
@@ -406,10 +392,9 @@ class TestTemplateTransformNode:
|
||||
"template": "Name: {{ user.name }}, Email: {{ user.email }}",
|
||||
}
|
||||
|
||||
mock_user = MagicMock()
|
||||
mock_user.to_object.return_value = {"name": "John Doe", "email": "john@example.com"}
|
||||
user = {"name": "John Doe", "email": "john@example.com"}
|
||||
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_user
|
||||
graph_runtime_state.variable_pool.add(["sys", "user_data"], user)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "Name: John Doe, Email: john@example.com"
|
||||
@@ -417,7 +402,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
@@ -427,7 +412,7 @@ class TestTemplateTransformNode:
|
||||
assert "John Doe" in result.outputs["output"]
|
||||
assert "john@example.com" in result.outputs["output"]
|
||||
|
||||
def test_run_with_list_values(self, mock_graph_runtime_state, graph_init_params):
|
||||
def test_run_with_list_values(self, graph_runtime_state, graph_init_params):
|
||||
"""Test _run with list variable values."""
|
||||
node_data = {
|
||||
"title": "List Template",
|
||||
@@ -435,10 +420,9 @@ class TestTemplateTransformNode:
|
||||
"template": "Tags: {% for tag in tags %}#{{ tag }} {% endfor %}",
|
||||
}
|
||||
|
||||
mock_tags = MagicMock()
|
||||
mock_tags.to_object.return_value = ["python", "ai", "workflow"]
|
||||
tags = ["python", "ai", "workflow"]
|
||||
|
||||
mock_graph_runtime_state.variable_pool.get.return_value = mock_tags
|
||||
graph_runtime_state.variable_pool.add(["sys", "tags"], tags)
|
||||
|
||||
mock_renderer = MagicMock()
|
||||
mock_renderer.render_template.return_value = "Tags: #python #ai #workflow "
|
||||
@@ -446,7 +430,7 @@ class TestTemplateTransformNode:
|
||||
node = _build_template_transform_node(
|
||||
node_data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=mock_graph_runtime_state,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
jinja2_template_renderer=mock_renderer,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user