use super::{BlockKind, Transcript, apply_event};
use saya_agent::AgentEvent;
fn last_block_text(transcript: &Transcript) -> Option<&str> {
transcript.blocks().last().map(|b| b.text.as_str())
}
#[test]
fn turn_reset_replaces_the_accumulated_answer_rather_than_appending() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::assistant_text("sw"), false);
assert_eq!(last_block_text(&t), Some("The answ"));
apply_event(&mut t, AgentEvent::turn_reset(), false);
apply_event(
&mut t,
AgentEvent::assistant_text("The answer is 42."),
false,
);
let assistant: Vec<_> = t
.blocks()
.iter()
.filter(|b| b.kind == BlockKind::Assistant)
.collect();
assert_eq!(
assistant.len(),
1,
"the retried answer stays one assistant block: {:?}",
t.blocks()
);
assert_eq!(
assistant[0].text,
"The answer is 42.",
"the retried answer must replace the partial text, not append to it: {:?}",
t.blocks()
);
assert_eq!(
last_block_text(&t),
Some(piped_retry_notice().as_str()),
"the retry notice trails the resumed answer: {:?}",
t.blocks()
);
}
#[test]
fn turn_reset_with_nothing_streamed_is_a_no_op() {
let mut t = Transcript::new();
t.push(BlockKind::System, "memory off · recall disabled");
apply_event(&mut t, AgentEvent::turn_reset(), false);
assert_eq!(t.blocks().len(), 1, "nothing streamed → nothing to clear");
apply_event(&mut t, AgentEvent::assistant_text("the answer"), false);
assert_eq!(last_block_text(&t), Some("the answer"));
}
fn piped_retry_notice() -> String {
use crate::render::{RenderFormat, TerminalEvent, render_event};
let rendered = render_event(&TerminalEvent::TurnReset, RenderFormat::Text);
rendered.stdout.trim_end_matches('\n').to_string()
}
#[test]
fn a_retry_is_announced_in_the_transcript() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::assistant_text("sw"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
assert!(
t.blocks().iter().any(|b| b.text == piped_retry_notice()),
"a retry must leave a visible notice in the transcript: {:?}",
t.blocks()
);
}
#[test]
fn the_retry_notice_matches_the_piped_surface() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
let notice = t
.blocks()
.iter()
.find(|b| b.text == piped_retry_notice())
.expect("the retry notice must be present");
assert_eq!(notice.text, piped_retry_notice());
}
#[test]
fn the_notice_is_a_system_block_not_an_error() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
let notice = t
.blocks()
.iter()
.find(|b| b.text == piped_retry_notice())
.expect("the retry notice must be present");
assert_eq!(
notice.kind,
BlockKind::System,
"a recovering retry is not a failure: {:?}",
t.blocks()
);
assert!(
t.blocks().iter().all(|b| b.kind != BlockKind::Error),
"the retry must not render as an error: {:?}",
t.blocks()
);
}
#[test]
fn the_restreamed_answer_does_not_append_to_the_notice() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
apply_event(
&mut t,
AgentEvent::assistant_text("The answer is 42."),
false,
);
let blocks = t.blocks();
let last = blocks.last().expect("the retry notice trails the answer");
assert_eq!(last.kind, BlockKind::System);
assert_eq!(last.text, piped_retry_notice());
let answer = &blocks[blocks.len() - 2];
assert_eq!(answer.kind, BlockKind::Assistant);
assert_eq!(answer.text, "The answer is 42.");
}
#[test]
fn the_discarded_partial_is_not_resurrected() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::assistant_text("sw"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
assert!(
t.blocks().iter().all(|b| !b.text.contains("The an")),
"the discarded partial must be gone: {:?}",
t.blocks()
);
}
#[test]
fn a_turn_with_no_retry_says_nothing_extra() {
let mut t = Transcript::new();
apply_event(
&mut t,
AgentEvent::assistant_text("The answer is 42."),
false,
);
assert!(
t.blocks().iter().all(|b| b.text != piped_retry_notice()),
"no retry happened, so no notice may appear: {:?}",
t.blocks()
);
}
#[test]
fn repeated_retries_leave_one_notice_each() {
let mut t = Transcript::new();
apply_event(&mut t, AgentEvent::assistant_text("The an"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
apply_event(&mut t, AgentEvent::assistant_text("The ans"), false);
apply_event(&mut t, AgentEvent::turn_reset(), false);
apply_event(
&mut t,
AgentEvent::assistant_text("The answer is 42."),
false,
);
let notices = t
.blocks()
.iter()
.filter(|b| b.kind == BlockKind::System && b.text == piped_retry_notice())
.count();
assert_eq!(
notices,
2,
"each discarded attempt leaves its own notice: {:?}",
t.blocks()
);
}