use tempfile::tempdir;
use super::{AGENT, step_dir, write_file, write_tool};
use crate::git_tree::{AgentState, Framing};
use crate::steps_view::{Doc, build, detail};
const COMPLETE: &[u8] = br#"{"type":"message_start"}
{"type":"usage","input_tokens":10,"output_tokens":5}
{"type":"finish","reason":"stop"}
{"type":"end"}
"#;
const FAILED: &[u8] = br#"{"type":"error","kind":"x"}
{"type":"end"}
"#;
const AUTH_FAILED: &[u8] = br#"{"type":"error","status":401,"message":"Unauthorized"}
{"type":"end"}
"#;
#[test]
fn build_summarizes_steps_in_order_reusing_framing_attempts_tokens() {
let dir = tempdir().unwrap();
let ws = dir.path();
write_file(ws, "002", "response.json", FAILED);
write_file(ws, "001", "response.json", COMPLETE);
write_file(
ws,
"001",
"meta.json",
br#"{"commit":"abcdef1234567890","started_at":"t-start","ended_at":"t-end"}"#,
);
write_file(ws, "003", "meta.json", b"{}");
write_file(ws, "0004", "meta.json", b"{}");
std::fs::create_dir_all(step_dir(ws, "notaseq")).unwrap();
std::fs::write(ws.join("steps").join(AGENT).join("006"), b"x").unwrap();
let view = build(ws, AGENT, AgentState::Stopped);
let seqs: Vec<&str> = view.steps.iter().map(|s| s.seq.as_str()).collect();
assert_eq!(seqs, vec!["001", "002", "003"]);
let s1 = &view.steps[0];
assert_eq!(s1.framing, Framing::Complete);
assert_eq!(s1.attempts, 1);
assert_eq!(s1.tokens.total_tokens(), 15);
assert_eq!(s1.commit.as_deref(), Some("abcdef1234567890"));
assert_eq!(s1.started_at.as_deref(), Some("t-start"));
assert_eq!(s1.ended_at.as_deref(), Some("t-end"));
assert_eq!(view.steps[1].framing, Framing::Failed);
assert_eq!(view.steps[2].framing, Framing::Killed);
assert_eq!(view.steps[2].attempts, 0);
assert_eq!(view.steps[2].commit, None);
}
#[test]
fn build_flags_auth_shaped_step_failures_for_the_login_affordance() {
let dir = tempdir().unwrap();
let ws = dir.path();
write_file(ws, "001", "response.json", COMPLETE);
write_file(ws, "002", "response.json", FAILED); write_file(ws, "003", "response.json", AUTH_FAILED); let view = build(ws, AGENT, AgentState::Stopped);
assert!(!view.steps[0].auth_failed);
assert!(!view.steps[1].auth_failed);
assert!(view.steps[2].auth_failed);
}
#[test]
fn build_missing_tree_is_empty() {
let dir = tempdir().unwrap();
assert_eq!(
build(dir.path(), AGENT, AgentState::Stopped).steps,
Vec::new()
);
}
#[test]
fn build_forgiving_when_meta_is_malformed_or_absent() {
let dir = tempdir().unwrap();
let ws = dir.path();
write_file(ws, "001", "response.json", COMPLETE);
write_file(ws, "001", "meta.json", b"not json at all");
let view = build(ws, AGENT, AgentState::Stopped);
assert_eq!(view.steps[0].commit, None);
assert_eq!(view.steps[0].started_at, None);
assert_eq!(view.steps[0].framing, Framing::Complete);
}
#[test]
fn detail_parses_records_splits_response_and_derives_tool_is_error() {
let dir = tempdir().unwrap();
let ws = dir.path();
write_file(ws, "001", "meta.json", br#"{"commit":"c"}"#);
write_file(ws, "001", "request.json", br#"{"model":"opus"}"#);
write_file(
ws,
"001",
"staging.json",
br#"[{"type":"text","text":"hi"}]"#,
);
write_file(
ws,
"001",
"response.json",
b"{\"type\":\"message_start\"}\nnot json\n{\"type\":\"end\"}\n",
);
write_tool(ws, "001", "toolu_1", b"{}", Some(br#"{"exit_code":0}"#));
write_tool(ws, "001", "toolu_2", b"{}", Some(br#"{"exit_code":2}"#));
write_tool(ws, "001", "toolu_0", b"{}", Some(b"oops"));
write_tool(ws, "001", "toolu_3", b"{}", Some(br#"{"stdout":"x"}"#));
std::fs::write(step_dir(ws, "001").join("tools").join(".keep"), b"").unwrap();
let d = detail(ws, AGENT, "001");
assert!(matches!(d.meta, Doc::Json(_)));
assert!(matches!(d.request, Doc::Json(_)));
assert!(matches!(d.staging, Doc::Json(_)));
assert_eq!(d.response.len(), 3);
assert!(matches!(d.response[0], Doc::Json(_)));
assert_eq!(d.response[1], Doc::Raw(b"not json".to_vec()));
assert!(matches!(d.response[2], Doc::Json(_)));
let ids: Vec<&str> = d.tools.iter().map(|t| t.tool_id.as_str()).collect();
assert_eq!(ids, vec!["toolu_0", "toolu_1", "toolu_2", "toolu_3"]);
let errs: Vec<bool> = d.tools.iter().map(|t| t.is_error).collect();
assert_eq!(errs, vec![false, false, true, false]);
assert_eq!(d.tools[0].output, Doc::Raw(b"oops".to_vec()));
}
#[test]
fn detail_is_forgiving_when_every_file_is_absent() {
let dir = tempdir().unwrap();
let d = detail(dir.path(), AGENT, "001");
assert_eq!(d.meta, Doc::Raw(Vec::new()));
assert_eq!(d.request, Doc::Raw(Vec::new()));
assert_eq!(d.staging, Doc::Raw(Vec::new()));
assert!(d.response.is_empty());
assert!(d.tools.is_empty());
assert_eq!(d.seq, "001");
}