use super::*;
#[test]
fn group_turn_indices_basic_two_turns() {
let flags = [true, false, false, true, false];
let turns = group_turn_indices(&flags, |b| *b);
assert_eq!(turns, vec![vec![0, 1, 2], vec![3, 4]]);
}
#[test]
fn group_turn_indices_synthetic_lead_folds_into_first_turn() {
let flags = [false, false, true, false, true];
let turns = group_turn_indices(&flags, |b| *b);
assert_eq!(turns, vec![vec![0, 1, 2, 3], vec![4]]);
}
#[test]
fn group_turn_indices_only_synthetic_lead_no_genuine_user() {
let flags = [false, false, false];
let turns = group_turn_indices(&flags, |b| *b);
assert_eq!(turns, vec![vec![0, 1, 2]]);
}
#[test]
fn group_turn_indices_empty_is_empty() {
let flags: [bool; 0] = [];
assert!(group_turn_indices(&flags, |b| *b).is_empty());
}
#[test]
fn group_turn_indices_first_record_genuine_no_fold() {
let flags = [true, false, true];
let turns = group_turn_indices(&flags, |b| *b);
assert_eq!(turns, vec![vec![0, 1], vec![2]]);
}
#[test]
fn opens_turn_grouping_splits_on_auq_answer_and_skips_interrupt() {
let records: Vec<Record> = [
r#"{"type":"user","message":{"role":"user","content":"pick one"}}"#,
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"q1","name":"AskUserQuestion","input":{"questions":[{"question":"which?"}]}}]}}"#,
r#"{"type":"user","toolUseResult":{"answers":{"which?":"the bold one"}},"message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"q1","content":"User has answered your questions: \"which?\"=\"the bold one\"."}]}}"#,
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"ok"}]}}"#,
r#"{"type":"user","message":{"role":"user","content":[{"type":"text","text":"[Request interrupted by user]"}]}}"#,
r#"{"type":"user","message":{"role":"user","content":"next question"}}"#,
]
.iter()
.map(|l| parse(l))
.collect();
let turns = group_turn_indices(&records, |r| r.opens_turn());
assert_eq!(
turns,
vec![vec![0, 1], vec![2, 3, 4], vec![5]],
"AUQ answer opens turn 1; interrupt is a member, not a boundary"
);
}
#[test]
fn superseded_drafts_collapse_same_parent_edit_resend() {
let records: Vec<Record> = [
r#"{"type":"user","uuid":"u0","parentUuid":"root","message":{"role":"user","content":"start"}}"#,
r#"{"type":"assistant","uuid":"a0","parentUuid":"u0","message":{"role":"assistant","content":[{"type":"text","text":"ok"}]}}"#,
r#"{"type":"user","uuid":"d1","parentUuid":"a0","message":{"role":"user","content":"draft v1"}}"#,
r#"{"type":"user","uuid":"d2","parentUuid":"a0","message":{"role":"user","content":"draft v2"}}"#,
r#"{"type":"user","uuid":"u1","parentUuid":"a0","message":{"role":"user","content":"draft v2, with a tail"}}"#,
r#"{"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"noted"}]}}"#,
]
.iter()
.map(|l| parse(l))
.collect();
let sup = superseded_draft_indices(&records, |r| r);
assert_eq!(
sup.len(),
2,
"the two earlier same-parent drafts are superseded"
);
assert!(
sup.contains(&2) && sup.contains(&3),
"drafts d1,d2 superseded; u1 (last in file order) survives"
);
assert!(!sup.contains(&4));
let turns = group_turn_indices_deduped(&records, |r| r);
assert_eq!(
turns,
vec![vec![0, 1], vec![4, 5]],
"two real turns; abandoned drafts vanish entirely (neither boundary nor member)"
);
}
#[test]
fn superseded_drafts_exact_duplicate_collapses_to_one() {
let records: Vec<Record> = [
r#"{"type":"assistant","uuid":"a0","parentUuid":"root","message":{"role":"assistant","content":[{"type":"text","text":"hi"}]}}"#,
r#"{"type":"user","uuid":"d1","parentUuid":"a0","message":{"role":"user","content":"the same resent line"}}"#,
r#"{"type":"user","uuid":"d2","parentUuid":"a0","message":{"role":"user","content":"the same resent line"}}"#,
r#"{"type":"user","uuid":"u1","parentUuid":"a0","message":{"role":"user","content":"the same resent line"}}"#,
]
.iter()
.map(|l| parse(l))
.collect();
assert_eq!(
group_turn_indices_deduped(&records, |r| r),
vec![vec![0, 3]],
"3 identical drafts → 1 turn (opener idx3)"
);
}
#[test]
fn superseded_drafts_distinct_parents_not_merged() {
let records: Vec<Record> = [
r#"{"type":"user","uuid":"u0","parentUuid":"a0","message":{"role":"user","content":"continue"}}"#,
r#"{"type":"assistant","uuid":"x","parentUuid":"u0","message":{"role":"assistant","content":[{"type":"text","text":"ok"}]}}"#,
r#"{"type":"user","uuid":"u1","parentUuid":"x","message":{"role":"user","content":"continue"}}"#,
]
.iter()
.map(|l| parse(l))
.collect();
assert!(superseded_draft_indices(&records, |r| r).is_empty());
assert_eq!(
group_turn_indices_deduped(&records, |r| r),
vec![vec![0, 1], vec![2]]
);
}
#[test]
fn superseded_drafts_null_parent_never_grouped() {
let records: Vec<Record> = [
r#"{"type":"user","uuid":"u0","message":{"role":"user","content":"a"}}"#,
r#"{"type":"user","uuid":"u1","message":{"role":"user","content":"b"}}"#,
]
.iter()
.map(|l| parse(l))
.collect();
assert!(superseded_draft_indices(&records, |r| r).is_empty());
assert_eq!(
group_turn_indices_deduped(&records, |r| r),
vec![vec![0], vec![1]]
);
}
#[test]
fn deduped_grouping_matches_plain_when_no_drafts() {
let records: Vec<Record> = [
r#"{"type":"user","uuid":"u0","parentUuid":"r","message":{"role":"user","content":"q1"}}"#,
r#"{"type":"assistant","uuid":"a0","parentUuid":"u0","message":{"role":"assistant","content":[{"type":"text","text":"r1"}]}}"#,
r#"{"type":"user","uuid":"u1","parentUuid":"a0","message":{"role":"user","content":"q2"}}"#,
]
.iter()
.map(|l| parse(l))
.collect();
let plain = group_turn_indices(&records, |r| r.opens_turn());
let deduped = group_turn_indices_deduped(&records, |r| r);
assert_eq!(plain, deduped);
assert_eq!(deduped, vec![vec![0, 1], vec![2]]);
}