use super::{SmDecision, TaskSpec, parse_decision};
#[test]
fn parse_fenced_json() {
let reply = "Here is my plan:\n```json\n{\"action\":\"delegate\",\"tasks\":[\
{\"workdir\":\"/repo\",\"prompt\":\"add login\"},\
{\"workdir\":\"/repo\",\"prompt\":\"write tests\",\"model\":\"sonnet\"}]}\n```\nDone.";
match parse_decision(reply) {
SmDecision::Delegate { tasks } => {
assert_eq!(tasks.len(), 2);
assert_eq!(tasks[0].workdir, "/repo");
assert_eq!(tasks[0].prompt, "add login");
assert_eq!(tasks[1].model.as_deref(), Some("sonnet"));
}
other => panic!("expected Delegate, got {other:?}"),
}
}
#[test]
fn parse_bare_json() {
let reply = "{\"action\":\"delegate\",\"tasks\":[{\"workdir\":\"/r\",\"prompt\":\"do it\"}]}";
assert_eq!(
parse_decision(reply),
SmDecision::Delegate {
tasks: vec![TaskSpec {
workdir: "/r".to_string(),
prompt: "do it".to_string(),
model: None,
}],
}
);
}
#[test]
fn parse_prose_wrapped_json() {
let reply = "I think we should ask first. {\"action\":\"respond\",\
\"message\":\"which repo?\"} Let me know.";
assert_eq!(
parse_decision(reply),
SmDecision::Respond {
message: "which repo?".to_string()
}
);
}
#[test]
fn parse_do_work_action() {
let reply = "{\"action\":\"do_work\",\"summary\":\"I edited main.rs to add the flag\"}";
assert_eq!(
parse_decision(reply),
SmDecision::DoWork {
summary: "I edited main.rs to add the flag".to_string()
}
);
}
#[test]
fn parse_garbage_is_respond() {
let reply = "I am not sure what you mean — can you clarify the goal?";
assert_eq!(
parse_decision(reply),
SmDecision::Respond {
message: "I am not sure what you mean — can you clarify the goal?".to_string()
}
);
}
#[test]
fn parse_both_json_and_bare_fence() {
let reply = "Plan:\n```json\n{\"action\":\"delegate\",\"tasks\":[\
{\"workdir\":\"/repo\",\"prompt\":\"add login\"}]}\n```\n\nAside:\n```\nsome shell\n```";
match parse_decision(reply) {
SmDecision::Delegate { tasks } => {
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].workdir, "/repo");
assert_eq!(tasks[0].prompt, "add login");
}
other => panic!("expected Delegate from the json fence, got {other:?}"),
}
}
#[test]
fn parse_prose_brace_after_json() {
let reply =
"{\"action\":\"delegate\",\"tasks\":[{\"workdir\":\"/r\",\"prompt\":\"go\"}]} see {docs}";
match parse_decision(reply) {
SmDecision::Delegate { tasks } => {
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].prompt, "go");
}
other => panic!("expected Delegate despite trailing prose brace, got {other:?}"),
}
}
#[test]
fn parse_prose_brace_before_json() {
let reply = "Context {see notes}: {\"action\":\"respond\",\"message\":\"hi there\"}";
assert_eq!(
parse_decision(reply),
SmDecision::Respond {
message: "hi there".to_string()
}
);
}
#[test]
fn parse_brace_inside_string_value() {
let reply = "{\"action\":\"delegate\",\"tasks\":[\
{\"workdir\":\"/r\",\"prompt\":\"render {a:{b:1}} and ship\"}]}";
match parse_decision(reply) {
SmDecision::Delegate { tasks } => {
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].prompt, "render {a:{b:1}} and ship");
}
other => panic!("expected Delegate with braces inside string, got {other:?}"),
}
}
#[test]
fn parse_escaped_quote_in_string() {
let reply = r#"{"action":"respond","message":"say \"hi\" and {wave}"}"#;
assert_eq!(
parse_decision(reply),
SmDecision::Respond {
message: "say \"hi\" and {wave}".to_string()
}
);
}
#[test]
fn task_launchable_predicate() {
let blank = TaskSpec {
workdir: " ".to_string(),
prompt: "x".to_string(),
model: None,
};
let full = TaskSpec {
workdir: "/r".to_string(),
prompt: "x".to_string(),
model: None,
};
assert!(!blank.is_launchable());
assert!(full.is_launchable());
}