"""Route module comprehensive coverage steps (ported).""" from __future__ import annotations from pathlib import Path from behave import given, then, when from cleveragents.core.exceptions import ConfigurationError from cleveragents.langgraph.graph import GraphConfig from cleveragents.langgraph.nodes import Edge, NodeConfig, NodeType from cleveragents.reactive.route import ( BridgeConfig, RouteComplexityAnalyzer, RouteConfig, RouteType, ) from cleveragents.reactive.stream_router import StreamConfig, StreamType @given("the route system is available") def step_route_system_available(context): context.route_configs = {} context.stream_configs = {} context.graph_configs = {} context.analysis_results = {} context.errors = {} @given("I create a RouteConfig with graph type but no nodes") def step_create_graph_config_no_nodes(context): context.test_config_params = { "name": "test_graph", "type": RouteType.GRAPH, "nodes": {}, } @when("I validate the configuration") def step_validate_configuration(context): try: context.test_config = RouteConfig(**context.test_config_params) context.validation_error = None except Exception as exc: # pylint: disable=broad-except context.validation_error = exc @then("I should get a ConfigurationError about missing nodes") def step_verify_missing_nodes_error(context): assert context.validation_error is not None assert isinstance(context.validation_error, ConfigurationError) assert "must have nodes defined" in str(context.validation_error) @given("I create a RouteConfig with graph type") def step_create_graph_route_config(context): context.test_config = RouteConfig( name="test_graph", type=RouteType.GRAPH, nodes={"test_node": {"type": "agent"}}, edges=[{"source": "start", "target": "test_node"}], ) @when("I try to convert it to StreamConfig") def step_convert_to_stream_config(context): try: context.conversion_result = context.test_config.to_stream_config() context.conversion_error = None except Exception as exc: # pylint: disable=broad-except context.conversion_error = exc @then("I should get a ValueError about invalid conversion") def step_verify_invalid_conversion_error(context): assert context.conversion_error is not None assert isinstance(context.conversion_error, ValueError) assert "Cannot convert" in str(context.conversion_error) @given("I create a RouteConfig with stream type") def step_create_stream_route_config(context): context.test_config = RouteConfig( name="test_stream", type=RouteType.STREAM, stream_type=StreamType.COLD, operators=[{"type": "map"}], ) @when("I try to convert it to GraphConfig") def step_convert_to_graph_config(context): try: context.conversion_result = context.test_config.to_graph_config() context.conversion_error = None except Exception as exc: # pylint: disable=broad-except context.conversion_error = exc @given("I have a valid StreamConfig") def step_create_valid_stream_config(context): context.source_stream_config = StreamConfig( name="test_stream", type=StreamType.HOT, operators=[ {"type": "map", "params": {"function": "transform"}}, {"type": "filter", "params": {"predicate": "is_valid"}}, ], subscriptions=["input_topic"], publications=["output_topic"], agents=["agent1", "agent2"], initial_value="initial", buffer_size=10, template_config={"template": "test"}, ) @when("I create a RouteConfig from the StreamConfig") def step_create_route_from_stream(context): context.result_route_config = RouteConfig.from_stream_config( context.source_stream_config ) @then("the RouteConfig should have stream type") def step_verify_stream_type(context): assert context.result_route_config.type == RouteType.STREAM @then("it should preserve all stream configuration details") def step_verify_stream_details_preserved(context): route = context.result_route_config source = context.source_stream_config assert route.name == source.name assert route.stream_type == source.type assert route.operators == source.operators assert route.subscriptions == source.subscriptions assert route.publications == source.publications assert route.agents == source.agents assert route.initial_value == source.initial_value assert route.buffer_size == source.buffer_size assert route.template_config == source.template_config @given("I have a valid GraphConfig with nodes and edges") def step_create_valid_graph_config(context): node1 = NodeConfig( name="node1", type=NodeType.AGENT, agent="test_agent", tools=["tool1", "tool2"], retry_policy={"max_retries": 3}, timeout=30, parallel=True, metadata={"key": "value"}, ) node2 = NodeConfig( name="node2", type=NodeType.FUNCTION, function="test_function", condition={"type": "test_condition"}, subgraph="test_subgraph", ) edge1 = Edge( source="start", target="node1", condition={"type": "edge_condition"}, metadata={"edge_key": "edge_value"}, ) edge2 = Edge(source="node1", target="node2") context.source_graph_config = GraphConfig( name="test_graph", nodes={"node1": node1, "node2": node2}, edges=[edge1, edge2], entry_point="start", state_class=None, checkpointing=True, checkpoint_dir=Path("/tmp/checkpoints"), enable_time_travel=True, parallel_execution=True, metadata={"graph_meta": "meta"}, ) @when("I create a RouteConfig from the GraphConfig") def step_create_route_from_graph(context): context.result_route_config = RouteConfig.from_graph_config( context.source_graph_config ) @then("the RouteConfig should have graph type") def step_verify_graph_type(context): assert context.result_route_config.type == RouteType.GRAPH @then("it should preserve all graph configuration details") def step_verify_graph_details_preserved(context): route = context.result_route_config source = context.source_graph_config assert route.name == source.name assert route.nodes assert route.edges assert route.entry_point == source.entry_point assert route.checkpointing == source.checkpointing assert ( (route.checkpoint_dir == str(source.checkpoint_dir)) if source.checkpoint_dir else True ) assert route.enable_time_travel == source.enable_time_travel assert route.parallel_execution == source.parallel_execution assert route.metadata == source.metadata @then("node configurations should be converted to dictionaries") def step_verify_node_conversion(context): assert all(isinstance(v, dict) for v in context.result_route_config.nodes.values()) @then("edge configurations should be converted to dictionaries") def step_verify_edge_conversion(context): assert all(isinstance(e, dict) for e in context.result_route_config.edges) @given("I have a RouteConfig with bridge type") def step_bridge_type_config(context): context.bridge_route = RouteConfig( name="bridge_route", type=RouteType.BRIDGE, bridge=BridgeConfig() ) @when("I analyze the route complexity") def step_analyze_route_complexity(context): context.analysis = RouteComplexityAnalyzer.analyze_route(context.bridge_route) @then("it should return bridge complexity analysis") def step_bridge_analysis(context): assert context.analysis.get("complexity") == "bridge" @then('the complexity should be "{expected}"') def step_complexity_value(context, expected): assert str(context.analysis.get("complexity")) == expected @then("the score should be {score:d}") def step_score_value(context, score): assert int(context.analysis.get("score", -1)) == score @given("I have stream routes with different features") def step_stream_routes_features(context): context.stream_routes = [ RouteConfig(name="simple", type=RouteType.STREAM, operators=[]), RouteConfig( name="ops", type=RouteType.STREAM, operators=[{"type": "map"}, {"type": "filter"}], ), RouteConfig( name="connected", type=RouteType.STREAM, operators=[{"type": "map"}], subscriptions=["s1"], publications=["s2"], ), RouteConfig( name="hot", type=RouteType.STREAM, stream_type=StreamType.HOT, operators=[] ), ] @when("I analyze each route complexity") def step_analyze_each_route(context): context.analysis_results = [ RouteComplexityAnalyzer.analyze_route(rc) for rc in context.stream_routes ] @then("routes with more operators should have higher complexity scores") def step_routes_more_ops_score(context): scores = [a["score"] for a in context.analysis_results] assert max(scores) >= min(scores) @then("routes with routing connections should get additional score") def step_routes_connections_score(context): scores = [a["score"] for a in context.analysis_results] assert scores[2] >= scores[1] @then("hot streams should get additional score") def step_hot_stream_score(context): scores = [a["score"] for a in context.analysis_results] assert scores[3] >= scores[0] @then("the complexity classification should match the score ranges") def step_complexity_classification(context): for analysis in context.analysis_results: c = analysis["complexity"] s = analysis["score"] if s <= 2: assert c == "simple" elif s <= 5: assert c in ("simple", "moderate") else: assert c in ("moderate", "complex") @given("I have graph routes with conditional edges") def step_graph_routes_conditionals(context): context.graph_routes = [ RouteConfig( name="cond_graph", type=RouteType.GRAPH, nodes={"n1": {"type": "agent"}, "n2": {"type": "function"}}, edges=[{"source": "n1", "target": "n2", "condition": {"field": "x"}}], checkpointing=True, enable_time_travel=True, ) ] @when("I analyze the graph complexity") def step_analyze_graph_complexity(context): context.analysis_results = [ RouteComplexityAnalyzer.analyze_route(rc) for rc in context.graph_routes ] @then("conditional edges should increase the complexity score") def step_cond_edges_score(context): assert context.analysis_results[0]["score"] >= 9 @then("the features should include conditional edge information") def step_cond_edges_features(context): assert any("conditional" in f for f in context.analysis_results[0]["features"]) @then("checkpointing should increase the score") def step_checkpointing_score(context): assert "checkpointing enabled" in context.analysis_results[0]["features"] @then("time travel should increase the score") def step_time_travel_score(context): assert "time travel enabled" in context.analysis_results[0]["features"] @given("I have streams with different complexity scores") def step_streams_for_recommendations(context): context.stream_scores = [ RouteComplexityAnalyzer._get_stream_recommendation(1), RouteComplexityAnalyzer._get_stream_recommendation(3), RouteComplexityAnalyzer._get_stream_recommendation(6), ] @when("I get recommendations for each stream") def step_get_stream_recs(context): context.recs = context.stream_scores @then("simple streams should get simple transformation recommendation") def step_simple_stream_rec(context): assert "simple" in context.recs[0].lower() @then("moderate streams should get multi-step processing recommendation") def step_moderate_stream_rec(context): assert ( "multi-step" in context.recs[1].lower() or "pipelines" in context.recs[1].lower() ) @then("complex streams should get graph consideration recommendation") def step_complex_stream_rec(context): assert "graph" in context.recs[2].lower() or "consider" in context.recs[2].lower() @given("I have graphs with different complexity scores") def step_graphs_for_recommendations(context): context.graph_scores = [ RouteComplexityAnalyzer._get_graph_recommendation(8), RouteComplexityAnalyzer._get_graph_recommendation(12), RouteComplexityAnalyzer._get_graph_recommendation(18), ] @when("I get recommendations for each graph") def step_get_graph_recs(context): context.graph_recs = context.graph_scores @then("moderate graphs should get conditional logic recommendation") def step_moderate_graph_rec(context): assert "conditional" in context.graph_recs[0].lower() @then("complex graphs should get stateful workflow recommendation") def step_complex_graph_rec(context): assert "stateful" in context.graph_recs[1].lower() @then("advanced graphs should get feature evaluation recommendation") def step_advanced_graph_rec(context): assert ( "advanced" in context.graph_recs[2].lower() or "ensure" in context.graph_recs[2].lower() ) @given("I have different requirement scenarios") def step_requirement_scenarios(context): context.requirements = [ {"needs_persistence": True}, {"needs_state": True, "needs_conditionals": True}, {"needs_conditionals": True, "is_continuous": False}, {"is_stateless": True, "is_continuous": True}, {}, ] @when("I ask for route type suggestions") def step_suggest_route_types(context): context.suggestions = [ RouteComplexityAnalyzer.suggest_route_type(req) for req in context.requirements ] @then("persistence requirements should suggest graph") def step_persistence_suggests_graph(context): assert context.suggestions[0] == RouteType.GRAPH @then("state with conditionals should suggest graph") def step_state_conditionals_suggest_graph(context): assert context.suggestions[1] == RouteType.GRAPH @then("conditionals without continuous should suggest graph") def step_conditionals_no_continuous_graph(context): assert context.suggestions[2] == RouteType.GRAPH @then("stateless continuous should suggest stream") def step_stateless_continuous_stream(context): assert context.suggestions[3] == RouteType.STREAM @then("simple cases should default to stream") def step_simple_cases_stream(context): assert context.suggestions[4] == RouteType.STREAM @given("I create a stream RouteConfig without specifying stream_type") def step_stream_without_stream_type(context): context.test_config = RouteConfig(name="default_stream", type=RouteType.STREAM) @when("the post_init validation runs") def step_post_init_validation(context): context.post_init_type = context.test_config.stream_type @then("the stream_type should default to COLD") def step_stream_type_default(context): assert context.post_init_type == StreamType.COLD @given("I have a RouteConfig with various node types and configurations") def step_complex_node_route(context): context.complex_route = RouteConfig( name="complex_graph", type=RouteType.GRAPH, nodes={ "agent_node": { "type": "agent", "agent": "agentA", "retry_policy": {"max_retries": 2}, "timeout": 5, }, "func_node": { "type": "function", "function": "fn1", "parallel": True, "metadata": {"x": 1}, }, "router": { "type": "message_router", "metadata": {"rules": [{"target": "end"}]}, }, }, edges=[ {"source": "start", "target": "agent_node"}, { "source": "agent_node", "target": "func_node", "condition": {"equals": "ok"}, }, {"source": "func_node", "target": "router"}, {"source": "router", "target": "end"}, ], entry_point="start", checkpointing=False, enable_time_travel=False, parallel_execution=True, metadata={"meta": True}, ) @when("I convert it to GraphConfig") def step_convert_complex_route(context): context.graph_config_result = context.complex_route.to_graph_config() @then("all node types should be properly converted") def step_all_nodes_converted(context): assert set(context.graph_config_result.nodes.keys()) == set( context.complex_route.nodes.keys() ) @then("all node properties should be preserved") def step_node_properties_preserved(context): for name, node_cfg in context.graph_config_result.nodes.items(): source = context.complex_route.nodes[name] if "agent" in source: assert node_cfg.agent == source["agent"] if "function" in source: assert node_cfg.function == source["function"] @then("retry policies and timeouts should be included") def step_retry_timeout_included(context): node = context.graph_config_result.nodes["agent_node"] assert node.retry_policy == context.complex_route.nodes["agent_node"].get( "retry_policy" ) assert node.timeout == context.complex_route.nodes["agent_node"].get("timeout") @then("conditions and subgraphs should be handled") def step_conditions_subgraphs(context): edge = next(e for e in context.graph_config_result.edges if e.target == "func_node") assert edge.condition is not None @then("node metadata should be preserved in conversion") def step_metadata_preserved(context): router = context.graph_config_result.nodes["router"] assert router.metadata.get("rules") is not None