refactor: cleanup test files with fixtures; remove unused imports; remove unused variables

This commit is contained in:
2025-11-13 18:41:10 +05:30
parent 5fd1d213cd
commit 485084fbcc
23 changed files with 376 additions and 594 deletions
+5 -5
View File
@@ -64,7 +64,7 @@ class Agent(ABC):
self.output_stream = Subject()
# Track pending tasks for cleanup
self._pending_tasks: List[asyncio.Task] = []
self._pending_tasks: List[asyncio.Task[str]] = []
# Set up processing pipeline
self._setup_processing_pipeline()
@@ -75,10 +75,10 @@ class Agent(ABC):
"""Create a future and schedule the coroutine on the event loop."""
try:
loop = asyncio.get_running_loop()
except RuntimeError:
# No running loop - create a new one and set it
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
except RuntimeError as exc:
raise RuntimeError(
"No active event loop available."
) from exc
task = loop.create_task(self._process_wrapper(message_data))
self._pending_tasks.append(task)
# Remove completed tasks to prevent memory leak
+22 -29
View File
@@ -4,7 +4,6 @@ Comprehensive unit tests for langgraph graph module.
import asyncio
import tempfile
from enum import Enum
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch
@@ -90,6 +89,24 @@ def conditional_edge_structure():
return nodes, edges
@pytest.fixture
def cycle_graph_structure():
"""Fixture for a graph structure with cycles (start -> node1 -> node2 -> node1)."""
nodes = {
"start": NodeConfig(name="start", type=NodeType.START),
"node1": NodeConfig(name="node1", type=NodeType.AGENT),
"node2": NodeConfig(name="node2", type=NodeType.AGENT),
}
edges = [
Edge(source="start", target="node1"),
Edge(source="node1", target="node2"),
Edge(source="node2", target="node1"),
]
config = GraphConfig(name="test_graph", nodes=nodes, edges=edges)
graph = LangGraph(config)
return graph
@pytest.fixture
def node_stream_setup():
"""Fixture for setting up a graph with a single function node for stream subscription tests."""
@@ -288,21 +305,9 @@ class TestLangGraphAnalysis:
assert graph.has_cycles is False
def test_detect_cycles_with_cycles(self):
def test_detect_cycles_with_cycles(self, cycle_graph_structure):
"""Test cycle detection with cycles."""
nodes = {
"start": NodeConfig(name="start", type=NodeType.START),
"node1": NodeConfig(name="node1", type=NodeType.AGENT),
"node2": NodeConfig(name="node2", type=NodeType.AGENT),
}
edges = [
Edge(source="start", target="node1"),
Edge(source="node1", target="node2"),
Edge(source="node2", target="node1"), # Cycle
]
config = GraphConfig(name="test_graph", nodes=nodes, edges=edges)
graph = LangGraph(config)
graph = cycle_graph_structure
assert graph.has_cycles is True
@@ -453,21 +458,9 @@ class TestLangGraphAnalysis:
assert "node2" in reachable
assert "unreachable" not in reachable
def test_find_reachable_nodes_with_cycle(self):
def test_find_reachable_nodes_with_cycle(self, cycle_graph_structure):
"""Test finding reachable nodes when graph has cycles (node in visited branch)."""
nodes = {
"start": NodeConfig(name="start", type=NodeType.START),
"node1": NodeConfig(name="node1", type=NodeType.AGENT),
"node2": NodeConfig(name="node2", type=NodeType.AGENT),
}
edges = [
Edge(source="start", target="node1"),
Edge(source="node1", target="node2"),
Edge(source="node2", target="node1"), # Cycle back to node1
]
config = GraphConfig(name="test_graph", nodes=nodes, edges=edges)
graph = LangGraph(config)
graph = cycle_graph_structure
reachable = graph._find_reachable_nodes("start")
# All nodes should be reachable despite the cycle
+3 -3
View File
@@ -299,7 +299,7 @@ class TestStateManager:
manager = StateManager()
received_states = []
manager.state_stream.subscribe(lambda state: received_states.append(state))
manager.state_stream.subscribe(received_states.append)
manager.update_state({"current_node": "node1"})
@@ -341,7 +341,7 @@ class TestStateManager:
assert len(checkpoints) == 1
# Verify checkpoint content
with open(checkpoints[0], 'r') as f:
with open(checkpoints[0], 'r', encoding='utf-8') as f:
data = json.load(f)
assert data["state"]["current_node"] == "test_node"
@@ -397,7 +397,7 @@ class TestStateManager:
assert latest is not None
assert isinstance(latest, Path)
with open(latest, 'r') as f:
with open(latest, 'r', encoding='utf-8') as f:
data = json.load(f)
assert data["state"]["current_node"] == "node2"
+62 -151
View File
@@ -22,6 +22,12 @@ from cleveragents.reactive.route import RouteConfig, RouteType
from cleveragents.reactive.stream_router import StreamType
@pytest.fixture
def parser():
"""Fixture for ReactiveConfigParser instance."""
return ReactiveConfigParser()
class TestAgentConfig:
"""Test cases for AgentConfig dataclass."""
@@ -134,10 +140,8 @@ class TestReactiveConfig:
class TestReactiveConfigParserInit:
"""Test cases for ReactiveConfigParser initialization."""
def test_parser_init(self):
def test_parser_init(self, parser):
"""Test parser initialization."""
parser = ReactiveConfigParser()
assert parser.logger is not None
assert isinstance(parser.logger, logging.Logger)
@@ -145,19 +149,15 @@ class TestReactiveConfigParserInit:
class TestParseFiles:
"""Test cases for parse_files method."""
def test_parse_files_empty_list(self):
def test_parse_files_empty_list(self, parser):
"""Test parsing empty file list."""
parser = ReactiveConfigParser()
result = parser.parse_files([])
assert isinstance(result, ReactiveConfig)
def test_parse_files_single_file(self):
def test_parse_files_single_file(self, parser):
"""Test parsing single file."""
parser = ReactiveConfigParser()
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
yaml.dump({
"agents": {
"agent1": {
@@ -177,12 +177,10 @@ class TestParseFiles:
finally:
os.unlink(filepath)
def test_parse_files_multiple_files(self):
def test_parse_files_multiple_files(self, parser):
"""Test parsing multiple files."""
parser = ReactiveConfigParser()
# Create first file
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f1:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f1:
yaml.dump({
"agents": {
"agent1": {"type": "llm"}
@@ -192,7 +190,7 @@ class TestParseFiles:
filepath1 = Path(f1.name)
# Create second file
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f2:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f2:
yaml.dump({
"agents": {
"agent2": {"type": "tool"}
@@ -210,11 +208,9 @@ class TestParseFiles:
os.unlink(filepath1)
os.unlink(filepath2)
def test_parse_files_with_templates(self):
def test_parse_files_with_templates(self, parser):
"""Test parsing file with templates."""
parser = ReactiveConfigParser()
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
yaml.dump({
"templates": {
"agents": {
@@ -235,12 +231,10 @@ class TestParseFiles:
finally:
os.unlink(filepath)
def test_parse_files_with_jinja2_template_syntax(self):
def test_parse_files_with_jinja2_template_syntax(self, parser):
"""Test parsing file with Jinja2 template syntax ({{% or {{)."""
parser = ReactiveConfigParser()
# Create a file without Jinja2 syntax first to establish baseline
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
yaml.dump({
"agents": {
"test_agent": {
@@ -253,7 +247,7 @@ class TestParseFiles:
filepath1 = Path(f.name)
# Create a file WITH Jinja2 syntax
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
# Write a file with Jinja2 template syntax in comments (will be detected)
f.write("""
# This file uses Jinja2: {{ test }}
@@ -282,10 +276,8 @@ agents:
class TestInterpolateEnvVars:
"""Test cases for _interpolate_env_vars method."""
def test_interpolate_env_vars_string(self):
def test_interpolate_env_vars_string(self, parser):
"""Test interpolating environment variables in string."""
parser = ReactiveConfigParser()
os.environ["TEST_VAR"] = "test_value"
result = parser._interpolate_env_vars("${TEST_VAR}")
@@ -294,10 +286,8 @@ class TestInterpolateEnvVars:
del os.environ["TEST_VAR"]
def test_interpolate_env_vars_dict(self):
def test_interpolate_env_vars_dict(self, parser):
"""Test interpolating environment variables in dict."""
parser = ReactiveConfigParser()
os.environ["TEST_KEY"] = "test_value"
config = {"key": "${TEST_KEY}"}
@@ -307,10 +297,8 @@ class TestInterpolateEnvVars:
del os.environ["TEST_KEY"]
def test_interpolate_env_vars_list(self):
def test_interpolate_env_vars_list(self, parser):
"""Test interpolating environment variables in list."""
parser = ReactiveConfigParser()
os.environ["TEST_ITEM"] = "item_value"
config = ["${TEST_ITEM}", "other"]
@@ -321,70 +309,54 @@ class TestInterpolateEnvVars:
del os.environ["TEST_ITEM"]
def test_interpolate_env_vars_with_default(self):
def test_interpolate_env_vars_with_default(self, parser):
"""Test interpolating with default value."""
parser = ReactiveConfigParser()
result = parser._interpolate_env_vars("${NONEXISTENT_VAR:-default_value}")
# The actual result includes the ":-" separator in the output
assert "default_value" in result
def test_interpolate_env_vars_no_match(self):
def test_interpolate_env_vars_no_match(self, parser):
"""Test interpolating when no environment variable pattern."""
parser = ReactiveConfigParser()
result = parser._interpolate_env_vars("plain_string")
assert result == "plain_string"
def test_interpolate_env_vars_default_boolean_true(self):
def test_interpolate_env_vars_default_boolean_true(self, parser):
"""Test replace_env_var with default boolean value true."""
parser = ReactiveConfigParser()
result = parser._interpolate_env_vars("${NONEXISTENT_VAR:-true}")
# Should convert to boolean string
assert "true" in str(result).lower()
def test_interpolate_env_vars_default_boolean_false(self):
def test_interpolate_env_vars_default_boolean_false(self, parser):
"""Test replace_env_var with default boolean value false."""
parser = ReactiveConfigParser()
result = parser._interpolate_env_vars("${NONEXISTENT_VAR:-false}")
# Should convert to boolean string
assert "false" in str(result).lower()
def test_interpolate_env_vars_default_integer(self):
def test_interpolate_env_vars_default_integer(self, parser):
"""Test replace_env_var with default integer value."""
parser = ReactiveConfigParser()
result = parser._interpolate_env_vars("${NONEXISTENT_VAR:-42}")
# Should return the integer as string
assert "42" in str(result)
def test_interpolate_env_vars_default_float(self):
def test_interpolate_env_vars_default_float(self, parser):
"""Test replace_env_var with default float value."""
parser = ReactiveConfigParser()
result = parser._interpolate_env_vars("${NONEXISTENT_VAR:-3.14}")
# Should return the float as string
assert "3.14" in str(result)
def test_interpolate_env_vars_missing_no_default_error(self):
def test_interpolate_env_vars_missing_no_default_error(self, parser):
"""Test replace_env_var raises error when env var missing and no default."""
parser = ReactiveConfigParser()
with pytest.raises(ConfigurationError, match="Environment variable.*is not set"):
parser._interpolate_env_vars("${NONEXISTENT_VAR_NO_DEFAULT}")
def test_interpolate_env_vars_converts_true_string(self):
def test_interpolate_env_vars_converts_true_string(self, parser):
"""Test interpolation converts 'true' string to boolean."""
parser = ReactiveConfigParser()
# Set and interpolate a 'true' value
os.environ["BOOL_VAR"] = "true"
@@ -395,10 +367,8 @@ class TestInterpolateEnvVars:
del os.environ["BOOL_VAR"]
def test_interpolate_env_vars_converts_integer_string(self):
def test_interpolate_env_vars_converts_integer_string(self, parser):
"""Test interpolation converts integer string."""
parser = ReactiveConfigParser()
# Set and interpolate an integer value
os.environ["INT_VAR"] = "123"
@@ -410,10 +380,8 @@ class TestInterpolateEnvVars:
del os.environ["INT_VAR"]
def test_interpolate_env_vars_converts_float_string(self):
def test_interpolate_env_vars_converts_float_string(self, parser):
"""Test interpolation converts float string."""
parser = ReactiveConfigParser()
# Set and interpolate a float value
os.environ["FLOAT_VAR"] = "45.67"
@@ -429,10 +397,8 @@ class TestInterpolateEnvVars:
class TestMergeConfigs:
"""Test cases for _merge_configs method."""
def test_merge_configs_simple(self):
def test_merge_configs_simple(self, parser):
"""Test merging simple configs."""
parser = ReactiveConfigParser()
base = {"key1": "value1"}
new = {"key2": "value2"}
@@ -441,10 +407,8 @@ class TestMergeConfigs:
assert base["key1"] == "value1"
assert base["key2"] == "value2"
def test_merge_configs_nested(self):
def test_merge_configs_nested(self, parser):
"""Test merging nested configs."""
parser = ReactiveConfigParser()
base = {"nested": {"key1": "value1"}}
new = {"nested": {"key2": "value2"}}
@@ -453,10 +417,8 @@ class TestMergeConfigs:
assert base["nested"]["key1"] == "value1"
assert base["nested"]["key2"] == "value2"
def test_merge_configs_overwrite(self):
def test_merge_configs_overwrite(self, parser):
"""Test that new values overwrite old values."""
parser = ReactiveConfigParser()
base = {"key": "old_value"}
new = {"key": "new_value"}
@@ -464,10 +426,8 @@ class TestMergeConfigs:
assert base["key"] == "new_value"
def test_merge_configs_list_append(self):
def test_merge_configs_list_append(self, parser):
"""Test merging lists."""
parser = ReactiveConfigParser()
base = {"items": ["item1"]}
new = {"items": ["item2"]}
@@ -475,10 +435,8 @@ class TestMergeConfigs:
assert base["items"] == ["item1", "item2"]
def test_merge_configs_none_input(self):
def test_merge_configs_none_input(self, parser):
"""Test merging when new is None."""
parser = ReactiveConfigParser()
base = {"key1": "value1", "key2": "value2"}
original_base = base.copy()
@@ -491,10 +449,8 @@ class TestMergeConfigs:
class TestParseStreamRoute:
"""Test cases for _parse_stream_route method."""
def test_parse_stream_route_basic(self):
def test_parse_stream_route_basic(self, parser):
"""Test parsing basic stream route."""
parser = ReactiveConfigParser()
route_data = {
"type": "stream",
"stream_type": "cold",
@@ -507,10 +463,8 @@ class TestParseStreamRoute:
assert result.type == RouteType.STREAM
assert result.stream_type == StreamType.COLD
def test_parse_stream_route_with_operators(self):
def test_parse_stream_route_with_operators(self, parser):
"""Test parsing stream route with operators."""
parser = ReactiveConfigParser()
route_data = {
"type": "stream",
"operators": [
@@ -523,10 +477,8 @@ class TestParseStreamRoute:
assert len(result.operators) == 1
assert result.operators[0]["type"] == "map"
def test_parse_stream_route_hot_stream(self):
def test_parse_stream_route_hot_stream(self, parser):
"""Test parsing hot stream."""
parser = ReactiveConfigParser()
route_data = {
"type": "stream",
"stream_type": "hot",
@@ -542,10 +494,8 @@ class TestParseStreamRoute:
class TestParseGraphRoute:
"""Test cases for _parse_graph_route method."""
def test_parse_graph_route_basic(self):
def test_parse_graph_route_basic(self, parser):
"""Test parsing basic graph route."""
parser = ReactiveConfigParser()
route_data = {
"type": "graph",
"nodes": {
@@ -560,10 +510,8 @@ class TestParseGraphRoute:
assert result.type == RouteType.GRAPH
assert "node1" in result.nodes
def test_parse_graph_route_with_edges(self):
def test_parse_graph_route_with_edges(self, parser):
"""Test parsing graph route with edges."""
parser = ReactiveConfigParser()
route_data = {
"type": "graph",
"nodes": {
@@ -580,10 +528,8 @@ class TestParseGraphRoute:
assert len(result.edges) == 1
assert result.edges[0]["source"] == "node1"
def test_parse_graph_route_with_checkpointing(self):
def test_parse_graph_route_with_checkpointing(self, parser):
"""Test parsing graph route with checkpointing."""
parser = ReactiveConfigParser()
route_data = {
"type": "graph",
"nodes": {"node1": {"type": "agent"}},
@@ -600,10 +546,8 @@ class TestParseGraphRoute:
class TestParseBridgeRoute:
"""Test cases for _parse_bridge_route method."""
def test_parse_bridge_route(self):
def test_parse_bridge_route(self, parser):
"""Test parsing bridge route."""
parser = ReactiveConfigParser()
route_data = {
"type": "bridge",
"bridge": {
@@ -622,19 +566,15 @@ class TestParseBridgeRoute:
class TestBuildReactiveConfig:
"""Test cases for _build_reactive_config method."""
def test_build_reactive_config_empty(self):
def test_build_reactive_config_empty(self, parser):
"""Test building config from empty dict."""
parser = ReactiveConfigParser()
result = parser._build_reactive_config({})
assert isinstance(result, ReactiveConfig)
assert result.agents == {}
def test_build_reactive_config_with_agents(self):
def test_build_reactive_config_with_agents(self, parser):
"""Test building config with agents."""
parser = ReactiveConfigParser()
config = {
"agents": {
"agent1": {
@@ -649,10 +589,8 @@ class TestBuildReactiveConfig:
assert "agent1" in result.agents
assert result.agents["agent1"].type == "llm"
def test_build_reactive_config_with_routes(self):
def test_build_reactive_config_with_routes(self, parser):
"""Test building config with routes."""
parser = ReactiveConfigParser()
config = {
"routes": {
"stream1": {
@@ -666,10 +604,8 @@ class TestBuildReactiveConfig:
assert "stream1" in result.routes
def test_build_reactive_config_with_templates(self):
def test_build_reactive_config_with_templates(self, parser):
"""Test building config with templates."""
parser = ReactiveConfigParser()
config = {
"templates": {
"agents": {
@@ -682,10 +618,8 @@ class TestBuildReactiveConfig:
assert "agents" in result.templates
def test_build_reactive_config_with_prompts(self):
def test_build_reactive_config_with_prompts(self, parser):
"""Test building config with prompts."""
parser = ReactiveConfigParser()
config = {
"prompts": {
"prompt1": "This is a test prompt"
@@ -697,10 +631,8 @@ class TestBuildReactiveConfig:
assert "prompt1" in result.prompts
assert result.prompts["prompt1"] == "This is a test prompt"
def test_build_reactive_config_with_template_strings(self):
def test_build_reactive_config_with_template_strings(self, parser):
"""Test building config where template_strings is not empty."""
parser = ReactiveConfigParser()
config = {
"template_strings": {
"agents": {
@@ -715,10 +647,8 @@ class TestBuildReactiveConfig:
assert "agents" in result.templates
assert "template1" in result.templates["agents"]
def test_build_reactive_config_with_agent_template(self):
def test_build_reactive_config_with_agent_template(self, parser):
"""Test building config with template/agent_template in agent_data."""
parser = ReactiveConfigParser()
config = {
"agents": {
"agent1": {
@@ -744,18 +674,15 @@ class TestBuildReactiveConfig:
class TestValidateConfig:
"""Test cases for _validate_config method."""
def test_validate_config_valid(self):
def test_validate_config_valid(self, parser):
"""Test validating valid config."""
parser = ReactiveConfigParser()
config = ReactiveConfig()
# Should not raise
parser._validate_config(config)
def test_validate_config_with_missing_agent(self):
def test_validate_config_with_missing_agent(self, parser):
"""Test validation when route references missing agent."""
parser = ReactiveConfigParser()
route = RouteConfig(
name="test_stream",
type=RouteType.STREAM,
@@ -770,10 +697,8 @@ class TestValidateConfig:
with pytest.raises(ConfigurationError):
parser._validate_config(config)
def test_validate_config_with_stream_route_type(self):
def test_validate_config_with_stream_route_type(self, parser):
"""Test validation with RouteType.STREAM in routes."""
parser = ReactiveConfigParser()
# Create an agent first
agent = AgentConfig(name="test_agent", type="llm")
@@ -794,10 +719,8 @@ class TestValidateConfig:
# Should not raise any errors
parser._validate_config(config)
def test_validate_config_with_graph_route_type(self):
def test_validate_config_with_graph_route_type(self, parser):
"""Test validation with RouteType.GRAPH in routes."""
parser = ReactiveConfigParser()
# Create an agent first
agent = AgentConfig(name="test_agent", type="llm")
@@ -820,10 +743,8 @@ class TestValidateConfig:
# Should not raise any errors
parser._validate_config(config)
def test_validate_config_with_merges(self):
def test_validate_config_with_merges(self, parser):
"""Test validation when config.merges has values."""
parser = ReactiveConfigParser()
# Create routes for merge
route1 = RouteConfig(name="stream1", type=RouteType.STREAM)
route2 = RouteConfig(name="stream2", type=RouteType.STREAM)
@@ -846,10 +767,8 @@ class TestValidateConfig:
# Should not raise any errors
parser._validate_config(config)
def test_validate_config_with_splits(self):
def test_validate_config_with_splits(self, parser):
"""Test validation when config.splits has values."""
parser = ReactiveConfigParser()
# Create routes for split
source_route = RouteConfig(name="source_stream", type=RouteType.STREAM)
route1 = RouteConfig(name="stream1", type=RouteType.STREAM)
@@ -875,10 +794,8 @@ class TestValidateConfig:
# Should not raise any errors
parser._validate_config(config)
def test_validate_config_with_pipeline_stage_graph(self):
def test_validate_config_with_pipeline_stage_graph(self, parser):
"""Test validation where pipeline has stage_type='graph'."""
parser = ReactiveConfigParser()
# Create a graph route
graph_route = RouteConfig(
name="test_graph",
@@ -905,10 +822,8 @@ class TestValidateConfig:
# Should not raise any errors (might log a warning if graph not found)
parser._validate_config(config)
def test_validate_config_with_pipeline_stage_stream(self):
def test_validate_config_with_pipeline_stage_stream(self, parser):
"""Test validation where pipeline has stage_type='stream'."""
parser = ReactiveConfigParser()
# Create a stream route
stream_route = RouteConfig(
name="test_stream",
@@ -937,11 +852,9 @@ class TestValidateConfig:
class TestComplexScenarios:
"""Test cases for complex parsing scenarios."""
def test_parse_complete_config_file(self):
def test_parse_complete_config_file(self, parser):
"""Test parsing complete config file."""
parser = ReactiveConfigParser()
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
yaml.dump({
"agents": {
"agent1": {
@@ -972,13 +885,11 @@ class TestComplexScenarios:
finally:
os.unlink(filepath)
def test_parse_config_with_env_interpolation(self):
def test_parse_config_with_env_interpolation(self, parser):
"""Test parsing config with environment variable interpolation."""
parser = ReactiveConfigParser()
os.environ["MODEL_NAME"] = "gpt-4"
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
yaml.dump({
"agents": {
"agent1": {
+1 -3
View File
@@ -3,7 +3,7 @@ Comprehensive unit tests for reactive route_bridge module.
"""
import asyncio
from unittest.mock import AsyncMock, Mock, patch
from unittest.mock import Mock, patch
import pytest
@@ -514,8 +514,6 @@ class TestCreateGraphFromStream:
graph_config = bridge._create_graph_from_stream(route_config)
# Check that an agent node was created
assert len(graph_config.nodes) > 0
# Find the agent node
agent_nodes = [n for n in graph_config.nodes.values() if n.type == NodeType.AGENT]
assert len(agent_nodes) == 1
assert agent_nodes[0].agent == "my_agent"
+7 -8
View File
@@ -2,8 +2,7 @@
Comprehensive unit tests for reactive stream_router module.
"""
import asyncio
from unittest.mock import AsyncMock, Mock, patch
from unittest.mock import Mock
import pytest
@@ -354,11 +353,11 @@ class TestSendMessage:
router.create_stream(config)
received = []
router.observables["test_stream"].subscribe(lambda x: received.append(x))
router.observables["test_stream"].subscribe(received.append)
router.send_message("test_stream", "test")
assert len(received) > 0
assert len(received) == 1
class TestSubscribeToOutput:
@@ -383,13 +382,13 @@ class TestSubscribeToOutput:
received = []
mock_observer = Mock()
mock_observer.on_next = lambda x: received.append(x)
mock_observer.on_next = received.append
mock_observer.on_error = Mock()
router.subscribe_to_output(mock_observer)
router.send_message("__output__", "test")
assert len(received) > 0
assert len(received) == 1
class TestMergeStreams:
@@ -670,8 +669,8 @@ class TestDispose:
# Dispose
router.dispose()
# Subscriptions should be disposed
assert len(router.subscriptions) >= 0
# Subscriptions should be disposed (list may be empty or cleared)
assert isinstance(router.subscriptions, list)
class TestHandleStreamError:
+3 -6
View File
@@ -3,15 +3,12 @@ Unit tests for agent_templates module.
"""
import pytest
import copy
from unittest.mock import Mock, MagicMock
from unittest.mock import Mock
from cleveragents.templates.agent_templates import AgentTemplate, CompositeAgentTemplate
from cleveragents.templates.base import (
TemplateType,
InstantiationContext,
ComponentReference,
TemplateParameter,
InstantiationContext
)
@@ -640,7 +637,7 @@ class TestCompositeAgentTemplate:
registry.get_template = Mock(return_value=mock_agent_template)
context = InstantiationContext()
result = template.instantiate({}, registry, context)
_ = template.instantiate({}, registry, context)
# Check that instantiate was called with merged params
call_args = mock_agent_template.instantiate.call_args
-2
View File
@@ -3,8 +3,6 @@ Unit tests for templates base module.
"""
import pytest
import copy
from unittest.mock import Mock, MagicMock
from cleveragents.templates.base import (
TemplateType,
@@ -3,7 +3,6 @@ Unit tests for deferred_template module.
"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from cleveragents.templates.deferred_template import (
DeferredTemplate,
@@ -4,7 +4,7 @@ Unit tests for enhanced_registry module.
import pytest
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock, mock_open
from unittest.mock import Mock
import tempfile
from cleveragents.templates.enhanced_registry import EnhancedTemplateRegistry
@@ -40,7 +40,7 @@ class TestEnhancedTemplateRegistry:
template_content = "name: file_template\ntype: llm\nmodel: gpt-4"
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(template_content)
temp_path = Path(f.name)
@@ -452,7 +452,7 @@ type: llm
model: gpt-4
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(template_content)
temp_path = Path(f.name)
+2 -7
View File
@@ -2,14 +2,9 @@
Unit tests for graph_templates module.
"""
import copy
from unittest.mock import MagicMock, Mock, patch
import pytest
from unittest.mock import MagicMock, Mock
from cleveragents.templates.base import (
BaseTemplate,
ComponentReference,
InstantiationContext,
TemplateType,
)
@@ -521,7 +516,7 @@ class TestGraphTemplate:
}
template = GraphTemplate("test_graph", TemplateType.GRAPH, definition)
registry = MagicMock()
_ = MagicMock()
context = InstantiationContext()
# Call _process_nodes directly with a dict containing None
@@ -36,7 +36,7 @@ items:
- one
- two
"""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".yaml", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".yaml", delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -5,7 +5,6 @@ Unit tests for inline_yaml_jinja module.
import tempfile
from pathlib import Path
import pytest
import yaml
from cleveragents.templates.inline_yaml_jinja import InlineYAMLJinja
@@ -38,7 +37,7 @@ class TestInlineYAMLJinja:
name: test
value: 123
"""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".yaml", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".yaml", delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -632,7 +631,7 @@ value: 123
name: {{ app_name }}
version: {{ version }}
"""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".yaml", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".yaml", delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -35,7 +35,7 @@ items:
- one
- two
"""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".yaml", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".yaml", delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -360,7 +360,7 @@ name: {{ app_name }}
name: {{ name }}
value: {{ value }}
"""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".yaml", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".yaml", delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
+103 -140
View File
@@ -4,7 +4,7 @@ Unit tests for loaders module.
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch
from unittest.mock import MagicMock, patch
import pytest
@@ -20,20 +20,24 @@ from cleveragents.templates.loaders import (
from cleveragents.templates.renderer import TemplateRenderer
@pytest.fixture
def mock_renderer():
"""Fixture for MagicMock TemplateRenderer."""
return MagicMock(spec=TemplateRenderer)
class TestTemplateLoader:
"""Test cases for TemplateLoader base class."""
def test_init(self):
def test_init(self, mock_renderer):
"""Test TemplateLoader initialization."""
renderer = MagicMock(spec=TemplateRenderer)
loader = TemplateLoader(renderer)
loader = TemplateLoader(mock_renderer)
assert loader.renderer == renderer
assert loader.renderer == mock_renderer
def test_load_not_implemented(self):
def test_load_not_implemented(self, mock_renderer):
"""Test that load method raises NotImplementedError."""
renderer = MagicMock(spec=TemplateRenderer)
loader = TemplateLoader(renderer)
loader = TemplateLoader(mock_renderer)
with pytest.raises(NotImplementedError, match="Method 'load' is not implemented"):
loader.load()
@@ -42,79 +46,71 @@ class TestTemplateLoader:
class TestFileTemplateLoader:
"""Test cases for FileTemplateLoader class."""
def test_init(self):
def test_init(self, mock_renderer):
"""Test FileTemplateLoader initialization."""
renderer = MagicMock(spec=TemplateRenderer)
file_paths = [Path("test1.j2"), Path("test2.j2")]
loader = FileTemplateLoader(renderer, file_paths)
loader = FileTemplateLoader(mock_renderer, file_paths)
assert loader.renderer == renderer
assert loader.renderer == mock_renderer
assert loader.file_paths == file_paths
def test_load_single_file(self):
def test_load_single_file(self, mock_renderer):
"""Test loading a single template file."""
renderer = MagicMock(spec=TemplateRenderer)
# Create temporary file
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".j2", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".j2", delete=False) as f:
f.write("Hello {{ name }}!")
temp_path = Path(f.name)
try:
loader = FileTemplateLoader(renderer, [temp_path])
loader = FileTemplateLoader(mock_renderer, [temp_path])
loader.load()
# Check that register_template was called
renderer.register_template.assert_called_once()
call_args = renderer.register_template.call_args
mock_renderer.register_template.assert_called_once()
call_args = mock_renderer.register_template.call_args
assert call_args[0][0] == temp_path.stem # template name
assert "Hello {{ name }}!" in call_args[0][1] # template content
finally:
temp_path.unlink()
def test_load_multiple_files(self):
def test_load_multiple_files(self, mock_renderer):
"""Test loading multiple template files."""
renderer = MagicMock(spec=TemplateRenderer)
# Create temporary files
temp_files = []
for i in range(3):
with tempfile.NamedTemporaryFile(
mode="w", suffix=".j2", delete=False, prefix=f"test{i}_"
mode="w", encoding='utf-8', suffix=".j2", delete=False, prefix=f"test{i}_"
) as f:
f.write(f"Template {i}")
temp_files.append(Path(f.name))
try:
loader = FileTemplateLoader(renderer, temp_files)
loader = FileTemplateLoader(mock_renderer, temp_files)
loader.load()
# Check that register_template was called 3 times
assert renderer.register_template.call_count == 3
assert mock_renderer.register_template.call_count == 3
finally:
for temp_path in temp_files:
temp_path.unlink()
def test_load_file_not_found(self):
def test_load_file_not_found(self, mock_renderer):
"""Test loading non-existent file raises TemplateError."""
renderer = MagicMock(spec=TemplateRenderer)
non_existent = Path("/nonexistent/file.j2")
loader = FileTemplateLoader(renderer, [non_existent])
loader = FileTemplateLoader(mock_renderer, [non_existent])
with pytest.raises(TemplateError, match="Template file not found"):
loader.load()
def test_load_file_read_error(self):
def test_load_file_read_error(self, mock_renderer):
"""Test handling file read errors."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".j2", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".j2", delete=False) as f:
f.write("content")
temp_path = Path(f.name)
try:
loader = FileTemplateLoader(renderer, [temp_path])
loader = FileTemplateLoader(mock_renderer, [temp_path])
# Mock open to raise an exception
with patch("builtins.open", side_effect=IOError("Read error")):
@@ -123,41 +119,37 @@ class TestFileTemplateLoader:
finally:
temp_path.unlink()
def test_load_uses_file_stem_as_name(self):
def test_load_uses_file_stem_as_name(self, mock_renderer):
"""Test that file stem is used as template name."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.NamedTemporaryFile(
mode="w", suffix=".j2", delete=False, prefix="my_template_"
mode="w", encoding='utf-8', suffix=".j2", delete=False, prefix="my_template_"
) as f:
f.write("content")
temp_path = Path(f.name)
try:
loader = FileTemplateLoader(renderer, [temp_path])
loader = FileTemplateLoader(mock_renderer, [temp_path])
loader.load()
# Check template name is file stem (without extension)
call_args = renderer.register_template.call_args
call_args = mock_renderer.register_template.call_args
assert call_args[0][0] == temp_path.stem
finally:
temp_path.unlink()
def test_load_empty_file(self):
def test_load_empty_file(self, mock_renderer):
"""Test loading empty file."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', suffix=".j2", delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".j2", delete=False) as f:
# Empty file
temp_path = Path(f.name)
try:
loader = FileTemplateLoader(renderer, [temp_path])
loader = FileTemplateLoader(mock_renderer, [temp_path])
loader.load()
# Should still register the template
renderer.register_template.assert_called_once()
call_args = renderer.register_template.call_args
mock_renderer.register_template.assert_called_once()
call_args = mock_renderer.register_template.call_args
assert call_args[0][1] == "" # empty content
finally:
temp_path.unlink()
@@ -166,58 +158,51 @@ class TestFileTemplateLoader:
class TestDirectoryTemplateLoader:
"""Test cases for DirectoryTemplateLoader class."""
def test_init_with_defaults(self):
def test_init_with_defaults(self, mock_renderer):
"""Test DirectoryTemplateLoader initialization with defaults."""
renderer = MagicMock(spec=TemplateRenderer)
directory = Path("/test/dir")
loader = DirectoryTemplateLoader(renderer, directory)
loader = DirectoryTemplateLoader(mock_renderer, directory)
assert loader.renderer == renderer
assert loader.renderer == mock_renderer
assert loader.directory_path == directory
assert loader.recursive is True
assert loader.pattern == "*.j2"
def test_init_with_custom_params(self):
def test_init_with_custom_params(self, mock_renderer):
"""Test DirectoryTemplateLoader initialization with custom parameters."""
renderer = MagicMock(spec=TemplateRenderer)
directory = Path("/test/dir")
loader = DirectoryTemplateLoader(
renderer, directory, recursive=False, pattern="*.jinja2"
mock_renderer, directory, recursive=False, pattern="*.jinja2"
)
assert loader.recursive is False
assert loader.pattern == "*.jinja2"
def test_load_directory_not_found(self):
def test_load_directory_not_found(self, mock_renderer):
"""Test loading from non-existent directory raises TemplateError."""
renderer = MagicMock(spec=TemplateRenderer)
non_existent = Path("/nonexistent/directory")
loader = DirectoryTemplateLoader(renderer, non_existent)
loader = DirectoryTemplateLoader(mock_renderer, non_existent)
with pytest.raises(TemplateError, match="Template directory not found"):
loader.load()
def test_load_path_is_not_directory(self):
def test_load_path_is_not_directory(self, mock_renderer):
"""Test loading when path is not a directory raises TemplateError."""
renderer = MagicMock(spec=TemplateRenderer)
# Create a file instead of directory
with tempfile.NamedTemporaryFile(prefix='cleveragent_', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', delete=False) as f:
temp_path = Path(f.name)
try:
loader = DirectoryTemplateLoader(renderer, temp_path)
loader = DirectoryTemplateLoader(mock_renderer, temp_path)
with pytest.raises(TemplateError, match="Not a directory"):
loader.load()
finally:
temp_path.unlink()
def test_load_directory_with_files(self):
def test_load_directory_with_files(self, mock_renderer):
"""Test loading templates from directory."""
renderer = MagicMock(spec=TemplateRenderer)
# Create temporary directory with template files
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
@@ -227,16 +212,14 @@ class TestDirectoryTemplateLoader:
file_path = dir_path / f"template{i}.j2"
file_path.write_text(f"Template {i}")
loader = DirectoryTemplateLoader(renderer, dir_path)
loader = DirectoryTemplateLoader(mock_renderer, dir_path)
loader.load()
# Should register 3 templates
assert renderer.register_template.call_count == 3
assert mock_renderer.register_template.call_count == 3
def test_load_directory_recursive(self):
def test_load_directory_recursive(self, mock_renderer):
"""Test loading templates recursively from subdirectories."""
renderer = MagicMock(spec=TemplateRenderer)
# Create temporary directory with subdirectories
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
@@ -249,16 +232,14 @@ class TestDirectoryTemplateLoader:
(dir_path / "root.j2").write_text("Root template")
(sub_dir / "sub.j2").write_text("Sub template")
loader = DirectoryTemplateLoader(renderer, dir_path, recursive=True)
loader = DirectoryTemplateLoader(mock_renderer, dir_path, recursive=True)
loader.load()
# Should register 2 templates
assert renderer.register_template.call_count == 2
assert mock_renderer.register_template.call_count == 2
def test_load_directory_non_recursive(self):
def test_load_directory_non_recursive(self, mock_renderer):
"""Test loading templates non-recursively."""
renderer = MagicMock(spec=TemplateRenderer)
# Create temporary directory with subdirectories
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
@@ -271,16 +252,14 @@ class TestDirectoryTemplateLoader:
(dir_path / "root.j2").write_text("Root template")
(sub_dir / "sub.j2").write_text("Sub template")
loader = DirectoryTemplateLoader(renderer, dir_path, recursive=False)
loader = DirectoryTemplateLoader(mock_renderer, dir_path, recursive=False)
loader.load()
# Should register only 1 template (root level)
assert renderer.register_template.call_count == 1
assert mock_renderer.register_template.call_count == 1
def test_load_directory_with_pattern(self):
def test_load_directory_with_pattern(self, mock_renderer):
"""Test loading templates with custom pattern."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
@@ -290,17 +269,15 @@ class TestDirectoryTemplateLoader:
(dir_path / "template3.txt").write_text("Template 3")
loader = DirectoryTemplateLoader(
renderer, dir_path, recursive=False, pattern="*.jinja2"
mock_renderer, dir_path, recursive=False, pattern="*.jinja2"
)
loader.load()
# Should register only .jinja2 files
assert renderer.register_template.call_count == 1
assert mock_renderer.register_template.call_count == 1
def test_load_uses_relative_path_as_name(self):
def test_load_uses_relative_path_as_name(self, mock_renderer):
"""Test that relative path is used as template name."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
sub_dir = dir_path / "subdir"
@@ -309,36 +286,32 @@ class TestDirectoryTemplateLoader:
file_path = sub_dir / "template.j2"
file_path.write_text("content")
loader = DirectoryTemplateLoader(renderer, dir_path, recursive=True)
loader = DirectoryTemplateLoader(mock_renderer, dir_path, recursive=True)
loader.load()
# Check template name uses relative path
call_args = renderer.register_template.call_args
call_args = mock_renderer.register_template.call_args
template_name = call_args[0][0]
assert "subdir" in template_name or template_name == "subdir/template"
def test_load_empty_directory(self):
def test_load_empty_directory(self, mock_renderer):
"""Test loading from empty directory."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
loader = DirectoryTemplateLoader(renderer, dir_path)
loader = DirectoryTemplateLoader(mock_renderer, dir_path)
loader.load()
# Should not register any templates
renderer.register_template.assert_not_called()
mock_renderer.register_template.assert_not_called()
def test_load_file_read_error_in_directory(self):
def test_load_file_read_error_in_directory(self, mock_renderer):
"""Test handling file read errors when loading from directory."""
renderer = MagicMock(spec=TemplateRenderer)
with tempfile.TemporaryDirectory() as temp_dir:
dir_path = Path(temp_dir)
(dir_path / "template.j2").write_text("content")
loader = DirectoryTemplateLoader(renderer, dir_path)
loader = DirectoryTemplateLoader(mock_renderer, dir_path)
# Mock open to raise an exception
with patch("builtins.open", side_effect=IOError("Read error")):
@@ -349,18 +322,16 @@ class TestDirectoryTemplateLoader:
class TestConfigTemplateLoader:
"""Test cases for ConfigTemplateLoader class."""
def test_init(self):
def test_init(self, mock_renderer):
"""Test ConfigTemplateLoader initialization."""
renderer = MagicMock(spec=TemplateRenderer)
config = {"templates": {"test": "content"}}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
assert loader.renderer == renderer
assert loader.renderer == mock_renderer
assert loader.config == config
def test_load_with_string_templates(self):
def test_load_with_string_templates(self, mock_renderer):
"""Test loading templates from config with string values."""
renderer = MagicMock(spec=TemplateRenderer)
config = {
"templates": {
"template1": "Content 1",
@@ -368,21 +339,20 @@ class TestConfigTemplateLoader:
}
}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Should register 2 templates
assert renderer.register_template.call_count == 2
assert mock_renderer.register_template.call_count == 2
# Check calls
calls = renderer.register_template.call_args_list
calls = mock_renderer.register_template.call_args_list
names = [call[0][0] for call in calls]
assert "template1" in names
assert "template2" in names
def test_load_with_dict_templates(self):
def test_load_with_dict_templates(self, mock_renderer):
"""Test loading templates from config with dict values."""
renderer = MagicMock(spec=TemplateRenderer)
config = {
"templates": {
"template1": {
@@ -396,51 +366,47 @@ class TestConfigTemplateLoader:
}
}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Should register 2 templates
assert renderer.register_template.call_count == 2
assert mock_renderer.register_template.call_count == 2
def test_load_with_empty_templates(self):
def test_load_with_empty_templates(self, mock_renderer):
"""Test loading with empty templates section."""
renderer = MagicMock(spec=TemplateRenderer)
config = {"templates": {}}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Should not register any templates
renderer.register_template.assert_not_called()
mock_renderer.register_template.assert_not_called()
def test_load_without_templates_section(self):
def test_load_without_templates_section(self, mock_renderer):
"""Test loading config without templates section."""
renderer = MagicMock(spec=TemplateRenderer)
config = {"other": "data"}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Should not register any templates
renderer.register_template.assert_not_called()
mock_renderer.register_template.assert_not_called()
def test_load_with_invalid_template_definition(self):
def test_load_with_invalid_template_definition(self, mock_renderer):
"""Test loading with invalid template definition."""
renderer = MagicMock(spec=TemplateRenderer)
config = {
"templates": {
"invalid": {"no_content_key": "value"},
}
}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
with pytest.raises(TemplateError, match="Invalid template definition"):
loader.load()
def test_load_with_mixed_template_types(self):
def test_load_with_mixed_template_types(self, mock_renderer):
"""Test loading with mix of string and dict templates."""
renderer = MagicMock(spec=TemplateRenderer)
config = {
"templates": {
"string_template": "String content",
@@ -448,40 +414,37 @@ class TestConfigTemplateLoader:
}
}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Should register 2 templates
assert renderer.register_template.call_count == 2
assert mock_renderer.register_template.call_count == 2
def test_load_with_register_error(self):
def test_load_with_register_error(self, mock_renderer):
"""Test handling errors during template registration."""
renderer = MagicMock(spec=TemplateRenderer)
renderer.register_template.side_effect = Exception("Registration error")
mock_renderer.register_template.side_effect = Exception("Registration error")
config = {"templates": {"test": "content"}}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
with pytest.raises(TemplateError, match="Failed to load templates from configuration"):
loader.load()
def test_load_preserves_template_content(self):
def test_load_preserves_template_content(self, mock_renderer):
"""Test that template content is preserved correctly."""
renderer = MagicMock(spec=TemplateRenderer)
content = "Hello {{ name }}! Welcome to {{ place }}."
config = {"templates": {"greeting": content}}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Check that exact content was registered
call_args = renderer.register_template.call_args
call_args = mock_renderer.register_template.call_args
assert call_args[0][0] == "greeting"
assert call_args[0][1] == content
def test_load_with_complex_dict_template(self):
def test_load_with_complex_dict_template(self, mock_renderer):
"""Test loading template with additional metadata."""
renderer = MagicMock(spec=TemplateRenderer)
config = {
"templates": {
"complex": {
@@ -492,12 +455,12 @@ class TestConfigTemplateLoader:
}
}
loader = ConfigTemplateLoader(renderer, config)
loader = ConfigTemplateLoader(mock_renderer, config)
loader.load()
# Should register the template with just the content
renderer.register_template.assert_called_once()
call_args = renderer.register_template.call_args
mock_renderer.register_template.assert_called_once()
call_args = mock_renderer.register_template.call_args
assert call_args[0][1] == "Template content"
@@ -506,7 +469,7 @@ class TestLoadFromFile:
def test_load_from_existing_file(self):
"""Test loading content from existing file."""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', delete=False) as f:
f.write("Template content")
temp_path = f.name
@@ -539,7 +502,7 @@ class TestLoadFromFile:
def test_load_from_empty_file(self):
"""Test loading empty file."""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', delete=False) as f:
temp_path = f.name
try:
@@ -551,7 +514,7 @@ class TestLoadFromFile:
def test_load_from_file_read_error(self):
"""Test handling file read errors."""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', delete=False) as f:
temp_path = f.name
try:
@@ -568,7 +531,7 @@ class TestLoadFromFile:
Line 2
Line 3"""
with tempfile.NamedTemporaryFile(mode="w", prefix='cleveragent_', delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', delete=False) as f:
f.write(content)
temp_path = f.name
+39 -66
View File
@@ -10,6 +10,12 @@ from cleveragents.templates.base import InstantiationContext, TemplateType
from cleveragents.templates.registry import TemplateRegistry
@pytest.fixture
def registry():
"""Fixture for TemplateRegistry instance."""
return TemplateRegistry()
class TestTemplateRegistry:
"""Test cases for TemplateRegistry class."""
@@ -24,9 +30,8 @@ class TestTemplateRegistry:
assert registry.templates[TemplateType.GRAPH] == {}
assert registry.templates[TemplateType.STREAM] == {}
def test_register_template_agent(self):
def test_register_template_agent(self, registry):
"""Test registering an agent template."""
registry = TemplateRegistry()
definition = {
"type": "llm",
@@ -38,9 +43,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.AGENT, "test_agent")
def test_register_template_composite_agent(self):
def test_register_template_composite_agent(self, registry):
"""Test registering a composite agent template."""
registry = TemplateRegistry()
definition = {
"type": "composite",
@@ -52,9 +56,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.AGENT, "composite_agent")
def test_register_template_graph(self):
def test_register_template_graph(self, registry):
"""Test registering a graph template."""
registry = TemplateRegistry()
definition = {
"nodes": {},
@@ -66,9 +69,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.GRAPH, "test_graph")
def test_register_template_stream(self):
def test_register_template_stream(self, registry):
"""Test registering a stream template."""
registry = TemplateRegistry()
definition = {
"type": "hot",
@@ -80,9 +82,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.STREAM, "test_stream")
def test_register_template_unknown_type(self):
def test_register_template_unknown_type(self, registry):
"""Test registering template with unknown type raises error."""
registry = TemplateRegistry()
definition = {"type": "unknown"}
@@ -91,9 +92,8 @@ class TestTemplateRegistry:
# This should trigger the ValueError for unknown types
registry.register_template("INVALID_TYPE", "test", definition) # type: ignore
def test_get_template_exists(self):
def test_get_template_exists(self, registry):
"""Test getting an existing template."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
@@ -103,31 +103,27 @@ class TestTemplateRegistry:
assert template is not None
assert template.name == "test_agent"
def test_get_template_not_found(self):
def test_get_template_not_found(self, registry):
"""Test getting a non-existent template raises error."""
registry = TemplateRegistry()
with pytest.raises(ValueError, match="not found"):
registry.get_template(TemplateType.AGENT, "nonexistent")
def test_has_template_true(self):
def test_has_template_true(self, registry):
"""Test has_template returns True for existing template."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
assert registry.has_template(TemplateType.AGENT, "test_agent") is True
def test_has_template_false(self):
def test_has_template_false(self, registry):
"""Test has_template returns False for non-existent template."""
registry = TemplateRegistry()
assert registry.has_template(TemplateType.AGENT, "nonexistent") is False
def test_instantiate_basic(self):
def test_instantiate_basic(self, registry):
"""Test basic template instantiation."""
registry = TemplateRegistry()
definition = {
"type": "llm",
@@ -144,9 +140,8 @@ class TestTemplateRegistry:
assert result is not None
assert result["type"] == "llm"
def test_instantiate_with_context(self):
def test_instantiate_with_context(self, registry):
"""Test template instantiation with custom context."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
@@ -161,9 +156,8 @@ class TestTemplateRegistry:
assert result is not None
def test_instantiate_without_context(self):
def test_instantiate_without_context(self, registry):
"""Test template instantiation creates default context."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
@@ -176,16 +170,14 @@ class TestTemplateRegistry:
assert result is not None
def test_instantiate_from_config_none(self):
def test_instantiate_from_config_none(self, registry):
"""Test instantiate_from_config with None config raises error."""
registry = TemplateRegistry()
with pytest.raises(ValueError, match="Cannot instantiate from None"):
registry.instantiate_from_config(None)
def test_instantiate_from_config_with_template_key(self):
def test_instantiate_from_config_with_template_key(self, registry):
"""Test instantiate_from_config with 'template' key."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
@@ -199,9 +191,8 @@ class TestTemplateRegistry:
assert result is not None
def test_instantiate_from_config_template_not_found(self):
def test_instantiate_from_config_template_not_found(self, registry):
"""Test instantiate_from_config when template not found."""
registry = TemplateRegistry()
config = {
"template": "nonexistent",
@@ -211,9 +202,8 @@ class TestTemplateRegistry:
with pytest.raises(ValueError, match="not found in any category"):
registry.instantiate_from_config(config)
def test_instantiate_from_config_with_agent_template_key(self):
def test_instantiate_from_config_with_agent_template_key(self, registry):
"""Test instantiate_from_config with 'agent_template' key."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
@@ -227,9 +217,8 @@ class TestTemplateRegistry:
assert result is not None
def test_instantiate_from_config_with_graph_template_key(self):
def test_instantiate_from_config_with_graph_template_key(self, registry):
"""Test instantiate_from_config with 'graph_template' key."""
registry = TemplateRegistry()
definition = {"nodes": {}, "edges": [], "parameters": {}}
registry.register_template(TemplateType.GRAPH, "test_graph", definition)
@@ -243,9 +232,8 @@ class TestTemplateRegistry:
assert result is not None
def test_instantiate_from_config_with_stream_template_key(self):
def test_instantiate_from_config_with_stream_template_key(self, registry):
"""Test instantiate_from_config with 'stream_template' key."""
registry = TemplateRegistry()
definition = {"type": "hot", "operators": [], "parameters": {}}
registry.register_template(TemplateType.STREAM, "test_stream", definition)
@@ -259,9 +247,8 @@ class TestTemplateRegistry:
assert result is not None
def test_instantiate_from_config_direct_agent_definition(self):
def test_instantiate_from_config_direct_agent_definition(self, registry):
"""Test instantiate_from_config with direct agent definition."""
registry = TemplateRegistry()
config = {
"type": "llm",
@@ -273,9 +260,8 @@ class TestTemplateRegistry:
# Direct agent definitions return None (handled by agent factory)
assert result is None
def test_instantiate_from_config_direct_graph_definition(self):
def test_instantiate_from_config_direct_graph_definition(self, registry):
"""Test instantiate_from_config with direct graph definition."""
registry = TemplateRegistry()
config = {
"name": "test_graph",
@@ -288,9 +274,8 @@ class TestTemplateRegistry:
# Should return GraphConfig
assert result is not None
def test_instantiate_from_config_direct_stream_definition(self):
def test_instantiate_from_config_direct_stream_definition(self, registry):
"""Test instantiate_from_config with direct stream definition."""
registry = TemplateRegistry()
config = {
"name": "test_stream",
@@ -302,9 +287,8 @@ class TestTemplateRegistry:
# Should return StreamConfig
assert result is not None
def test_instantiate_from_config_unknown_type(self):
def test_instantiate_from_config_unknown_type(self, registry):
"""Test instantiate_from_config with unknown config type."""
registry = TemplateRegistry()
config = {
"unknown_key": "value"
@@ -313,9 +297,8 @@ class TestTemplateRegistry:
with pytest.raises(ValueError, match="Cannot determine instance type"):
registry.instantiate_from_config(config)
def test_instantiate_from_config_with_custom_context(self):
def test_instantiate_from_config_with_custom_context(self, registry):
"""Test instantiate_from_config with custom context."""
registry = TemplateRegistry()
definition = {"type": "llm", "parameters": {}}
registry.register_template(TemplateType.AGENT, "test_agent", definition)
@@ -330,9 +313,8 @@ class TestTemplateRegistry:
assert result is not None
def test_register_all_templates_empty(self):
def test_register_all_templates_empty(self, registry):
"""Test register_all_templates with empty config."""
registry = TemplateRegistry()
templates_config = {}
@@ -343,9 +325,8 @@ class TestTemplateRegistry:
assert len(registry.templates[TemplateType.GRAPH]) == 0
assert len(registry.templates[TemplateType.STREAM]) == 0
def test_register_all_templates_agents(self):
def test_register_all_templates_agents(self, registry):
"""Test register_all_templates with agent templates."""
registry = TemplateRegistry()
templates_config = {
"agents": {
@@ -360,9 +341,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.AGENT, "agent1")
assert registry.has_template(TemplateType.AGENT, "agent2")
def test_register_all_templates_graphs(self):
def test_register_all_templates_graphs(self, registry):
"""Test register_all_templates with graph templates."""
registry = TemplateRegistry()
templates_config = {
"graphs": {
@@ -377,9 +357,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.GRAPH, "graph1")
assert registry.has_template(TemplateType.GRAPH, "graph2")
def test_register_all_templates_streams(self):
def test_register_all_templates_streams(self, registry):
"""Test register_all_templates with stream templates."""
registry = TemplateRegistry()
templates_config = {
"streams": {
@@ -394,9 +373,8 @@ class TestTemplateRegistry:
assert registry.has_template(TemplateType.STREAM, "stream1")
assert registry.has_template(TemplateType.STREAM, "stream2")
def test_register_all_templates_all_types(self):
def test_register_all_templates_all_types(self, registry):
"""Test register_all_templates with all template types."""
registry = TemplateRegistry()
templates_config = {
"agents": {
@@ -416,9 +394,8 @@ class TestTemplateRegistry:
assert len(registry.templates[TemplateType.GRAPH]) == 1
assert len(registry.templates[TemplateType.STREAM]) == 1
def test_list_templates_empty(self):
def test_list_templates_empty(self, registry):
"""Test list_templates with no templates."""
registry = TemplateRegistry()
result = registry.list_templates()
@@ -428,9 +405,8 @@ class TestTemplateRegistry:
"stream": []
}
def test_list_templates_all_types(self):
def test_list_templates_all_types(self, registry):
"""Test list_templates with templates of all types."""
registry = TemplateRegistry()
registry.register_template(TemplateType.AGENT, "agent1", {"type": "llm", "parameters": {}})
registry.register_template(TemplateType.GRAPH, "graph1", {"nodes": {}, "edges": [], "parameters": {}})
@@ -442,9 +418,8 @@ class TestTemplateRegistry:
assert "graph1" in result["graph"]
assert "stream1" in result["stream"]
def test_list_templates_specific_type(self):
def test_list_templates_specific_type(self, registry):
"""Test list_templates for a specific template type."""
registry = TemplateRegistry()
registry.register_template(TemplateType.AGENT, "agent1", {"type": "llm", "parameters": {}})
registry.register_template(TemplateType.AGENT, "agent2", {"type": "llm", "parameters": {}})
@@ -457,9 +432,8 @@ class TestTemplateRegistry:
assert "agent2" in result["agent"]
assert "graph" not in result
def test_instantiate_from_config_template_search_order(self):
def test_instantiate_from_config_template_search_order(self, registry):
"""Test instantiate_from_config searches templates in order."""
registry = TemplateRegistry()
# Register template with same name in multiple categories
registry.register_template(TemplateType.AGENT, "shared_name", {"type": "llm", "parameters": {}})
@@ -475,9 +449,8 @@ class TestTemplateRegistry:
# Should find agent first (AGENT is checked before GRAPH)
assert result is not None
def test_register_template_multiple_times(self):
def test_register_template_multiple_times(self, registry):
"""Test registering template multiple times overwrites."""
registry = TemplateRegistry()
definition1 = {"type": "llm", "model": "gpt-3", "parameters": {}}
definition2 = {"type": "llm", "model": "gpt-4", "parameters": {}}
+87 -114
View File
@@ -14,6 +14,18 @@ from cleveragents.templates.renderer import (
)
@pytest.fixture
def simple_renderer():
"""Fixture for TemplateRenderer with SIMPLE engine."""
return TemplateRenderer(TemplateEngine.SIMPLE)
@pytest.fixture
def jinja2_renderer():
"""Fixture for TemplateRenderer with JINJA2 engine."""
return TemplateRenderer(TemplateEngine.JINJA2)
class TestResolvePath:
"""Test cases for _resolve_path function."""
@@ -137,114 +149,96 @@ class TestTemplateRenderer:
assert renderer.engine_type == TemplateEngine.SIMPLE
def test_register_template_simple(self):
def test_register_template_simple(self, simple_renderer):
"""Test registering a template with simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
simple_renderer.register_template("test", "Hello {{ name }}")
renderer.register_template("test", "Hello {{ name }}")
assert "test" in simple_renderer.templates
assert "test" in renderer.templates
def test_register_template_jinja2(self):
def test_register_template_jinja2(self, jinja2_renderer):
"""Test registering a template with Jinja2 engine."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
jinja2_renderer.register_template("test", "Hello {{ name }}")
renderer.register_template("test", "Hello {{ name }}")
assert "test" in jinja2_renderer.templates
assert "test" in renderer.templates
def test_register_template_empty_name(self):
def test_register_template_empty_name(self, simple_renderer):
"""Test registering template with empty name raises error."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
with pytest.raises(TemplateError, match="name cannot be empty"):
renderer.register_template("", "template")
simple_renderer.register_template("", "template")
def test_register_template_jinja2_error(self):
def test_register_template_jinja2_error(self, jinja2_renderer):
"""Test registering invalid Jinja2 template raises error."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
# Invalid Jinja2 syntax
with pytest.raises(TemplateError, match="Failed to register"):
renderer.register_template("test", "{% if %}")
jinja2_renderer.register_template("test", "{% if %}")
def test_register_template_jinja2_no_from_string(self):
def test_register_template_jinja2_no_from_string(self, jinja2_renderer):
"""Test registering template when engine has no from_string method."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
renderer.engine = Mock()
delattr(renderer.engine, 'from_string') # Remove from_string attribute
jinja2_renderer.engine = Mock()
delattr(jinja2_renderer.engine, 'from_string')
with pytest.raises(TemplateError, match="no from_string method"):
renderer.register_template("test", "template")
jinja2_renderer.register_template("test", "template")
def test_render_simple(self):
def test_render_simple(self, simple_renderer):
"""Test rendering template with simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Hello {{ name }}")
simple_renderer.register_template("test", "Hello {{ name }}")
result = renderer.render("test", {"name": "World"})
result = simple_renderer.render("test", {"name": "World"})
assert result == "Hello World"
def test_render_jinja2(self):
def test_render_jinja2(self, jinja2_renderer):
"""Test rendering template with Jinja2 engine."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
renderer.register_template("test", "Hello {{ name }}")
jinja2_renderer.register_template("test", "Hello {{ name }}")
result = renderer.render("test", {"name": "World"})
result = jinja2_renderer.render("test", {"name": "World"})
assert result == "Hello World"
def test_render_template_not_found(self):
def test_render_template_not_found(self, simple_renderer):
"""Test rendering non-existent template raises error."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
with pytest.raises(TemplateError, match="not found"):
renderer.render("nonexistent", {})
simple_renderer.render("nonexistent", {})
def test_render_simple_missing_variable(self):
def test_render_simple_missing_variable(self, simple_renderer):
"""Test rendering with missing variable in simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Hello {name}")
simple_renderer.register_template("test", "Hello {name}")
with pytest.raises(TemplateError, match="Missing template variable"):
renderer.render("test", {})
simple_renderer.render("test", {})
def test_render_simple_with_format_placeholders(self):
def test_render_simple_with_format_placeholders(self, simple_renderer):
"""Test rendering with {} format placeholders."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Hello {name}")
simple_renderer.register_template("test", "Hello {name}")
result = renderer.render("test", {"name": "World"})
result = simple_renderer.render("test", {"name": "World"})
assert result == "Hello World"
def test_render_simple_with_jinja_like_placeholders(self):
def test_render_simple_with_jinja_like_placeholders(self, simple_renderer):
"""Test rendering with {{ }} placeholders in simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Value: {{ value }}")
simple_renderer.register_template("test", "Value: {{ value }}")
result = renderer.render("test", {"value": 42})
result = simple_renderer.render("test", {"value": 42})
assert result == "Value: 42"
def test_render_simple_with_expression(self):
def test_render_simple_with_expression(self, simple_renderer):
"""Test rendering with expression in simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Result: {{ len(items) }}")
simple_renderer.register_template("test", "Result: {{ len(items) }}")
result = renderer.render("test", {"items": [1, 2, 3], "len": len})
result = simple_renderer.render("test", {"items": [1, 2, 3], "len": len})
assert result == "Result: 3"
def test_render_jinja2_no_render_method(self):
def test_render_jinja2_no_render_method(self, jinja2_renderer):
"""Test rendering when Jinja2 template has no render method."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
renderer.register_template("test", "template")
renderer.templates["test"] = Mock(spec=[]) # Mock without render method
jinja2_renderer.register_template("test", "template")
jinja2_renderer.templates["test"] = Mock(spec=[])
with pytest.raises(TemplateError, match="no render method"):
renderer.render("test", {})
jinja2_renderer.render("test", {})
def test_render_mustache(self):
"""Test rendering template with Mustache engine."""
@@ -270,43 +264,34 @@ class TestTemplateRenderer:
except TemplateError:
pytest.skip("Pystache not installed")
def test_render_string_simple(self):
def test_render_string_simple(self, simple_renderer):
"""Test rendering string with simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
result = renderer.render_string("Hello {{ name }}", {"name": "World"})
result = simple_renderer.render_string("Hello {{ name }}", {"name": "World"})
assert result == "Hello World"
def test_render_string_jinja2(self):
def test_render_string_jinja2(self, jinja2_renderer):
"""Test rendering string with Jinja2 engine."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
result = renderer.render_string("Hello {{ name }}", {"name": "World"})
result = jinja2_renderer.render_string("Hello {{ name }}", {"name": "World"})
assert result == "Hello World"
def test_render_string_simple_missing_variable(self):
def test_render_string_simple_missing_variable(self, simple_renderer):
"""Test rendering string with missing variable in simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
with pytest.raises(TemplateError, match="Missing template variable"):
renderer.render_string("Hello {name}", {})
simple_renderer.render_string("Hello {name}", {})
def test_render_string_with_source_description(self):
def test_render_string_with_source_description(self, simple_renderer):
"""Test rendering string with source description."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
with pytest.raises(TemplateError, match="for config.yaml"):
renderer.render_string("Hello {name}", {}, "config.yaml")
simple_renderer.render_string("Hello {name}", {}, "config.yaml")
def test_render_string_jinja2_no_from_string(self):
def test_render_string_jinja2_no_from_string(self, jinja2_renderer):
"""Test rendering string when Jinja2 engine has no from_string method."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
renderer.engine = Mock(spec=[]) # Mock without from_string method
jinja2_renderer.engine = Mock(spec=[])
with pytest.raises(TemplateError, match="no from_string method"):
renderer.render_string("template", {})
jinja2_renderer.render_string("template", {})
def test_render_string_mustache(self):
"""Test rendering string with Mustache engine."""
@@ -330,58 +315,49 @@ class TestTemplateRenderer:
except TemplateError:
pytest.skip("Pystache not installed")
def test_get_template_exists(self):
def test_get_template_exists(self, simple_renderer):
"""Test getting an existing template."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "template")
simple_renderer.register_template("test", "template")
template = renderer.get_template("test")
template = simple_renderer.get_template("test")
assert template is not None
def test_get_template_not_found(self):
def test_get_template_not_found(self, simple_renderer):
"""Test getting non-existent template raises error."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
with pytest.raises(TemplateError, match="not found"):
renderer.get_template("nonexistent")
simple_renderer.get_template("nonexistent")
def test_list_templates_empty(self):
def test_list_templates_empty(self, simple_renderer):
"""Test listing templates when none registered."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
result = renderer.list_templates()
result = simple_renderer.list_templates()
assert result == []
def test_list_templates_multiple(self):
def test_list_templates_multiple(self, simple_renderer):
"""Test listing multiple templates."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test1", "template1")
renderer.register_template("test2", "template2")
renderer.register_template("test3", "template3")
simple_renderer.register_template("test1", "template1")
simple_renderer.register_template("test2", "template2")
simple_renderer.register_template("test3", "template3")
result = renderer.list_templates()
result = simple_renderer.list_templates()
assert len(result) == 3
assert "test1" in result
assert "test2" in result
assert "test3" in result
def test_render_simple_with_none_in_expression(self):
def test_render_simple_with_none_in_expression(self, simple_renderer):
"""Test rendering expression that evaluates to None."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Value: {{ none_value }}")
simple_renderer.register_template("test", "Value: {{ none_value }}")
result = renderer.render("test", {"none_value": None})
result = simple_renderer.render("test", {"none_value": None})
assert result == "Value: "
def test_render_string_simple_with_format_and_jinja(self):
def test_render_string_simple_with_format_and_jinja(self, simple_renderer):
"""Test rendering with both format and jinja-like placeholders."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
result = renderer.render_string(
result = simple_renderer.render_string(
"Name: {{ name }}, Age: {age}",
{"name": "John", "age": 30}
)
@@ -389,34 +365,31 @@ class TestTemplateRenderer:
assert "John" in result
assert "30" in result
def test_render_simple_with_complex_expression(self):
def test_render_simple_with_complex_expression(self, simple_renderer):
"""Test rendering with complex expression."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "Sum: {{ sum([1, 2, 3]) }}")
simple_renderer.register_template("test", "Sum: {{ sum([1, 2, 3]) }}")
result = renderer.render("test", {"sum": sum})
result = simple_renderer.render("test", {"sum": sum})
assert result == "Sum: 6"
def test_render_jinja2_with_complex_template(self):
def test_render_jinja2_with_complex_template(self, jinja2_renderer):
"""Test rendering complex Jinja2 template."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
renderer.register_template(
jinja2_renderer.register_template(
"test",
"{% for item in items %}{{ item }}{% if not loop.last %}, {% endif %}{% endfor %}"
)
result = renderer.render("test", {"items": ["a", "b", "c"]})
result = jinja2_renderer.render("test", {"items": ["a", "b", "c"]})
assert result == "a, b, c"
def test_render_error_handling(self):
def test_render_error_handling(self, simple_renderer):
"""Test rendering error is properly wrapped."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
renderer.register_template("test", "{{ bad_expression() }}")
simple_renderer.register_template("test", "{{ bad_expression() }}")
with pytest.raises(TemplateError, match="Failed to render template"):
renderer.render("test", {})
simple_renderer.render("test", {})
if __name__ == "__main__":
@@ -193,7 +193,7 @@ name: {{ name }}
value: 42
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -246,7 +246,7 @@ macros:
assert parsed is not None
# Macros with endmacro are detected
assert "macros" in parsed or len(templates) >= 0
assert "macros" in parsed or isinstance(templates, list)
def test_load_string_inline_with_jinja_expression(self):
"""Test loading YAML with Jinja expression in value."""
@@ -2,13 +2,11 @@
Unit tests for stream_templates module.
"""
import copy
from unittest.mock import MagicMock, Mock
import pytest
from cleveragents.templates.base import (
ComponentReference,
InstantiationContext,
TemplateType,
)
+23 -36
View File
@@ -3,11 +3,16 @@ Comprehensive unit tests for template_store module.
"""
import pytest
import yaml
from cleveragents.templates.template_store import TemplateDefinition, TemplateStore
@pytest.fixture
def store():
"""Fixture for TemplateStore instance."""
return TemplateStore()
class TestTemplateStore:
"""Test cases for TemplateStore class."""
@@ -26,9 +31,8 @@ class TestTemplateStore:
"streams": {},
}
def test_add_template_with_string(self):
def test_add_template_with_string(self, store):
"""Test adding template as string."""
store = TemplateStore()
template_str = """
type: llm
@@ -40,9 +44,8 @@ parameters:
assert "test_agent" in store.raw_templates["agents"]
assert store.raw_templates["agents"]["test_agent"] == template_str
def test_add_template_with_dict(self):
def test_add_template_with_dict(self, store):
"""Test adding template as dictionary."""
store = TemplateStore()
template_dict = {
"type": "llm",
@@ -58,9 +61,8 @@ parameters:
assert isinstance(store.raw_templates["agents"]["test_agent"], str)
assert "type: llm" in store.raw_templates["agents"]["test_agent"]
def test_add_template_extracts_metadata_from_string(self):
def test_add_template_extracts_metadata_from_string(self, store):
"""Test metadata extraction from string template."""
store = TemplateStore()
template_str = """
type: llm
@@ -76,9 +78,8 @@ parameters:
assert "model" in metadata["parameters"]
assert metadata["parameters"]["model"] == "gpt-4"
def test_add_template_extracts_metadata_from_dict(self):
def test_add_template_extracts_metadata_from_dict(self, store):
"""Test metadata extraction from dict template."""
store = TemplateStore()
template_dict = {
"type": "graph",
@@ -110,9 +111,8 @@ parameters:
metadata = store.get_metadata("agents", "invalid_template")
# Metadata might be None or empty if parsing failed
def test_add_template_with_no_type(self):
def test_add_template_with_no_type(self, store):
"""Test adding template without type field."""
store = TemplateStore()
template_dict = {
"parameters": {
@@ -126,9 +126,8 @@ parameters:
assert metadata is not None
assert metadata["type"] == "unknown"
def test_add_template_with_no_parameters(self):
def test_add_template_with_no_parameters(self, store):
"""Test adding template without parameters field."""
store = TemplateStore()
template_dict = {
"type": "simple"
@@ -140,33 +139,28 @@ parameters:
assert metadata is not None
assert metadata["parameters"] == {}
def test_get_template_existing(self):
def test_get_template_existing(self, store):
"""Test getting existing template."""
store = TemplateStore()
template_str = "type: llm"
store.add_template("agents", "test", template_str)
result = store.get_template("agents", "test")
assert result == template_str
def test_get_template_nonexistent(self):
def test_get_template_nonexistent(self, store):
"""Test getting non-existent template."""
store = TemplateStore()
result = store.get_template("agents", "nonexistent")
assert result is None
def test_get_template_invalid_type(self):
def test_get_template_invalid_type(self, store):
"""Test getting template with invalid type."""
store = TemplateStore()
result = store.get_template("invalid_type", "test")
assert result is None
def test_get_metadata_existing(self):
def test_get_metadata_existing(self, store):
"""Test getting metadata for existing template."""
store = TemplateStore()
template_dict = {
"type": "llm",
@@ -178,23 +172,20 @@ parameters:
assert metadata is not None
assert metadata["type"] == "llm"
def test_get_metadata_nonexistent(self):
def test_get_metadata_nonexistent(self, store):
"""Test getting metadata for non-existent template."""
store = TemplateStore()
metadata = store.get_metadata("agents", "nonexistent")
assert metadata is None
def test_get_metadata_invalid_type(self):
def test_get_metadata_invalid_type(self, store):
"""Test getting metadata with invalid type."""
store = TemplateStore()
metadata = store.get_metadata("invalid_type", "test")
assert metadata is None
def test_instantiate_template_basic(self):
def test_instantiate_template_basic(self, store):
"""Test basic template instantiation."""
store = TemplateStore()
template_str = """
type: llm
@@ -207,9 +198,8 @@ model: "{{ model_name }}"
assert result["type"] == "llm"
assert result["model"] == "gpt-4"
def test_instantiate_template_with_utility_functions(self):
def test_instantiate_template_with_utility_functions(self, store):
"""Test template instantiation with utility functions."""
store = TemplateStore()
template_str = """
type: agent
@@ -227,16 +217,14 @@ items:
assert result["count"] == 3
assert len(result["items"]) == 3
def test_instantiate_template_not_found(self):
def test_instantiate_template_not_found(self, store):
"""Test instantiating non-existent template."""
store = TemplateStore()
with pytest.raises(ValueError, match="Template.*not found"):
store.instantiate_template("agents", "nonexistent", {})
def test_instantiate_template_with_complex_params(self):
def test_instantiate_template_with_complex_params(self, store):
"""Test template instantiation with complex parameters."""
store = TemplateStore()
template_str = """
type: graph
@@ -261,9 +249,8 @@ nodes:
assert "node1" in result["nodes"]
assert "node2" in result["nodes"]
def test_add_multiple_templates_same_type(self):
def test_add_multiple_templates_same_type(self, store):
"""Test adding multiple templates of same type."""
store = TemplateStore()
store.add_template("agents", "agent1", {"type": "llm"})
store.add_template("agents", "agent2", {"type": "tool"})
@@ -144,7 +144,7 @@ name: {{ name }}
value: {{ value }}
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -505,7 +505,7 @@ name: {{ name }}
value: {{ value }}
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -747,7 +747,7 @@ class TestYAMLJinjaLoaderEdgeCases:
{% endif %}
"""
protected, sections = loader._protect_template_sections(content)
protected, _ = loader._protect_template_sections(content)
assert protected is not None
@@ -761,7 +761,7 @@ name: static
value: 42
"""
protected, sections = loader._protect_template_sections(content)
protected, _ = loader._protect_template_sections(content)
assert protected is not None
@@ -5,7 +5,6 @@ Unit tests for yaml_preprocessor module.
import pytest
import tempfile
from pathlib import Path
from unittest.mock import Mock, patch
from cleveragents.templates.yaml_preprocessor import (
YAMLTemplateProcessor,
@@ -111,7 +110,7 @@ name: {{ name }}
value: {{ value }}
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -249,7 +248,7 @@ name: {{ agent_name }}
type: llm
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -272,7 +271,7 @@ invalid: : yaml
{% endfor %}
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -644,7 +643,7 @@ name: static
value: 42
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
@@ -127,7 +127,7 @@ name: {{ name }}
value: {{ value }}
"""
with tempfile.NamedTemporaryFile(mode='w', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='cleveragent_', suffix='.yaml', delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)