use super::*;
use std::io::Write as _;
use std::sync::atomic::{AtomicU64, Ordering};
#[test]
fn truncate_short_excerpt_unchanged() {
assert_eq!(truncate_excerpt("hello"), "hello");
}
#[test]
fn truncate_long_excerpt_marks_dropped_count() {
let s = "x".repeat(EXCERPT_MAX + 5);
let out = truncate_excerpt(&s);
assert!(out.ends_with("… (+5 chars)"), "got: {out}");
assert!(out.starts_with(&"x".repeat(EXCERPT_MAX)));
}
#[test]
fn truncate_counts_chars_not_bytes() {
let s = "🛠".repeat(EXCERPT_MAX + 2);
let out = truncate_excerpt(&s);
assert!(out.ends_with("… (+2 chars)"), "got: {out}");
}
#[test]
fn format_timestamp_uses_system_local_canonical_marker() {
let raw = "2026-06-07T05:48:22.880Z";
let out = format_timestamp(Some(raw));
let ts: jiff::Timestamp = raw.parse().expect("parseable instant");
let local = ts
.to_zoned(crate::timez::local_tz())
.strftime("%Y-%m-%d %H:%M:%S")
.to_string();
assert!(out.contains(&local), "expected local {local:?} in {out:?}");
assert!(out.contains("(UTC"), "marker missing: {out}");
assert!(!out.contains(raw), "the raw-UTC copy must be gone: {out}");
}
#[test]
fn format_timestamp_missing_is_em_dash() {
assert_eq!(format_timestamp(None), "—");
}
#[test]
fn format_timestamp_unparseable_surfaces_raw() {
let out = format_timestamp(Some("not-a-time"));
assert!(out.contains("not-a-time"));
assert!(out.contains("unparsed"));
}
#[test]
fn local_iso_matches_system_tz_offset() {
let raw = "2026-06-07T05:48:22.880Z";
let out = local_iso(raw).expect("local iso");
let ts: jiff::Timestamp = raw.parse().expect("parseable instant");
let expected = ts
.to_zoned(crate::timez::local_tz())
.strftime("%Y-%m-%dT%H:%M:%S%:z")
.to_string();
assert_eq!(out, expected);
}
static COUNTER: AtomicU64 = AtomicU64::new(0);
fn tmp_session(name_stem: &str, lines: &[&str]) -> PathBuf {
let p = std::env::temp_dir().join(format!(
"csift-sess-{}-{}-{name_stem}.jsonl",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
));
let mut f = std::fs::File::create(&p).unwrap();
for l in lines {
writeln!(f, "{l}").unwrap();
}
p
}
#[test]
fn capture_identity_if_empty_only_fills_blanks() {
let rec: Record = serde_json::from_str(
r#"{"type":"user","cwd":"/c","version":"9.9","gitBranch":"b","sessionId":"sid","message":{"role":"user","content":"x"}}"#,
)
.unwrap();
let mut cwd = None;
let mut version = Some("keep".to_string()); let mut branch = None;
let mut sid = None;
capture_identity_if_empty(&rec, &mut cwd, &mut version, &mut branch, &mut sid);
assert_eq!(cwd.as_deref(), Some("/c"));
assert_eq!(version.as_deref(), Some("keep"), "pre-set value preserved");
assert_eq!(branch.as_deref(), Some("b"));
assert_eq!(sid.as_deref(), Some("sid"));
}
#[test]
fn summarize_head_first_user_captures_identity() {
let p = tmp_session(
"head",
&[
r#"{"type":"user","cwd":"/Users/testuser/Projects/foo","version":"2.1.0","gitBranch":"main","sessionId":"sid-data","message":{"role":"user","content":"first q"}}"#,
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"mid agent"}]}}"#,
r#"{"type":"user","message":{"role":"user","content":"last q"}}"#,
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"last agent"}]}}"#,
],
);
let s = summarize_session(&p).unwrap();
std::fs::remove_file(&p).ok();
assert_eq!(s.cwd.as_deref(), Some("/Users/testuser/Projects/foo"));
assert_eq!(s.version.as_deref(), Some("2.1.0"));
assert_eq!(s.git_branch.as_deref(), Some("main"));
assert_eq!(s.first_user.as_ref().unwrap().excerpt, "first q");
assert_eq!(s.last_user.as_ref().unwrap().excerpt, "last q");
assert_eq!(s.last_agent.as_ref().unwrap().excerpt, "last agent");
}
#[test]
fn summarize_backfills_identity_from_tail_when_head_user_lacks_it() {
let p = tmp_session(
"tailfill",
&[
r#"{"type":"user","message":{"role":"user","content":"first q, no identity"}}"#,
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"a"}]}}"#,
r#"{"type":"user","cwd":"/tail/cwd","version":"3.0","gitBranch":"dev","sessionId":"sid-tail","message":{"role":"user","content":"last q, has identity"}}"#,
],
);
let s = summarize_session(&p).unwrap();
std::fs::remove_file(&p).ok();
assert_eq!(
s.first_user.as_ref().unwrap().excerpt,
"first q, no identity"
);
assert_eq!(
s.last_user.as_ref().unwrap().excerpt,
"last q, has identity"
);
assert_eq!(s.cwd.as_deref(), Some("/tail/cwd"));
assert_eq!(s.version.as_deref(), Some("3.0"));
assert_eq!(s.git_branch.as_deref(), Some("dev"));
}
#[test]
fn summarize_session_id_from_data_when_filename_has_no_stem() {
let p = tmp_session(
"dataid",
&[r#"{"type":"user","sessionId":"sid-data-xyz","message":{"role":"user","content":"hi"}}"#],
);
let s = summarize_session(&p).unwrap();
std::fs::remove_file(&p).ok();
assert!(!s.session_id.is_empty());
}
#[test]
fn summarize_head_skips_non_genuine_records_before_first_user() {
let p = tmp_session(
"headnoise",
&[
r#"{"type":"last-prompt","leafUuid":"x"}"#,
r#"{"type":"user","isMeta":true,"message":{"role":"user","content":"Continue from where you left off."}}"#,
r#"{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"x","content":"carrier"}]}}"#,
r#"{"type":"user","message":{"role":"user","content":"the genuine first question"}}"#,
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"a"}]}}"#,
],
);
let s = summarize_session(&p).unwrap();
std::fs::remove_file(&p).ok();
assert_eq!(
s.first_user.as_ref().unwrap().excerpt,
"the genuine first question"
);
}
#[test]
fn top_level_summary_is_not_subagent_and_is_its_own_parent() {
let p = tmp_session(
"toplevel",
&[r#"{"type":"user","message":{"role":"user","content":"hi"}}"#],
);
let s = summarize_session(&p).unwrap();
let stem = p.file_stem().unwrap().to_str().unwrap().to_string();
std::fs::remove_file(&p).ok();
assert!(!s.is_subagent);
assert_eq!(s.parent_session_id, stem);
assert_eq!(s.parent_session_id, s.session_id);
}
#[test]
fn subagent_summary_carries_is_subagent_and_refeedable_parent() {
let dir = std::env::temp_dir().join(format!(
"csift-subdir-{}-{}/11111111-2222-3333-4444-555555555555/subagents/workflows/wf_z",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir_all(&dir).unwrap();
let p = dir.join("agent-deadbeefcafe1234.jsonl");
{
let mut f = std::fs::File::create(&p).unwrap();
writeln!(
f,
r#"{{"type":"user","message":{{"role":"user","content":"sub work"}}}}"#
)
.unwrap();
}
let s = summarize_session(&p).unwrap();
std::fs::remove_file(&p).ok();
assert_eq!(
s.session_id, "deadbeefcafe1234",
"bare hex (agent- stripped)"
);
assert!(s.is_subagent, "a subagents/ path is a subagent transcript");
assert_eq!(
s.parent_session_id, "11111111-2222-3333-4444-555555555555",
"parent is the re-feedable uuid dir before subagents/"
);
}
#[test]
fn summarize_session_id_is_filename_stem() {
let p = tmp_session(
"stemid",
&[r#"{"type":"user","sessionId":"DATA-ID","message":{"role":"user","content":"hi"}}"#],
);
let s = summarize_session(&p).unwrap();
let stem = p.file_stem().unwrap().to_str().unwrap().to_string();
std::fs::remove_file(&p).ok();
assert_eq!(s.session_id, stem);
assert_ne!(s.session_id, "DATA-ID");
}
#[test]
fn clone_probe_arms_and_origin_join_edges() {
static N: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let root = std::env::temp_dir().join(format!("csift-clone-{}-{n}", std::process::id()));
std::fs::create_dir_all(&root).unwrap();
let bx = "0f1e2d3c-4b5a-4697-8807-16f5e4d3c2b1";
let clone = root.join("c1.jsonl");
std::fs::write(
&clone,
format!(
"not json but mentions \"timestamp\" anyway\n{{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"{bx}\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}}\n"
),
)
.unwrap();
assert_eq!(
clone_head_boundary(&clone).unwrap().as_deref(),
Some(bx),
"garbage before the boundary is walked past"
);
let empty = root.join("c0.jsonl");
std::fs::write(&empty, "").unwrap();
assert_eq!(clone_head_boundary(&empty).unwrap(), None);
let blank = root.join("c3.jsonl");
std::fs::write(
&blank,
"{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}\n",
)
.unwrap();
assert_eq!(clone_head_boundary(&blank).unwrap(), None);
let anon = root.join("c2.jsonl");
std::fs::write(
&anon,
"{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}\n",
)
.unwrap();
assert_eq!(clone_head_boundary(&anon).unwrap(), None);
let origin = root.join("o1.jsonl");
std::fs::write(
&origin,
format!(
"{{\"type\":\"user\",\"uuid\":\"u1\",\"timestamp\":\"2026-06-07T05:00:00.000Z\",\"message\":{{\"role\":\"user\",\"content\":\"discussing {bx} in prose\"}}}}\n{{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"{bx}\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}}\n"
),
)
.unwrap();
assert_eq!(
clone_origin(&clone, bx).as_deref(),
Some("o1"),
"prose quote is skipped, the record wins"
);
std::fs::remove_file(&origin).unwrap();
let bystander = root.join("b1.jsonl");
std::fs::write(
&bystander,
format!(
"{{\"type\":\"user\",\"uuid\":\"u1\",\"timestamp\":\"2026-06-07T05:00:00.000Z\",\"message\":{{\"role\":\"user\",\"content\":\"only prose about {bx}\"}}}}\n"
),
)
.unwrap();
assert_eq!(clone_origin(&clone, bx), None);
let decoy = root.join("d1.jsonl");
std::fs::write(
&decoy,
format!(
"{{\"type\":\"user\",\"uuid\":\"u1\",\"timestamp\":\"2026-06-07T05:00:00.000Z\",\"message\":{{\"role\":\"user\",\"content\":\"prose about {bx}\"}}}}\n{{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"99999999-aaaa-4bbb-8ccc-dddddddddddd\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}}\n"
),
)
.unwrap();
assert_eq!(
clone_origin(&clone, bx),
None,
"an unrelated boundary near a prose quote is not the origin"
);
std::fs::remove_file(&decoy).unwrap();
std::fs::remove_dir_all(&root).unwrap();
}
#[test]
fn clone_origin_decoys_stay_outside_the_join() {
let root = std::env::temp_dir().join(format!("csift-clone-decoy-{}", std::process::id()));
std::fs::create_dir_all(&root).unwrap();
let bx = "3a4b5c6d-7e8f-4a9b-8c0d-1e2f3a4b5c6d";
let clone = root.join("c1.jsonl");
std::fs::write(
&clone,
format!(
"{{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"{bx}\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}}\n"
),
)
.unwrap();
let txt = root.join("o1.txt");
std::fs::write(
&txt,
format!(
"{{\"type\":\"user\",\"uuid\":\"u1\",\"timestamp\":\"2026-06-07T05:00:00.000Z\",\"message\":{{\"role\":\"user\",\"content\":\"hi\"}}}}\n{{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"{bx}\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}}\n"
),
)
.unwrap();
assert_eq!(
clone_origin(&clone, bx),
None,
"a non-jsonl decoy never becomes the origin"
);
std::fs::remove_file(&txt).unwrap();
let refr = root.join("s2.jsonl");
std::fs::write(
&refr,
format!(
"{{\"type\":\"user\",\"uuid\":\"u1\",\"timestamp\":\"2026-06-07T05:00:00.000Z\",\"message\":{{\"role\":\"user\",\"content\":\"hi\"}}}}\n{{\"type\":\"system\",\"subtype\":\"compact_boundary\",\"uuid\":\"77777777-8888-4999-8aaa-bbbbbbbbbbbb\",\"logicalParentUuid\":\"{bx}\",\"timestamp\":\"2026-06-07T05:10:00.000Z\"}}\n"
),
)
.unwrap();
assert_eq!(
clone_origin(&clone, bx),
None,
"a boundary that only references the uuid is not the carrier"
);
std::fs::remove_file(&refr).unwrap();
std::fs::remove_file(&clone).ok();
}