From 485084fbccea6545007dc42a8a4d14ce174d6dd3 Mon Sep 17 00:00:00 2001 From: Aditya Chhabra Date: Thu, 13 Nov 2025 18:41:10 +0530 Subject: [PATCH] refactor: cleanup test files with fixtures; remove unused imports; remove unused variables --- src/cleveragents/agents/base.py | 10 +- tests/unit/langgraph/test_graph.py | 51 ++-- tests/unit/langgraph/test_state.py | 6 +- tests/unit/reactive/test_config_parser.py | 213 +++++---------- tests/unit/reactive/test_route_bridge.py | 4 +- tests/unit/reactive/test_stream_router.py | 15 +- tests/unit/templates/test_agent_templates.py | 9 +- tests/unit/templates/test_base.py | 2 - .../unit/templates/test_deferred_template.py | 1 - .../unit/templates/test_enhanced_registry.py | 6 +- tests/unit/templates/test_graph_templates.py | 9 +- .../templates/test_inline_jinja_handler.py | 2 +- .../unit/templates/test_inline_yaml_jinja.py | 5 +- .../templates/test_jinja_yaml_preprocessor.py | 4 +- tests/unit/templates/test_loaders.py | 243 ++++++++---------- tests/unit/templates/test_registry.py | 105 +++----- tests/unit/templates/test_renderer.py | 201 +++++++-------- .../unit/templates/test_smart_yaml_loader.py | 4 +- tests/unit/templates/test_stream_templates.py | 2 - tests/unit/templates/test_template_store.py | 59 ++--- .../unit/templates/test_yaml_jinja_loader.py | 8 +- .../unit/templates/test_yaml_preprocessor.py | 9 +- .../templates/test_yaml_template_engine.py | 2 +- 23 files changed, 376 insertions(+), 594 deletions(-) diff --git a/src/cleveragents/agents/base.py b/src/cleveragents/agents/base.py index eda8f015..b8facb91 100644 --- a/src/cleveragents/agents/base.py +++ b/src/cleveragents/agents/base.py @@ -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 diff --git a/tests/unit/langgraph/test_graph.py b/tests/unit/langgraph/test_graph.py index 57789bc3..58210cf0 100644 --- a/tests/unit/langgraph/test_graph.py +++ b/tests/unit/langgraph/test_graph.py @@ -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 diff --git a/tests/unit/langgraph/test_state.py b/tests/unit/langgraph/test_state.py index 6029b164..b236bd14 100644 --- a/tests/unit/langgraph/test_state.py +++ b/tests/unit/langgraph/test_state.py @@ -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" diff --git a/tests/unit/reactive/test_config_parser.py b/tests/unit/reactive/test_config_parser.py index 5fc5e96e..33103071 100644 --- a/tests/unit/reactive/test_config_parser.py +++ b/tests/unit/reactive/test_config_parser.py @@ -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": { diff --git a/tests/unit/reactive/test_route_bridge.py b/tests/unit/reactive/test_route_bridge.py index ee56ca8e..ee64fa6b 100644 --- a/tests/unit/reactive/test_route_bridge.py +++ b/tests/unit/reactive/test_route_bridge.py @@ -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" diff --git a/tests/unit/reactive/test_stream_router.py b/tests/unit/reactive/test_stream_router.py index f51f800f..05bb356c 100644 --- a/tests/unit/reactive/test_stream_router.py +++ b/tests/unit/reactive/test_stream_router.py @@ -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: diff --git a/tests/unit/templates/test_agent_templates.py b/tests/unit/templates/test_agent_templates.py index 792c1ddd..a04cc23e 100644 --- a/tests/unit/templates/test_agent_templates.py +++ b/tests/unit/templates/test_agent_templates.py @@ -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 diff --git a/tests/unit/templates/test_base.py b/tests/unit/templates/test_base.py index f3d0efd3..3cd97b32 100644 --- a/tests/unit/templates/test_base.py +++ b/tests/unit/templates/test_base.py @@ -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, diff --git a/tests/unit/templates/test_deferred_template.py b/tests/unit/templates/test_deferred_template.py index 36ec9dc6..75932364 100644 --- a/tests/unit/templates/test_deferred_template.py +++ b/tests/unit/templates/test_deferred_template.py @@ -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, diff --git a/tests/unit/templates/test_enhanced_registry.py b/tests/unit/templates/test_enhanced_registry.py index 0e22601b..7bcc5620 100644 --- a/tests/unit/templates/test_enhanced_registry.py +++ b/tests/unit/templates/test_enhanced_registry.py @@ -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) diff --git a/tests/unit/templates/test_graph_templates.py b/tests/unit/templates/test_graph_templates.py index 5e5cf1cd..3a500b05 100644 --- a/tests/unit/templates/test_graph_templates.py +++ b/tests/unit/templates/test_graph_templates.py @@ -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 diff --git a/tests/unit/templates/test_inline_jinja_handler.py b/tests/unit/templates/test_inline_jinja_handler.py index 7a5cd15f..1f3e2b3c 100644 --- a/tests/unit/templates/test_inline_jinja_handler.py +++ b/tests/unit/templates/test_inline_jinja_handler.py @@ -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) diff --git a/tests/unit/templates/test_inline_yaml_jinja.py b/tests/unit/templates/test_inline_yaml_jinja.py index 273ecc9d..c14eec9f 100644 --- a/tests/unit/templates/test_inline_yaml_jinja.py +++ b/tests/unit/templates/test_inline_yaml_jinja.py @@ -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) diff --git a/tests/unit/templates/test_jinja_yaml_preprocessor.py b/tests/unit/templates/test_jinja_yaml_preprocessor.py index eb8b1cda..3e4523a9 100644 --- a/tests/unit/templates/test_jinja_yaml_preprocessor.py +++ b/tests/unit/templates/test_jinja_yaml_preprocessor.py @@ -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) diff --git a/tests/unit/templates/test_loaders.py b/tests/unit/templates/test_loaders.py index 74148cca..358ac9a6 100644 --- a/tests/unit/templates/test_loaders.py +++ b/tests/unit/templates/test_loaders.py @@ -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 diff --git a/tests/unit/templates/test_registry.py b/tests/unit/templates/test_registry.py index a916953b..c0e6c75b 100644 --- a/tests/unit/templates/test_registry.py +++ b/tests/unit/templates/test_registry.py @@ -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": {}} diff --git a/tests/unit/templates/test_renderer.py b/tests/unit/templates/test_renderer.py index ffb91f8a..0e60d4af 100644 --- a/tests/unit/templates/test_renderer.py +++ b/tests/unit/templates/test_renderer.py @@ -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__": diff --git a/tests/unit/templates/test_smart_yaml_loader.py b/tests/unit/templates/test_smart_yaml_loader.py index 4ec50c38..070e79dd 100644 --- a/tests/unit/templates/test_smart_yaml_loader.py +++ b/tests/unit/templates/test_smart_yaml_loader.py @@ -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.""" diff --git a/tests/unit/templates/test_stream_templates.py b/tests/unit/templates/test_stream_templates.py index 675a3eca..22619300 100644 --- a/tests/unit/templates/test_stream_templates.py +++ b/tests/unit/templates/test_stream_templates.py @@ -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, ) diff --git a/tests/unit/templates/test_template_store.py b/tests/unit/templates/test_template_store.py index 2e34a978..4b6055cf 100644 --- a/tests/unit/templates/test_template_store.py +++ b/tests/unit/templates/test_template_store.py @@ -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"}) diff --git a/tests/unit/templates/test_yaml_jinja_loader.py b/tests/unit/templates/test_yaml_jinja_loader.py index 13dcdbb7..c18d478b 100644 --- a/tests/unit/templates/test_yaml_jinja_loader.py +++ b/tests/unit/templates/test_yaml_jinja_loader.py @@ -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 diff --git a/tests/unit/templates/test_yaml_preprocessor.py b/tests/unit/templates/test_yaml_preprocessor.py index 27e9ef4a..bf05974e 100644 --- a/tests/unit/templates/test_yaml_preprocessor.py +++ b/tests/unit/templates/test_yaml_preprocessor.py @@ -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) diff --git a/tests/unit/templates/test_yaml_template_engine.py b/tests/unit/templates/test_yaml_template_engine.py index 114a4084..9c5385f0 100644 --- a/tests/unit/templates/test_yaml_template_engine.py +++ b/tests/unit/templates/test_yaml_template_engine.py @@ -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)