use super::*;
#[test]
fn json_and_ndjson_share_one_serde_path() {
let event = RunEvent::Paused {
reason: PauseReason::BudgetExhausted,
};
let json = render_run_event(&event, RenderFormat::Json);
let ndjson = render_run_event(&event, RenderFormat::Ndjson);
assert_eq!(
json.stdout, ndjson.stdout,
"one serialization, two framings"
);
assert_eq!(ndjson.stdout, format!("{}\n", journal_line(&event)));
assert!(ndjson.stdout.contains(r#""type":"paused""#), "{ndjson:?}");
assert!(ndjson.stdout.contains("budget_exhausted"), "{ndjson:?}");
}
#[test]
fn lifecycle_lines_render_distinctly() {
let lines = [
RunEvent::RunStarted,
RunEvent::PlanApproved { scopes: None },
RunEvent::StepStarted { step: 0 },
RunEvent::StepCompleted { step: 0 },
RunEvent::StepFailed { step: 0 },
RunEvent::Paused {
reason: PauseReason::WallClockExceeded,
},
RunEvent::Completed,
RunEvent::Failed {
code: RunFailureCode::SafetyQuery,
},
RunEvent::Cancelled,
]
.map(|event| run_event_text(&event));
for (index, line) in lines.iter().enumerate() {
assert!(line.ends_with('\n'), "line {index} is newline-terminated");
}
for a in 0..lines.len() {
for b in (a + 1)..lines.len() {
assert_ne!(lines[a], lines[b], "lines {a} and {b} must differ");
}
}
assert_eq!(lines[2], "step 1 started\n", "steps render one-based");
assert!(
lines[5].contains("the wall-clock budget tripped"),
"{lines:?}"
);
}
#[test]
fn unreported_usage_renders_unknown_not_zero() {
let event = RunEvent::Usage {
endpoint: "primary".into(),
tokens: Some(12),
turns: None,
tool_calls: None,
cached_input_tokens: None,
cache_creation_input_tokens: None,
};
let text = run_event_text(&event);
assert!(text.contains("tokens 12"), "{text}");
assert!(text.contains("turns unknown"), "{text}");
assert!(text.contains("tool calls unknown"), "{text}");
assert!(!text.contains("0"), "no zero is invented: {text}");
}
#[test]
fn the_download_level_line_states_the_running_spend() {
let text = run_event_text(&RunEvent::DownloadedBytes { bytes: 97 });
assert!(text.contains("97"), "{text}");
assert!(text.ends_with('\n'), "line is newline-terminated");
}
#[test]
fn the_show_stanza_omits_the_usage_section_when_the_run_recorded_none() {
let bare = run_show_text(RunShowStanza {
id: "r-3",
status: "completed",
failure_cause: None,
created_unix_ms: 1,
updated_unix_ms: 2,
spec: None,
paused: None,
deliverables: &[],
usage: &[],
});
assert!(!bare.contains("usage"), "no section without usage: {bare}");
}
#[test]
fn the_show_stanza_names_cause_and_pause() {
let text = run_show_text(RunShowStanza {
id: "r-1",
status: "failed",
failure_cause: Some(failure_code_cause(RunFailureCode::SafetyQuery)),
created_unix_ms: 100,
updated_unix_ms: 200,
spec: Some(("the goal", "workspace-write")),
paused: Some(PauseReason::StoreUnavailable),
deliverables: &[],
usage: &[],
});
assert_eq!(
text,
"run r-1\nstatus: failed (the safety gate refused a query)\ncreated: 100\n\
updated: 200\ngoal: the goal\nscopes: workspace-write\n\
paused: the state store became unavailable"
);
}
#[test]
fn the_show_stanza_carries_deliverables_and_omits_the_section_when_there_are_none() {
let bare = run_show_text(RunShowStanza {
id: "r-2",
status: "completed",
failure_cause: None,
created_unix_ms: 1,
updated_unix_ms: 2,
spec: None,
paused: None,
deliverables: &[],
usage: &[],
});
assert!(
!bare.contains("deliverables"),
"no section without deliverables: {bare}"
);
let listed = run_show_text(RunShowStanza {
id: "r-2",
status: "completed",
failure_cause: None,
created_unix_ms: 1,
updated_unix_ms: 2,
spec: None,
paused: None,
deliverables: &["step 0: report.md (29 bytes, sha256 4634)".to_string()],
usage: &[],
});
assert!(
listed.ends_with("\ndeliverables:\nstep 0: report.md (29 bytes, sha256 4634)"),
"deliverables close the stanza: {listed}"
);
}