forked from cleveragents/cleveragents-core
refactor: cleanup test files with fixtures; remove unused imports; remove unused variables
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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": {}}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user