Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/graph/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,92 @@ 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");
}

/// Source with a topic that defines an action def and a reasoning action that invokes
/// it, reads a variable, and writes another variable.
fn source_with_invocation() -> &'static str {
r#"config:
agent_name: "Test"

variables:
param_val: mutable string = ""
result: mutable string = ""

topic main:
description: "Main topic"

actions:
do_something:
description: "An action"
inputs:
param: string
description: "Input parameter"
outputs:
data: string
description: "Output data"
target: "flow://DoSomething"

reasoning:
instructions: "Handle"
actions:
invoke_something: @actions.do_something
description: "Invoke do_something"
with param=@variables.param_val
set @variables.result = @outputs.data
"#
}

#[test]
fn test_find_action_invokers() {
// A reasoning action referencing @actions.do_something via an Invokes edge should
// appear in find_action_invokers(do_something_idx).
let graph = parse_and_build(source_with_invocation());
let action_def_idx = graph
.get_action_def("main", "do_something")
.expect("do_something action def not found");
let invokers = graph.find_action_invokers(action_def_idx);
assert_eq!(invokers.len(), 1, "Expected exactly 1 invoker for do_something");
}

#[test]
fn test_find_variable_readers() {
// A reasoning action with `with param=@variables.param_val` creates a Reads edge.
// find_variable_readers should return that action.
let graph = parse_and_build(source_with_invocation());
let var_idx = graph
.get_variable("param_val")
.expect("param_val variable not found");
let readers = graph.find_variable_readers(var_idx);
assert!(!readers.is_empty(), "Expected at least one reader for param_val");
}

#[test]
fn test_find_variable_writers() {
// A reasoning action with `set @variables.result = @outputs.data` creates a Writes
// edge. find_variable_writers should return that action.
let graph = parse_and_build(source_with_invocation());
let var_idx = graph
.get_variable("result")
.expect("result variable not found");
let writers = graph.find_variable_writers(var_idx);
assert!(!writers.is_empty(), "Expected at least one writer for result");
}

#[test]
fn test_get_topic_reasoning_actions() {
// get_topic_reasoning_actions should return the reasoning actions declared in a
// topic's reasoning block (one in this case: invoke_something).
let graph = parse_and_build(source_with_invocation());
let actions = graph.get_topic_reasoning_actions("main");
assert_eq!(actions.len(), 1, "Expected 1 reasoning action in topic main");
}

#[test]
fn test_get_topic_action_defs() {
// get_topic_action_defs should return the action definitions declared in a topic's
// actions block (one in this case: do_something).
let graph = parse_and_build(source_with_invocation());
let defs = graph.get_topic_action_defs("main");
assert_eq!(defs.len(), 1, "Expected 1 action def in topic main");
}
}
92 changes: 92 additions & 0 deletions tests/test_serializer_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,95 @@ 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_system_block_with_messages() {
// Covers the top-level `system:` block containing both `instructions:` and
// `messages:` (welcome + error). The serializer writes these fields but no
// roundtrip test existed — this ensures they survive parse → serialize → parse.
let original = r#"config:
agent_name: "SupportAgent"

system:
instructions: "You are a helpful support agent."

messages:
welcome: "Hello! How can I help you today?"
error: "I had trouble processing your request. Please try again."

topic main:
description: "Main"
"#;

let ast = parse(original).expect("Failed to parse original");
let serialized = serialize(&ast);

assert!(serialized.contains("system:"), "Missing system block in serialized output");
assert!(serialized.contains("messages:"), "Missing messages block in serialized output");

let reparsed = parse(&serialized).expect("Failed to reparse serialized");
assert!(reparsed.system.is_some(), "system block lost after roundtrip");
let system = &reparsed.system.as_ref().unwrap().node;
assert!(system.instructions.is_some(), "instructions lost after roundtrip");
let messages = system.messages.as_ref().expect("messages block lost after roundtrip");
assert!(messages.node.welcome.is_some(), "welcome message lost after roundtrip");
assert!(messages.node.error.is_some(), "error message lost after roundtrip");
}

#[test]
fn test_roundtrip_action_with_various_param_types() {
// Covers action defs with non-string parameter types: `id`, `boolean`, `number`.
// These types are serialized differently from `string` but no roundtrip test
// existed for them.
let original = r#"config:
agent_name: "TypedAgent"

topic main:
description: "Main"
actions:
lookup_record:
description: "Look up a Salesforce record"
inputs:
record_id: id
description: "Salesforce record ID"
is_required: True
include_details: boolean
description: "Whether to include full details"
is_required: False
max_results: number
description: "Maximum number of results to return"
outputs:
found: boolean
description: "Whether the record was found"
score: number
description: "Confidence score"
target: "flow://LookupRecord"
reasoning:
instructions: "Help the user look up records"
"#;

let ast = parse(original).expect("Failed to parse original");
let serialized = serialize(&ast);

assert!(serialized.contains("lookup_record:"), "Missing action def in serialized output");
assert!(serialized.contains(": id"), "Missing id param type");
assert!(serialized.contains(": boolean"), "Missing boolean param type");
assert!(serialized.contains(": number"), "Missing number param type");

let reparsed = parse(&serialized).expect("Failed to reparse serialized");
assert_eq!(reparsed.topics.len(), 1);
let topic = &reparsed.topics[0].node;
let actions = topic.actions.as_ref().expect("Missing actions block after roundtrip");
assert_eq!(actions.node.actions.len(), 1, "Expected 1 action def");
let action = &actions.node.actions[0].node;
assert_eq!(
action.inputs.as_ref().expect("inputs missing").node.len(),
3,
"Expected 3 inputs"
);
assert_eq!(
action.outputs.as_ref().expect("outputs missing").node.len(),
2,
"Expected 2 outputs"
);
}