use super::*;
#[test]
fn automation_trigger_parses_task_notification() {
let r = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>wh1it9jlj</task-id>\n<tool-use-id>toolu_x</tool-use-id>\n<output-file>/tmp/x.output</output-file>\n<status>completed</status>\n<summary>Dynamic workflow \"READ-ONLY: verify csift files\" completed</summary>\n</task-notification>"}}"#,
);
assert!(
r.is_genuine_user(),
"a task-notification passes the genuine-user gate"
);
assert!(r.opens_turn());
let t = r.automation_trigger().expect("classified as automation");
assert_eq!(t.task_id.as_deref(), Some("wh1it9jlj"));
assert_eq!(t.status.as_deref(), Some("completed"));
assert_eq!(
t.kind,
AutomationKind::Workflow,
"Dynamic workflow → workflow"
);
assert_eq!(
t.summary.as_deref(),
Some("Dynamic workflow \"READ-ONLY: verify csift files\" completed")
);
let label = r.automation_label().unwrap();
assert!(
label.starts_with("[workflow wh1it9jlj completed]"),
"got: {label}"
);
assert!(
label.contains("Dynamic workflow"),
"summary in label: {label}"
);
assert!(
!label.contains("<task-id>"),
"raw XML must not leak: {label}"
);
}
#[test]
fn automation_trigger_none_for_human_and_partial_graceful() {
let human =
parse(r#"{"type":"user","message":{"role":"user","content":"please fix the bug"}}"#);
assert!(human.automation_trigger().is_none());
assert!(human.automation_label().is_none());
let partial = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>abc</task-id>\n</task-notification>"}}"#,
);
let label = partial.automation_label().unwrap();
assert_eq!(label, "[task abc completed]");
}
#[test]
fn automation_kind_classifies_background_command_and_agent() {
let bg = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>b497m4ncp</task-id>\n<status>completed</status>\n<summary>Background command \"build venvs\" completed (exit code 0)</summary>\n</task-notification>"}}"#,
);
let t = bg.automation_trigger().unwrap();
assert_eq!(t.kind, AutomationKind::BackgroundCommand);
assert!(
bg.automation_label()
.unwrap()
.starts_with("[background-command b497m4ncp completed]"),
"got: {:?}",
bg.automation_label()
);
let ag = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>ag1</task-id>\n<status>completed</status>\n<summary>Agent executor finished its task</summary>\n</task-notification>"}}"#,
);
assert_eq!(ag.automation_trigger().unwrap().kind, AutomationKind::Agent);
assert!(ag
.automation_label()
.unwrap()
.starts_with("[agent ag1 completed]"));
let bgf = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>b0855naiz</task-id>\n<status>failed</status>\n<summary>Background command \"Launch the overnight guard\" failed with exit code 1</summary>\n</task-notification>"}}"#,
);
assert!(bgf
.automation_label()
.unwrap()
.starts_with("[background-command b0855naiz failed]"));
}
#[test]
fn monitor_cadence_event_replaces_fabricated_completed_status() {
let mon = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>b718g3gqq</task-id>\n<summary>Monitor event: \"full test suite re-run completion\"</summary>\n<event>STAGE2_OUTPUT_READY</event>\n</task-notification>"}}"#,
);
let t = mon.automation_trigger().expect("a monitor trigger");
assert_eq!(t.kind, AutomationKind::Monitor);
assert_eq!(t.status, None, "this monitor pulse carries no <status>");
assert_eq!(t.event.as_deref(), Some("STAGE2_OUTPUT_READY"));
let label = mon.automation_label().unwrap();
assert!(
label.starts_with("[monitor b718g3gqq STAGE2_OUTPUT_READY]"),
"event must replace fabricated `completed`: {label}"
);
assert!(
!label.contains("completed"),
"no fabricated `completed` when an event is present: {label}"
);
let timeout = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>q9</task-id>\n<summary>Monitor tick</summary>\n<event>[Monitor timed out — re-arm if needed.]</event>\n</task-notification>"}}"#,
);
let label2 = timeout.automation_label().unwrap();
assert!(label2.contains("Monitor timed out"), "got: {label2}");
assert!(!label2.contains("completed"), "got: {label2}");
let bare = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>z</task-id>\n<summary>Monitor tick</summary>\n</task-notification>"}}"#,
);
assert_eq!(
bare.automation_label().unwrap(),
"[monitor z completed] Monitor tick"
);
let both = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>w</task-id>\n<status>failed</status>\n<summary>Monitor tick</summary>\n<event>SOME_EVENT</event>\n</task-notification>"}}"#,
);
assert!(both
.automation_label()
.unwrap()
.starts_with("[monitor w failed]"));
}
#[test]
fn automation_kind_from_summary_direct() {
use AutomationKind::*;
assert_eq!(
AutomationKind::from_summary(Some("Background command \"x\"")),
BackgroundCommand
);
assert_eq!(
AutomationKind::from_summary(Some("Dynamic workflow \"x\"")),
Workflow
);
assert_eq!(
AutomationKind::from_summary(Some("workflow run done")),
Workflow
);
assert_eq!(AutomationKind::from_summary(Some("Agent x done")), Agent);
assert_eq!(
AutomationKind::from_summary(Some(" background command y")),
BackgroundCommand
);
assert_eq!(
AutomationKind::from_summary(Some("Monitor event: \"full test suite re-run\"")),
Monitor
);
assert_eq!(AutomationKind::from_summary(Some("Monitor tick")), Monitor);
assert_eq!(
AutomationKind::from_summary(Some("Scheduled wakeup fired")),
Monitor
);
assert_eq!(AutomationKind::from_summary(Some("cron run")), Monitor);
for s in [
"Background command \"Relaunch monitor timer (cycle 2)\" completed",
"Background command \"Re-arm corrected monitor (full-tree liveness)\" completed",
"Background command \"nightly monitor tick (25min)\"",
"Background command \"Run pre-commit gate\" completed (monitor it for failures)",
"Background command \"Baseline release build\"",
"Background command \"resource monitoring agent\"",
] {
assert_eq!(
AutomationKind::from_summary(Some(s)),
BackgroundCommand,
"{s}"
);
}
assert_eq!(
AutomationKind::from_summary(Some("Monitor \"watch the build\" ended: command exited")),
Monitor
);
assert_eq!(AutomationKind::from_summary(Some("something else")), Task);
assert_eq!(AutomationKind::from_summary(None), Task);
assert_eq!(AutomationKind::from_summary(Some("")), Task);
assert_eq!(BackgroundCommand.slug(), "background-command");
assert_eq!(Workflow.slug(), "workflow");
assert_eq!(Agent.slug(), "agent");
assert_eq!(Monitor.slug(), "monitor");
assert_eq!(Task.slug(), "task");
}
#[test]
fn automation_trigger_multibyte_summary_codepoint_safe() {
let r = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>zh1</task-id>\n<status>completed</status>\n<summary>🤖 batch shipped, please summarize 🎉</summary>\n</task-notification>"}}"#,
);
let t = r.automation_trigger().unwrap();
assert_eq!(
t.summary.as_deref(),
Some("🤖 batch shipped, please summarize 🎉")
);
}
#[test]
fn extract_xml_tag_handles_missing_and_empty() {
assert_eq!(extract_xml_tag("<a>x</a>", "a").as_deref(), Some("x"));
assert_eq!(extract_xml_tag("<a></a>", "a"), None); assert_eq!(extract_xml_tag("<a>x", "a"), None); assert_eq!(extract_xml_tag("no tags here", "a"), None);
}
#[test]
fn plural_word_pick_pinned() {
assert_eq!(plural(0), "s");
assert_eq!(plural(1), "");
assert_eq!(plural(2), "s");
}
#[test]
fn background_command_pulses_never_route_to_monitor() {
use AutomationKind::{BackgroundCommand, Monitor};
for name in [
"monitor loop",
"liveness probe",
"re-arm the watchdog",
"relaunch monitor timer",
"MONITOR (uppercase)",
] {
let s = format!("Background command \"{name}\" completed");
assert_eq!(
AutomationKind::from_summary(Some(&s)),
BackgroundCommand,
"{s}"
);
}
assert_eq!(
AutomationKind::from_summary(Some("Monitor event: \"watch\"")),
Monitor
);
assert_eq!(
AutomationKind::from_summary(Some("monitor \"watch\" ended")),
Monitor
);
}
#[test]
fn automation_label_failed_status_and_no_summary() {
let failed = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>bad7</task-id>\n<status>failed</status>\n<summary>The build broke</summary>\n</task-notification>"}}"#,
);
assert_eq!(
failed.automation_label().as_deref(),
Some("[task bad7 failed] The build broke")
);
let no_sum = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<task-id>q9</task-id>\n<status>running</status>\n<summary></summary>\n</task-notification>"}}"#,
);
assert_eq!(
no_sum.automation_label().as_deref(),
Some("[task q9 running]")
);
let bare = parse(
r#"{"type":"user","message":{"role":"user","content":"<task-notification>\n<summary>just a note</summary>\n</task-notification>"}}"#,
);
assert_eq!(
bare.automation_label().as_deref(),
Some("[task ? completed] just a note")
);
}