use super::answer::RETRY_NOTICE;
use super::{BlockKind, Transcript, apply_event};
use saya_agent::AgentEvent;
fn snapshot(transcript: &Transcript) -> Vec<(BlockKind, u32, String)> {
transcript
.blocks()
.iter()
.map(|b| (b.kind, b.chapter, b.text.clone()))
.collect()
}
#[test]
fn a_retry_in_a_new_chapter_leaves_the_previous_answer_alone() {
let mut t = Transcript::new();
t.push(BlockKind::User, "old question");
apply_event(&mut t, AgentEvent::assistant_text("old answer"), false);
t.push(BlockKind::User, "new question");
apply_event(&mut t, AgentEvent::turn_reset(), false);
let old = t
.blocks()
.iter()
.find(|b| b.kind == BlockKind::Assistant)
.expect("the completed answer must survive the cross-chapter reset");
assert_eq!(
old.text,
"old answer",
"a completed answer from an earlier chapter must not be erased: {:?}",
t.blocks()
);
assert_eq!(
old.chapter,
1,
"the old answer must still sit in its own chapter: {:?}",
t.blocks()
);
assert!(
t.blocks().iter().all(|b| b.text != RETRY_NOTICE),
"nothing streamed in this chapter, so no notice: {:?}",
t.blocks()
);
apply_event(&mut t, AgentEvent::assistant_text("new answer"), false);
let new_answer = t.blocks().last().expect("the retry opens its own block");
assert_eq!(new_answer.kind, BlockKind::Assistant);
assert_eq!(
new_answer.chapter,
2,
"the retried text must land in the chapter in flight: {:?}",
t.blocks()
);
assert_eq!(new_answer.text, "new answer");
let old = t
.blocks()
.iter()
.find(|b| b.kind == BlockKind::Assistant && b.chapter == 1)
.expect("the old answer is still there after the retry");
assert_eq!(old.text, "old answer");
}
#[test]
fn a_reset_with_no_answer_in_this_chapter_clears_nothing() {
let mut t = Transcript::new();
t.push(BlockKind::User, "old question");
apply_event(&mut t, AgentEvent::assistant_text("old answer"), false);
t.push(BlockKind::User, "new question");
let before = snapshot(&t);
apply_event(&mut t, AgentEvent::turn_reset(), false);
assert_eq!(
snapshot(&t),
before,
"no answer in this chapter → the reset must be a whole-transcript no-op: {:?}",
t.blocks()
);
}
#[test]
fn a_retry_within_one_chapter_still_discards_the_partial() {
let mut t = Transcript::new();
t.push(BlockKind::User, "current question");
apply_event(&mut t, AgentEvent::assistant_text("partial "), false);
apply_event(&mut t, AgentEvent::assistant_text("answer"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
assert!(
t.blocks().iter().all(|b| !b.text.contains("partial")),
"the partial in the live chapter is still discarded: {:?}",
t.blocks()
);
let answers = t
.blocks()
.iter()
.filter(|b| b.kind == BlockKind::Assistant)
.count();
assert_eq!(
answers,
1,
"one chapter, one answer block: {:?}",
t.blocks()
);
assert!(
t.blocks().iter().any(|b| b.text == RETRY_NOTICE),
"the within-chapter retry is still announced: {:?}",
t.blocks()
);
apply_event(&mut t, AgentEvent::assistant_text("the full answer"), false);
let answer = t
.blocks()
.iter()
.find(|b| b.kind == BlockKind::Assistant)
.expect("the re-stream resumes the same block");
assert_eq!(answer.text, "the full answer");
assert_eq!(
answer.chapter,
1,
"the whole retry stays inside the one chapter: {:?}",
t.blocks()
);
}
#[test]
fn a_second_retry_after_the_restream_began_still_resumes_behind_the_notice() {
let mut t = Transcript::new();
t.push(BlockKind::User, "current question");
apply_event(&mut t, AgentEvent::assistant_text("first try"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
apply_event(&mut t, AgentEvent::assistant_text("resumed"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
assert!(
t.blocks().iter().all(|b| !b.text.contains("resumed")),
"the resumed partial is discarded by the second retry: {:?}",
t.blocks()
);
let notices = t.blocks().iter().filter(|b| b.text == RETRY_NOTICE).count();
assert_eq!(
notices,
2,
"each discarded attempt leaves its own notice: {:?}",
t.blocks()
);
apply_event(
&mut t,
AgentEvent::assistant_text("the final answer"),
false,
);
let answers: Vec<_> = t
.blocks()
.iter()
.filter(|b| b.kind == BlockKind::Assistant)
.collect();
assert_eq!(
answers.len(),
1,
"the re-stream never opens a second assistant block: {:?}",
t.blocks()
);
assert_eq!(answers[0].text, "the final answer");
assert_eq!(answers[0].chapter, 1);
assert_eq!(
t.blocks().last().map(|b| b.text.as_str()),
Some(RETRY_NOTICE),
"the newest notice trails the resumed answer: {:?}",
t.blocks()
);
}