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
94 changes: 94 additions & 0 deletions src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,3 +1000,97 @@ topic claims_processing:
let file = result.unwrap();
assert_eq!(file.topics.len(), 2, "Should have 2 topics");
}

// ============================================================================
// Parser Error Path Tests
// ============================================================================

#[test]
fn test_parse_error_non_string_agent_name() {
// `agent_name` requires a quoted string literal. Providing a bare identifier
// must cause a parse error — `parse()` must return `Err`, not `Ok`.
let source = r#"config:
agent_name: NotAString
"#;
let result = parse(source);
assert!(
result.is_err(),
"Expected parse error for non-string agent_name, but parse succeeded"
);
}

#[test]
fn test_parse_error_number_literal_as_agent_name() {
// agent_name requires a string literal. A numeric literal is a different token
// type (NumberLit), so the parser must report an error.
let source = "config:\n agent_name: 42\n";
let result = parse(source);
assert!(
result.is_err(),
"Expected parse error for numeric agent_name, but parse succeeded"
);
}

// ============================================================================
// Expression Edge-Case Tests
// ============================================================================

#[test]
fn test_parse_unary_negation_in_variable_default() {
// Unary negation (`-`) in a variable default value exercises the `UnaryOp::Neg`
// branch of the expression parser, which has no other isolated test coverage.
let source = r#"config:
agent_name: "NegTest"

variables:
offset: mutable number = -1
description: "A negative offset"

topic main:
description: "Main"
"#;
let result = parse(source);
assert!(
result.is_ok(),
"Expected successful parse with unary negation default: {:?}",
result.err()
);
let file = result.unwrap();
let vars = file.variables.expect("Expected variables block");
assert_eq!(vars.node.variables.len(), 1, "Expected one variable");
let var = &vars.node.variables[0].node;
assert_eq!(var.name.node, "offset");
// The default must be present (value = -1, represented as UnaryOp::Neg)
assert!(var.default.is_some(), "Expected a default value for 'offset'");
}

#[test]
fn test_parse_ternary_expression_in_set_statement() {
// Ternary expression (`then_val if condition else else_val`) in a
// `before_reasoning:` set statement. The `Expr::Ternary` variant is defined and
// serialized but had no test coverage for parsing.
let source = r#"config:
agent_name: "TernaryTest"

variables:
flag: mutable boolean = True
label: mutable string = ""

topic main:
description: "Main"
before_reasoning:
set @variables.label = "active" if @variables.flag else "inactive"
reasoning:
instructions: "Help the user."
"#;
let result = parse(source);
assert!(
result.is_ok(),
"Expected successful parse with ternary expression in set: {:?}",
result.err()
);
let file = result.unwrap();
assert_eq!(file.topics.len(), 1);
let topic = &file.topics[0].node;
assert!(topic.before_reasoning.is_some(), "Expected before_reasoning block");
}
108 changes: 108 additions & 0 deletions tests/test_serializer_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,111 @@ 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 `system:` block containing both `messages:` (welcome + error) and
// `instructions:`. No prior roundtrip test exercised the messages sub-block.
let original = r#"config:
agent_name: "MsgAgent"

system:
instructions: "You are a helpful assistant."
messages:
welcome: "Hello! How can I help you today?"
error: "Something went wrong. 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 sub-block");
assert!(serialized.contains("welcome:"), "Missing welcome entry");
assert!(serialized.contains("error:"), "Missing error entry");

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

#[test]
fn test_roundtrip_linked_variable() {
// Covers a `linked` variable declaration with a `source:` reference binding.
// Previous roundtrip tests only covered `mutable` variables.
use busbar_sf_agentscript::ast::VariableKind;

let original = r#"config:
agent_name: "LinkedAgent"

variables:
user_email: linked string
description: "Email of the logged-in user"
source: @messagingSession.userEmail

topic main:
description: "Main"
"#;

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

assert!(serialized.contains("linked"), "Missing 'linked' keyword in serialized output");

let reparsed = parse(&serialized).expect("Failed to reparse serialized");
let vars = reparsed.variables.as_ref().expect("variables block lost after roundtrip");
assert_eq!(vars.node.variables.len(), 1, "Expected one variable after roundtrip");
let var = &vars.node.variables[0].node;
assert_eq!(var.name.node, "user_email");
assert!(
matches!(var.kind, VariableKind::Linked),
"Variable kind changed from Linked after roundtrip"
);
assert!(var.source.is_some(), "source reference lost after roundtrip");
}

#[test]
fn test_roundtrip_topic_with_system_override() {
// Covers a `topic` block that contains a `system:` override block. The override
// sets topic-scoped instructions that differ from the global system block.
// No prior roundtrip test exercised the per-topic system override.
let original = r#"config:
agent_name: "SysOverrideAgent"

topic support:
description: "Support topic"

system:
instructions: "You are now in support mode. Focus on resolving customer issues quickly."

reasoning:
instructions: "Help the user resolve their issue."
"#;

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

assert!(
serialized.contains("system:"),
"Missing system override in serialized output"
);
assert!(
serialized.contains("support mode"),
"Missing system override instruction text"
);

let reparsed = parse(&serialized).expect("Failed to reparse serialized");
assert_eq!(reparsed.topics.len(), 1);
let topic = &reparsed.topics[0].node;
assert!(topic.system.is_some(), "topic system override lost after roundtrip");
assert!(
topic.system.as_ref().unwrap().node.instructions.is_some(),
"system override instructions lost after roundtrip"
);
}