diff --git a/src/graph/queries.rs b/src/graph/queries.rs index 7508e45..660531a 100644 --- a/src/graph/queries.rs +++ b/src/graph/queries.rs @@ -347,4 +347,52 @@ topic main: // At least one edge should exist (the Routes edge from start_agent → main) assert!(graph.edge_count() > 0, "Expected at least one edge in the graph"); } + + #[test] + fn test_find_usages_of_transition_target_topic() { + // topic_a has a TransitionsTo edge → topic_b. + // find_usages(topic_b_idx) must include topic_a as a source of that edge, + // because any node whose outgoing edge terminates at topic_b "uses" topic_b. + let graph = parse_and_build(two_topic_source()); + let topic_a_idx = graph.get_topic("topic_a").expect("topic_a not found"); + let topic_b_idx = graph.get_topic("topic_b").expect("topic_b not found"); + + let usages = graph.find_usages(topic_b_idx); + assert!( + usages.nodes.contains(&topic_a_idx), + "Expected topic_a to appear as a user of topic_b via TransitionsTo edge" + ); + } + + #[test] + fn test_find_usages_of_topic_routed_from_start_agent() { + // start_agent routes to topic_a via a Routes edge. + // find_usages(topic_a_idx) must include the start_agent node as a source, + // because start_agent has an outgoing Routes edge terminating at topic_a. + let graph = parse_and_build(two_topic_source()); + let topic_a_idx = graph.get_topic("topic_a").expect("topic_a not found"); + let start_agent_idx = graph.get_start_agent().expect("start_agent not found"); + + let usages = graph.find_usages(topic_a_idx); + assert!( + usages.nodes.contains(&start_agent_idx), + "Expected start_agent to appear as a user of topic_a via Routes edge" + ); + } + + #[test] + fn test_find_dependencies_of_start_agent_returns_routed_topic() { + // start_agent routes to topic_a via a Routes edge. + // find_dependencies(start_agent_idx) must include topic_a as a dependency, + // because start_agent has an outgoing Routes edge pointing at topic_a. + let graph = parse_and_build(two_topic_source()); + let topic_a_idx = graph.get_topic("topic_a").expect("topic_a not found"); + let start_agent_idx = graph.get_start_agent().expect("start_agent not found"); + + let deps = graph.find_dependencies(start_agent_idx); + assert!( + deps.nodes.contains(&topic_a_idx), + "Expected topic_a to appear as a dependency of start_agent via Routes edge" + ); + } } diff --git a/tests/test_serializer_roundtrip.rs b/tests/test_serializer_roundtrip.rs index cc1c831..420517f 100644 --- a/tests/test_serializer_roundtrip.rs +++ b/tests/test_serializer_roundtrip.rs @@ -216,3 +216,127 @@ topic main: assert!(topic.before_reasoning.is_some(), "before_reasoning lost after roundtrip"); assert!(topic.after_reasoning.is_some(), "after_reasoning lost after roundtrip"); } + +#[test] +fn test_roundtrip_start_agent_basic() { + // Covers the `start_agent` block — the serializer writes it but no roundtrip + // test existed. Verifies that the name and reasoning instructions survive a + // parse → serialize → parse cycle. + let original = r#"config: + agent_name: "TestAgent" + +start_agent topic_selector: + description: "Routes customers to the appropriate topic" + reasoning: + instructions: "Analyze the customer request and select the most relevant topic" +"#; + + let ast = parse(original).expect("Failed to parse original"); + let serialized = serialize(&ast); + + assert!( + serialized.contains("start_agent topic_selector:"), + "start_agent header missing from serialized output" + ); + assert!( + serialized.contains("Routes customers"), + "start_agent description missing from serialized output" + ); + + let reparsed = parse(&serialized).expect("Failed to reparse serialized"); + assert!(reparsed.start_agent.is_some(), "start_agent block lost after roundtrip"); + let sa = &reparsed.start_agent.as_ref().unwrap().node; + assert_eq!(sa.name.node, "topic_selector", "start_agent name changed after roundtrip"); + assert!(sa.reasoning.is_some(), "reasoning block lost from start_agent after roundtrip"); +} + +#[test] +fn test_roundtrip_start_agent_with_reasoning_actions() { + // Verifies that multiple reasoning actions inside `start_agent` survive a + // parse → serialize → parse cycle without being dropped or duplicated. + let original = r#"config: + agent_name: "TestAgent" + +start_agent topic_selector: + description: "Routes customers" + reasoning: + instructions: "Select the best topic" + actions: + go_help: @utils.transition to @topic.help + description: "Route to help" + go_orders: @utils.transition to @topic.orders + description: "Route to orders" + +topic help: + description: "Help" + +topic orders: + description: "Orders" +"#; + + let ast = parse(original).expect("Failed to parse original"); + let serialized = serialize(&ast); + let reparsed = parse(&serialized).expect("Failed to reparse serialized"); + + let sa = reparsed + .start_agent + .as_ref() + .expect("start_agent lost after roundtrip"); + let reasoning = sa.node.reasoning.as_ref().expect("reasoning lost from start_agent"); + let actions = reasoning + .node + .actions + .as_ref() + .expect("reasoning actions lost from start_agent"); + assert_eq!( + actions.node.len(), + 2, + "Expected 2 reasoning actions in start_agent after roundtrip, got {}", + actions.node.len() + ); +} + +#[test] +fn test_roundtrip_system_block_with_messages() { + // Covers the top-level `system:` block with a `messages:` sub-block. + // The serializer writes messages but no roundtrip test existed. Verifies + // that the welcome and error messages survive a parse → serialize → parse cycle. + let original = r#"config: + agent_name: "TestAgent" + +system: + messages: + welcome: "Welcome to our service!" + error: "Something went wrong." + instructions: "You are a helpful AI assistant." + +topic main: + description: "Main topic" +"#; + + let ast = parse(original).expect("Failed to parse original"); + let serialized = serialize(&ast); + + assert!(serialized.contains("system:"), "system block missing from serialized output"); + assert!(serialized.contains("messages:"), "messages sub-block missing"); + assert!( + serialized.contains("Welcome to our service!"), + "welcome message missing from serialized output" + ); + + let reparsed = parse(&serialized).expect("Failed to reparse serialized"); + let sys = reparsed + .system + .as_ref() + .expect("system block lost after roundtrip"); + let msgs = sys + .node + .messages + .as_ref() + .expect("messages sub-block lost after roundtrip"); + let welcome = msgs.node.welcome.as_ref().expect("welcome message lost after roundtrip"); + assert_eq!( + welcome.node, "Welcome to our service!", + "welcome message content changed after roundtrip" + ); +}