use super::*;
fn recs(lines: &[&str]) -> Vec<Record> {
lines.iter().map(|l| parse(l)).collect()
}
fn u(uuid: &str, parent: &str, text: &str, ts: &str) -> String {
let p = if parent.is_empty() {
"null".to_string()
} else {
format!("\"{parent}\"")
};
format!(
r#"{{"type":"user","uuid":"{uuid}","parentUuid":{p},"timestamp":"{ts}","message":{{"role":"user","content":"{text}"}}}}"#
)
}
fn a(uuid: &str, parent: &str, text: &str, ts: &str) -> String {
format!(
r#"{{"type":"assistant","uuid":"{uuid}","parentUuid":"{parent}","timestamp":"{ts}","message":{{"role":"assistant","id":"m-{uuid}","content":[{{"type":"text","text":"{text}"}}]}}}}"#
)
}
fn att(uuid: &str, parent: &str, ts: &str) -> String {
format!(
r#"{{"type":"attachment","uuid":"{uuid}","parentUuid":"{parent}","timestamp":"{ts}","attachment":{{"type":"hook_additional_context","content":["ctx"]}}}}"#
)
}
fn survivals(c: &Chain, n: usize) -> Vec<&'static str> {
(0..n).map(|i| c.survival(i).as_str()).collect()
}
#[test]
fn the_tips_fallback_takes_the_one_record_nothing_points_at() {
let r = recs(&[
&u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
&att("x1", "u0", "2026-06-07T05:01:00.000Z"),
&u("u2", "x1", "survey the shoal", "2026-06-07T05:02:00.000Z"),
&att("x9", "not-in-this-file", "2026-06-07T05:09:00.000Z"),
]);
let c = Chain::build(&r, None);
assert_eq!(c.leaf_index, Some(2), "the tip, not the newest record");
let s = survivals(&c, r.len());
assert_eq!(
&s[0..3],
["live", "live", "live"],
"the tip's whole ancestry, attachment included"
);
assert_eq!(
s[3], "abandoned",
"the trailing attachment is off the chain, above the floor"
);
}
#[test]
fn two_tips_climbing_to_one_ancestor_are_one_candidate() {
let r = recs(&[
&u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
&a(
"a0",
"u0",
"charting the lagoon",
"2026-06-07T05:01:00.000Z",
),
&att("x1", "a0", "2026-06-07T05:02:00.000Z"),
&att("x2", "a0", "2026-06-07T05:03:00.000Z"),
&att("x9", "not-in-this-file", "2026-06-07T05:09:00.000Z"),
]);
let c = Chain::build(&r, None);
assert_eq!(c.leaf_index, Some(1), "the shared ancestor wins outright");
let s = survivals(&c, r.len());
assert_eq!(
&s[0..4],
["live", "live", "live", "live"],
"the leaf's own trailing attachments come with it"
);
assert_eq!(s[4], "abandoned");
}
#[test]
fn a_replay_copy_is_no_tip_of_its_own() {
let r = recs(&[
&u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
&a(
"a0",
"u0",
"charting the lagoon",
"2026-06-07T05:01:00.000Z",
),
&u("d1", "a0", "sound the reef", "2026-06-07T05:02:00.000Z"),
&att("x1", "a0", "2026-06-07T05:03:00.000Z"),
&u("d1", "a0", "sound the reef", "2026-06-07T05:04:00.000Z"),
&att("x9", "not-in-this-file", "2026-06-07T05:09:00.000Z"),
]);
let c = Chain::build(&r, None);
assert_eq!(
c.leaf_index,
Some(4),
"the surviving line of the duplicated uuid"
);
assert_eq!(c.replay_copies, 1, "the earlier line is the copy");
let s = survivals(&c, r.len());
assert_eq!(&s[0..2], ["live", "live"], "the ancestry the leaf reaches");
assert_eq!(s[4], "live");
assert_eq!(s[5], "abandoned");
}
#[test]
fn a_preserved_segment_is_walked_from_its_tail_up_to_its_head() {
let r = recs(&[
&u("u0", "", "dropped prompt", "2026-06-07T05:00:00.000Z"),
&a("a0", "u0", "dropped reply", "2026-06-07T05:00:05.000Z"),
&u("k1", "a0", "kept prompt", "2026-06-07T05:05:00.000Z"),
&a("k2", "k1", "kept reply", "2026-06-07T05:05:05.000Z"),
r#"{"type":"system","subtype":"compact_boundary","uuid":"b1","parentUuid":null,"timestamp":"2026-06-07T05:10:00.000Z","compactMetadata":{"trigger":"auto","preservedSegment":{"anchorUuid":"a0","headUuid":"k1","tailUuid":"k2"}}}"#,
&u("u1", "k2", "new prompt", "2026-06-07T05:11:00.000Z"),
&a("a1", "u1", "new reply", "2026-06-07T05:11:05.000Z"),
]);
let c = Chain::build(&r, None);
let s = survivals(&c, r.len());
assert_eq!(s[0], "pre-cut", "the segment cut ran");
assert_eq!(s[1], "pre-cut");
assert_eq!(s[2], "live", "the segment head survives");
assert_eq!(s[3], "live", "and its tail");
assert_eq!(s[5], "live");
assert_eq!(s[6], "live");
}
#[test]
fn the_orphan_reparent_is_conversation_records_only() {
let r = recs(&[
&u("u0", "", "dropped prompt", "2026-06-07T05:00:00.000Z"),
&a("a0", "u0", "dropped reply", "2026-06-07T05:00:05.000Z"),
&u("k1", "a0", "kept prompt", "2026-06-07T05:05:00.000Z"),
&a("k2", "k1", "kept reply", "2026-06-07T05:05:05.000Z"),
r#"{"type":"system","subtype":"compact_boundary","uuid":"b1","parentUuid":null,"timestamp":"2026-06-07T05:10:00.000Z","compactMetadata":{"trigger":"auto","preservedMessages":{"anchorUuid":"a0","uuids":["k1","k2"]}}}"#,
&att("x1", "u0", "2026-06-07T05:11:00.000Z"),
]);
let c = Chain::build(&r, None);
assert!(
c.leaf_index.is_none(),
"no record climbs to a conversation record, so there is no leaf"
);
assert!(
survivals(&c, r.len()).iter().all(|v| *v == "pre-cut"),
"csift declines to call anything abandoned in a region it could not resolve"
);
}
#[test]
fn a_cut_record_and_a_line_the_loader_never_admits_are_both_outside_the_tip_search() {
let r = recs(&[
&u("u0", "", "dropped prompt", "2026-06-07T05:00:00.000Z"),
&a("a0", "u0", "dropped reply", "2026-06-07T05:00:05.000Z"),
&u("k1", "a0", "kept prompt", "2026-06-07T05:05:00.000Z"),
&a("k2", "k1", "kept reply", "2026-06-07T05:05:05.000Z"),
r#"{"type":"system","subtype":"compact_boundary","uuid":"b1","parentUuid":null,"timestamp":"2026-06-07T05:10:00.000Z","compactMetadata":{"trigger":"auto","preservedMessages":{"anchorUuid":"a0","uuids":["k1","k2"]}}}"#,
&u("u1", "k2", "new prompt", "2026-06-07T05:11:00.000Z"),
&a("a1", "u1", "new reply", "2026-06-07T05:11:05.000Z"),
r#"{"type":"file-history-snapshot","uuid":"fh1","parentUuid":"a1","timestamp":"2026-06-07T05:12:00.000Z","messageId":"m-1"}"#,
&att("x9", "not-in-this-file", "2026-06-07T05:15:00.000Z"),
]);
let c = Chain::build(&r, None);
assert_eq!(c.leaf_index, Some(6), "the one tip after the cut");
let s = survivals(&c, r.len());
assert_eq!(&s[0..2], ["pre-cut", "pre-cut"], "the cut still cuts");
assert_eq!(s[2], "live", "the preserved pair");
assert_eq!(s[3], "live");
assert_eq!(&s[5..7], ["live", "live"], "and everything after it");
}