use rho_tools::tool_card::{ToolCard, ToolFamily, ToolHeader, ToolStatus};
use super::*;
fn call_id(value: &str) -> ToolCallId {
ToolCallId::from_string(value).unwrap()
}
fn card(label: &str) -> ToolCard {
ToolCard::new(
ToolStatus::Running,
ToolFamily::Default,
ToolHeader::call(label, None),
)
}
fn live_labels(batch: &ToolCallBatch) -> Vec<String> {
batch
.live_entries()
.map(|entry| entry.card.header_text())
.collect()
}
#[test]
fn promotion_preserves_model_order_instead_of_call_id_order() {
let mut batch = ToolCallBatch::default();
let first = call_id("z-model-first");
let second = call_id("a-model-second");
batch.preview(0, Some(first.clone()), card("first preview"));
batch.preview(1, Some(second.clone()), card("second preview"));
batch.started(second, card("second running"));
assert_eq!(live_labels(&batch), ["● first preview", "● second running"]);
batch.started(first, card("first running"));
assert_eq!(live_labels(&batch), ["● first running", "● second running"]);
}
#[test]
fn proposal_reuses_stream_preview_slot_when_call_id_matches() {
let mut batch = ToolCallBatch::default();
let call = call_id("call-agent");
batch.preview(3, Some(call.clone()), card("reviewer starting"));
batch.preview_call(call.clone(), card("reviewer proposed"));
batch.started(call, card("reviewer running"));
assert_eq!(batch.live_entries().count(), 1);
assert_eq!(live_labels(&batch), ["● reviewer running"]);
assert!(batch.previews.is_empty());
}
#[test]
fn proposal_without_stream_appends_in_arrival_order() {
let mut batch = ToolCallBatch::default();
let first = call_id("call-a");
let second = call_id("call-b");
batch.preview_call(first.clone(), card("first starting"));
batch.preview_call(second.clone(), card("second starting"));
batch.started(first, card("first running"));
batch.started(second, card("second running"));
assert_eq!(live_labels(&batch), ["● first running", "● second running"]);
}
#[test]
fn late_stream_preview_is_ignored_after_start() {
let mut batch = ToolCallBatch::default();
let call = call_id("call-agent");
batch.preview(3, Some(call.clone()), card("starting"));
batch.started(call.clone(), card("running"));
batch.preview(3, Some(call), card("stale starting"));
batch.preview(3, None, card("index only stale"));
assert_eq!(live_labels(&batch), ["● running"]);
assert!(batch.previews.is_empty());
}
#[test]
fn latest_is_last_model_order_entry_when_later_entry_is_still_a_preview() {
let mut batch = ToolCallBatch::default();
let first = call_id("z-model-first");
let second = call_id("a-model-second");
batch.preview(0, Some(first.clone()), card("first"));
batch.preview(1, Some(second), card("second"));
batch.started(first.clone(), card("first running"));
batch.latest_mut().unwrap().expanded = true;
assert!(!batch.running[&first].expanded);
assert!(batch.previews[&1].expanded);
}
#[test]
fn latest_is_last_model_order_entry_after_promotion() {
let mut batch = ToolCallBatch::default();
let first = call_id("z-model-first");
let second = call_id("a-model-second");
batch.preview(0, Some(first.clone()), card("first"));
batch.preview(1, Some(second.clone()), card("second"));
batch.started(first, card("first running"));
batch.started(second.clone(), card("second running"));
batch.latest_mut().unwrap().expanded = true;
assert!(!batch.running[&call_id("z-model-first")].expanded);
assert!(batch.running[&second].expanded);
}
#[test]
fn finished_removes_running_and_returns_expanded() {
let mut batch = ToolCallBatch::default();
let call = call_id("call-1");
batch.started(call.clone(), card("running"));
batch.latest_mut().unwrap().expanded = true;
assert!(batch.finished(&call));
assert!(!batch.is_running());
assert!(batch.live_entries().next().is_none());
}