[BREAKING] Python: Refactor SharedState to State with sync methods and superstep caching (#3667)

* Refactor SharedState to State with sync methods and superstep caching

* Fixes

* Address PR feedback

* Remove dead links

* Fix lab test import
This commit is contained in:
Evan Mattson
2026-02-05 01:42:52 +00:00
committed by GitHub
parent 4e25917644
commit 10afb86213
48 changed files with 1971 additions and 1724 deletions
@@ -16,7 +16,7 @@ Coverage includes:
- String interpolation: {Variable.Path}
"""
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import MagicMock
import pytest
@@ -29,123 +29,125 @@ class TestPowerFxBuiltinFunctions:
"""Test PowerFx built-in functions used in YAML workflows."""
@pytest.fixture
def mock_shared_state(self):
"""Create a mock shared state with async get/set methods."""
shared_state = MagicMock()
shared_state._data = {}
def mock_state(self):
"""Create a mock state with sync get/set methods."""
state = MagicMock()
state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
def mock_has(key):
return key in state._data
async def test_concat_simple(self, mock_shared_state):
state.get = MagicMock(side_effect=mock_get)
state.set = MagicMock(side_effect=mock_set)
state.has = MagicMock(side_effect=mock_has)
return state
async def test_concat_simple(self, mock_state):
"""Test Concat function with simple strings."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Concat("Nice to meet you, ", Local.userName, "!")
await state.set("Local.userName", "Alice")
result = await state.eval('=Concat("Nice to meet you, ", Local.userName, "!")')
state.set("Local.userName", "Alice")
result = state.eval('=Concat("Nice to meet you, ", Local.userName, "!")')
assert result == "Nice to meet you, Alice!"
async def test_concat_multiple_args(self, mock_shared_state):
async def test_concat_multiple_args(self, mock_state):
"""Test Concat with multiple arguments."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Concat(Local.greeting, ", ", Local.name, "!")
await state.set("Local.greeting", "Hello")
await state.set("Local.name", "World")
result = await state.eval('=Concat(Local.greeting, ", ", Local.name, "!")')
state.set("Local.greeting", "Hello")
state.set("Local.name", "World")
result = state.eval('=Concat(Local.greeting, ", ", Local.name, "!")')
assert result == "Hello, World!"
async def test_concat_with_local_namespace(self, mock_shared_state):
async def test_concat_with_local_namespace(self, mock_state):
"""Test Concat using Local.* namespace (maps to Local.*)."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Concat("Starting math coaching session for: ", Local.Problem)
await state.set("Local.Problem", "2 + 2")
result = await state.eval('=Concat("Starting math coaching session for: ", Local.Problem)')
state.set("Local.Problem", "2 + 2")
result = state.eval('=Concat("Starting math coaching session for: ", Local.Problem)')
assert result == "Starting math coaching session for: 2 + 2"
async def test_if_with_isblank(self, mock_shared_state):
async def test_if_with_isblank(self, mock_state):
"""Test If function with IsBlank."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize({"name": ""})
state = DeclarativeWorkflowState(mock_state)
state.initialize({"name": ""})
# From YAML: =If(IsBlank(inputs.name), "World", inputs.name)
# When input is blank
result = await state.eval('=If(IsBlank(Workflow.Inputs.name), "World", Workflow.Inputs.name)')
result = state.eval('=If(IsBlank(Workflow.Inputs.name), "World", Workflow.Inputs.name)')
assert result == "World"
# When input is provided
await state.initialize({"name": "Alice"})
result = await state.eval('=If(IsBlank(Workflow.Inputs.name), "World", Workflow.Inputs.name)')
state.initialize({"name": "Alice"})
result = state.eval('=If(IsBlank(Workflow.Inputs.name), "World", Workflow.Inputs.name)')
assert result == "Alice"
async def test_not_function(self, mock_shared_state):
async def test_not_function(self, mock_state):
"""Test Not function."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Not(Local.EscalationParameters.IsComplete)
await state.set("Local.EscalationParameters", {"IsComplete": False})
result = await state.eval("=Not(Local.EscalationParameters.IsComplete)")
state.set("Local.EscalationParameters", {"IsComplete": False})
result = state.eval("=Not(Local.EscalationParameters.IsComplete)")
assert result is True
await state.set("Local.EscalationParameters", {"IsComplete": True})
result = await state.eval("=Not(Local.EscalationParameters.IsComplete)")
state.set("Local.EscalationParameters", {"IsComplete": True})
result = state.eval("=Not(Local.EscalationParameters.IsComplete)")
assert result is False
async def test_or_function(self, mock_shared_state):
async def test_or_function(self, mock_state):
"""Test Or function."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Or(Local.feeling = "great", Local.feeling = "good")
await state.set("Local.feeling", "great")
result = await state.eval('=Or(Local.feeling = "great", Local.feeling = "good")')
state.set("Local.feeling", "great")
result = state.eval('=Or(Local.feeling = "great", Local.feeling = "good")')
assert result is True
await state.set("Local.feeling", "good")
result = await state.eval('=Or(Local.feeling = "great", Local.feeling = "good")')
state.set("Local.feeling", "good")
result = state.eval('=Or(Local.feeling = "great", Local.feeling = "good")')
assert result is True
await state.set("Local.feeling", "bad")
result = await state.eval('=Or(Local.feeling = "great", Local.feeling = "good")')
state.set("Local.feeling", "bad")
result = state.eval('=Or(Local.feeling = "great", Local.feeling = "good")')
assert result is False
async def test_upper_function(self, mock_shared_state):
async def test_upper_function(self, mock_state):
"""Test Upper function."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Upper(System.LastMessage.Text)
await state.set("System.LastMessage", {"Text": "hello world"})
result = await state.eval("=Upper(System.LastMessage.Text)")
state.set("System.LastMessage", {"Text": "hello world"})
result = state.eval("=Upper(System.LastMessage.Text)")
assert result == "HELLO WORLD"
async def test_find_function(self, mock_shared_state):
async def test_find_function(self, mock_state):
"""Test Find function."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =!IsBlank(Find("CONGRATULATIONS", Upper(Local.TeacherResponse)))
await state.set("Local.TeacherResponse", "CONGRATULATIONS! You solved it!")
result = await state.eval('=Not(IsBlank(Find("CONGRATULATIONS", Upper(Local.TeacherResponse))))')
state.set("Local.TeacherResponse", "CONGRATULATIONS! You solved it!")
result = state.eval('=Not(IsBlank(Find("CONGRATULATIONS", Upper(Local.TeacherResponse))))')
assert result is True
await state.set("Local.TeacherResponse", "Try again")
result = await state.eval('=Not(IsBlank(Find("CONGRATULATIONS", Upper(Local.TeacherResponse))))')
state.set("Local.TeacherResponse", "Try again")
result = state.eval('=Not(IsBlank(Find("CONGRATULATIONS", Upper(Local.TeacherResponse))))')
assert result is False
@@ -153,55 +155,53 @@ class TestPowerFxSystemVariables:
"""Test System.* variable access."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_system_conversation_id(self, mock_shared_state):
async def test_system_conversation_id(self, mock_state):
"""Test System.ConversationId access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: conversationId: =System.ConversationId
await state.set("System.ConversationId", "conv-12345")
result = await state.eval("=System.ConversationId")
state.set("System.ConversationId", "conv-12345")
result = state.eval("=System.ConversationId")
assert result == "conv-12345"
async def test_system_last_message_text(self, mock_shared_state):
async def test_system_last_message_text(self, mock_state):
"""Test System.LastMessage.Text access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Upper(System.LastMessage.Text) <> "EXIT"
await state.set("System.LastMessage", {"Text": "Hello"})
result = await state.eval("=System.LastMessage.Text")
state.set("System.LastMessage", {"Text": "Hello"})
result = state.eval("=System.LastMessage.Text")
assert result == "Hello"
async def test_system_last_message_exit_check(self, mock_shared_state):
async def test_system_last_message_exit_check(self, mock_state):
"""Test the exit check pattern from YAML."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: when: =Upper(System.LastMessage.Text) <> "EXIT"
await state.set("System.LastMessage", {"Text": "hello"})
result = await state.eval('=Upper(System.LastMessage.Text) <> "EXIT"')
state.set("System.LastMessage", {"Text": "hello"})
result = state.eval('=Upper(System.LastMessage.Text) <> "EXIT"')
assert result is True
await state.set("System.LastMessage", {"Text": "exit"})
result = await state.eval('=Upper(System.LastMessage.Text) <> "EXIT"')
state.set("System.LastMessage", {"Text": "exit"})
result = state.eval('=Upper(System.LastMessage.Text) <> "EXIT"')
assert result is False
@@ -209,99 +209,95 @@ class TestPowerFxComparisonOperators:
"""Test comparison operators used in YAML workflows."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_less_than(self, mock_shared_state):
async def test_less_than(self, mock_state):
"""Test < operator."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: condition: =Local.age < 65
await state.set("Local.age", 30)
assert await state.eval("=Local.age < 65") is True
state.set("Local.age", 30)
assert state.eval("=Local.age < 65") is True
await state.set("Local.age", 70)
assert await state.eval("=Local.age < 65") is False
state.set("Local.age", 70)
assert state.eval("=Local.age < 65") is False
async def test_less_than_with_local(self, mock_shared_state):
async def test_less_than_with_local(self, mock_state):
"""Test < with Local namespace."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: condition: =Local.TurnCount < 4
await state.set("Local.TurnCount", 2)
assert await state.eval("=Local.TurnCount < 4") is True
state.set("Local.TurnCount", 2)
assert state.eval("=Local.TurnCount < 4") is True
await state.set("Local.TurnCount", 5)
assert await state.eval("=Local.TurnCount < 4") is False
state.set("Local.TurnCount", 5)
assert state.eval("=Local.TurnCount < 4") is False
async def test_equality(self, mock_shared_state):
async def test_equality(self, mock_state):
"""Test = equality operator."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Local.feeling = "great"
await state.set("Local.feeling", "great")
assert await state.eval('=Local.feeling = "great"') is True
state.set("Local.feeling", "great")
assert state.eval('=Local.feeling = "great"') is True
await state.set("Local.feeling", "bad")
assert await state.eval('=Local.feeling = "great"') is False
state.set("Local.feeling", "bad")
assert state.eval('=Local.feeling = "great"') is False
async def test_inequality(self, mock_shared_state):
async def test_inequality(self, mock_state):
"""Test <> inequality operator."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Upper(System.LastMessage.Text) <> "EXIT"
await state.set("Local.status", "active")
assert await state.eval('=Local.status <> "done"') is True
assert await state.eval('=Local.status <> "active"') is False
state.set("Local.status", "active")
assert state.eval('=Local.status <> "done"') is True
assert state.eval('=Local.status <> "active"') is False
class TestPowerFxArithmetic:
"""Test arithmetic operations."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_addition(self, mock_shared_state):
async def test_addition(self, mock_state):
"""Test + operator."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: value: =Local.TurnCount + 1
await state.set("Local.TurnCount", 3)
result = await state.eval("=Local.TurnCount + 1")
state.set("Local.TurnCount", 3)
result = state.eval("=Local.TurnCount + 1")
assert result == 4
@@ -309,97 +305,95 @@ class TestPowerFxCustomFunctions:
"""Test custom functions (UserMessage, MessageText, AgentMessage)."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
@pytest.mark.asyncio
async def test_agent_message_function(self, mock_shared_state):
async def test_agent_message_function(self, mock_state):
"""Test AgentMessage function (.NET compatibility alias for AssistantMessage)."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From .NET YAML: messages: =AgentMessage(Local.Response)
await state.set("Local.Response", "Here is the analysis result")
result = await state.eval("=AgentMessage(Local.Response)")
state.set("Local.Response", "Here is the analysis result")
result = state.eval("=AgentMessage(Local.Response)")
assert isinstance(result, dict)
assert result["role"] == "assistant"
assert result["text"] == "Here is the analysis result"
@pytest.mark.asyncio
async def test_agent_message_with_empty_string(self, mock_shared_state):
async def test_agent_message_with_empty_string(self, mock_state):
"""Test AgentMessage with empty string."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
await state.set("Local.Response", "")
result = await state.eval("=AgentMessage(Local.Response)")
state.set("Local.Response", "")
result = state.eval("=AgentMessage(Local.Response)")
assert result["role"] == "assistant"
assert result["text"] == ""
@pytest.mark.asyncio
async def test_user_message_with_variable(self, mock_shared_state):
async def test_user_message_with_variable(self, mock_state):
"""Test UserMessage function with variable reference."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: messages: =UserMessage(Local.ServiceParameters.IssueDescription)
await state.set("Local.ServiceParameters", {"IssueDescription": "My computer won't boot"})
result = await state.eval("=UserMessage(Local.ServiceParameters.IssueDescription)")
state.set("Local.ServiceParameters", {"IssueDescription": "My computer won't boot"})
result = state.eval("=UserMessage(Local.ServiceParameters.IssueDescription)")
assert isinstance(result, dict)
assert result["role"] == "user"
assert result["text"] == "My computer won't boot"
async def test_user_message_with_simple_variable(self, mock_shared_state):
async def test_user_message_with_simple_variable(self, mock_state):
"""Test UserMessage with simple variable."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: messages: =Local.Problem
await state.set("Local.Problem", "What is 2+2?")
result = await state.eval("=UserMessage(Local.Problem)")
state.set("Local.Problem", "What is 2+2?")
result = state.eval("=UserMessage(Local.Problem)")
assert result["role"] == "user"
assert result["text"] == "What is 2+2?"
async def test_message_text_with_list(self, mock_shared_state):
async def test_message_text_with_list(self, mock_state):
"""Test MessageText extracts text from message list."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
await state.set(
state.set(
"Local.messages",
[
{"role": "user", "text": "Hello"},
{"role": "assistant", "text": "Hi there!"},
],
)
result = await state.eval("=MessageText(Local.messages)")
result = state.eval("=MessageText(Local.messages)")
assert result == "Hi there!"
async def test_message_text_empty_list(self, mock_shared_state):
async def test_message_text_empty_list(self, mock_state):
"""Test MessageText with empty list returns empty string."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
await state.set("Local.messages", [])
result = await state.eval("=MessageText(Local.messages)")
state.set("Local.messages", [])
result = state.eval("=MessageText(Local.messages)")
assert result == ""
@@ -407,51 +401,49 @@ class TestPowerFxNestedVariables:
"""Test nested variable access patterns from YAML."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_nested_local_variable(self, mock_shared_state):
async def test_nested_local_variable(self, mock_state):
"""Test nested Local.* variable access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Local.ServiceParameters.IssueDescription
await state.set("Local.ServiceParameters", {"IssueDescription": "Screen is black"})
result = await state.eval("=Local.ServiceParameters.IssueDescription")
state.set("Local.ServiceParameters", {"IssueDescription": "Screen is black"})
result = state.eval("=Local.ServiceParameters.IssueDescription")
assert result == "Screen is black"
async def test_nested_routing_parameters(self, mock_shared_state):
async def test_nested_routing_parameters(self, mock_state):
"""Test RoutingParameters access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Local.RoutingParameters.TeamName
await state.set("Local.RoutingParameters", {"TeamName": "Windows Support"})
result = await state.eval("=Local.RoutingParameters.TeamName")
state.set("Local.RoutingParameters", {"TeamName": "Windows Support"})
result = state.eval("=Local.RoutingParameters.TeamName")
assert result == "Windows Support"
async def test_nested_ticket_parameters(self, mock_shared_state):
async def test_nested_ticket_parameters(self, mock_state):
"""Test TicketParameters access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: =Local.TicketParameters.TicketId
await state.set("Local.TicketParameters", {"TicketId": "TKT-12345"})
result = await state.eval("=Local.TicketParameters.TicketId")
state.set("Local.TicketParameters", {"TicketId": "TKT-12345"})
result = state.eval("=Local.TicketParameters.TicketId")
assert result == "TKT-12345"
@@ -459,39 +451,37 @@ class TestPowerFxUndefinedVariables:
"""Test graceful handling of undefined variables."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_undefined_local_variable_returns_none(self, mock_shared_state):
async def test_undefined_local_variable_returns_none(self, mock_state):
"""Test that undefined Local.* variables return None."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# Variable not set - should return None (not raise)
result = await state.eval("=Local.UndefinedVariable")
result = state.eval("=Local.UndefinedVariable")
assert result is None
async def test_undefined_nested_variable_returns_none(self, mock_shared_state):
async def test_undefined_nested_variable_returns_none(self, mock_state):
"""Test that undefined nested variables return None."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# Nested undefined variable
result = await state.eval("=Local.Something.Nested.Deep")
result = state.eval("=Local.Something.Nested.Deep")
assert result is None
@@ -499,41 +489,39 @@ class TestStringInterpolation:
"""Test string interpolation patterns."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_interpolate_local_variable(self, mock_shared_state):
async def test_interpolate_local_variable(self, mock_state):
"""Test {Local.Variable} interpolation."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: activity: "Created ticket #{Local.TicketParameters.TicketId}"
await state.set("Local.TicketParameters", {"TicketId": "TKT-999"})
result = await state.interpolate_string("Created ticket #{Local.TicketParameters.TicketId}")
state.set("Local.TicketParameters", {"TicketId": "TKT-999"})
result = state.interpolate_string("Created ticket #{Local.TicketParameters.TicketId}")
assert result == "Created ticket #TKT-999"
async def test_interpolate_routing_team(self, mock_shared_state):
async def test_interpolate_routing_team(self, mock_state):
"""Test routing team interpolation."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize()
state = DeclarativeWorkflowState(mock_state)
state.initialize()
# From YAML: activity: Routing to {Local.RoutingParameters.TeamName}
await state.set("Local.RoutingParameters", {"TeamName": "Linux Support"})
result = await state.interpolate_string("Routing to {Local.RoutingParameters.TeamName}")
state.set("Local.RoutingParameters", {"TeamName": "Linux Support"})
result = state.interpolate_string("Routing to {Local.RoutingParameters.TeamName}")
assert result == "Routing to Linux Support"
@@ -541,41 +529,39 @@ class TestWorkflowInputsAccess:
"""Test Workflow.Inputs access patterns."""
@pytest.fixture
def mock_shared_state(self):
def mock_state(self):
"""Create a mock shared state."""
shared_state = MagicMock()
shared_state._data = {}
mock_state = MagicMock()
mock_state._data = {}
async def mock_get(key):
if key not in shared_state._data:
raise KeyError(key)
return shared_state._data[key]
def mock_get(key, default=None):
return mock_state._data.get(key, default)
async def mock_set(key, value):
shared_state._data[key] = value
def mock_set(key, value):
mock_state._data[key] = value
shared_state.get = AsyncMock(side_effect=mock_get)
shared_state.set = AsyncMock(side_effect=mock_set)
return shared_state
mock_state.get = MagicMock(side_effect=mock_get)
mock_state.set = MagicMock(side_effect=mock_set)
return mock_state
async def test_inputs_name(self, mock_shared_state):
async def test_inputs_name(self, mock_state):
"""Test inputs.name access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize({"name": "Alice", "age": 25})
state = DeclarativeWorkflowState(mock_state)
state.initialize({"name": "Alice", "age": 25})
# .NET style (standard)
result = await state.eval("=Workflow.Inputs.name")
result = state.eval("=Workflow.Inputs.name")
assert result == "Alice"
# Also test inputs.name shorthand
result = await state.eval("=inputs.name")
result = state.eval("=inputs.name")
assert result == "Alice"
async def test_inputs_problem(self, mock_shared_state):
async def test_inputs_problem(self, mock_state):
"""Test inputs.problem access."""
state = DeclarativeWorkflowState(mock_shared_state)
await state.initialize({"problem": "What is 5 * 6?"})
state = DeclarativeWorkflowState(mock_state)
state.initialize({"problem": "What is 5 * 6?"})
# .NET style (standard)
result = await state.eval("=Workflow.Inputs.problem")
result = state.eval("=Workflow.Inputs.problem")
assert result == "What is 5 * 6?"