use kcode_k1_chat_chatend::{ActionId, BoxContent, Chatend, DispatchCall, DispatchOutcome};
use super::dispatch;
fn action(sequence: u64) -> ActionId {
ActionId::new([sequence as u8; 12], sequence)
}
fn calls(chat: &mut Chatend, specs: &[(&str, &str)]) -> Vec<DispatchCall> {
chat.start_round().unwrap();
for (name, arguments) in specs {
chat.collect_provider_call((*name).into(), (*arguments).into())
.unwrap();
}
let actions = (1..=specs.len() as u64).map(action).collect::<Vec<_>>();
chat.complete_provider_output(&actions).unwrap()
}
fn terminal(outcome: &DispatchOutcome) -> &Result<String, String> {
let DispatchOutcome::Terminal(result) = outcome else {
panic!("dispatch must not return pending");
};
result
}
#[test]
fn current_time_call_returns_matching_return() {
let mut chat = Chatend::new();
let calls = calls(&mut chat, &[("CurrentTime", "{}")]);
let call = calls[0].clone();
let outcomes = dispatch(&calls);
assert!(
terminal(&outcomes[0])
.as_ref()
.is_ok_and(|time| !time.is_empty())
);
chat.complete_dispatch(outcomes).unwrap();
assert!(matches!(
chat.boxes().last().unwrap().content(),
BoxContent::KtoolReturn {
action_id,
originating_call,
result: Ok(_),
} if *action_id == call.action_id && *originating_call == call.call_box_id
));
}
#[test]
fn calls_and_returns_preserve_order() {
let mut chat = Chatend::new();
let calls = calls(
&mut chat,
&[("CurrentTime", "{}"), ("CurrentTime", " { } ")],
);
let outcomes = dispatch(&calls);
assert!(outcomes.iter().all(|outcome| terminal(outcome).is_ok()));
chat.complete_dispatch(outcomes).unwrap();
let boxes = chat.boxes();
assert!(
matches!(boxes[1].content(), BoxContent::KtoolCall { action_id, .. } if *action_id == calls[0].action_id)
);
assert!(
matches!(boxes[2].content(), BoxContent::KtoolCall { action_id, .. } if *action_id == calls[1].action_id)
);
assert!(
matches!(boxes[3].content(), BoxContent::KtoolReturn { action_id, .. } if *action_id == calls[0].action_id)
);
assert!(
matches!(boxes[4].content(), BoxContent::KtoolReturn { action_id, .. } if *action_id == calls[1].action_id)
);
}
#[test]
fn invalid_and_unknown_calls_remain_and_return_one_error_each() {
let cases = ["", "null", "[]", "{\"zone\":\"UTC\"}"];
for arguments in cases {
let mut chat = Chatend::new();
let calls = calls(&mut chat, &[("CurrentTime", arguments)]);
let call_box = chat.boxes()[1].clone();
let outcomes = dispatch(&calls);
assert_eq!(
terminal(&outcomes[0]),
&Err("invalid CurrentTime arguments".into())
);
chat.complete_dispatch(outcomes).unwrap();
assert_eq!(chat.boxes()[1], call_box);
assert!(
matches!(chat.boxes()[2].content(), BoxContent::KtoolReturn { result: Err(error), .. } if error == "invalid CurrentTime arguments")
);
}
let mut chat = Chatend::new();
let calls = calls(&mut chat, &[("NoSuchTool", "{}")]);
let outcomes = dispatch(&calls);
assert_eq!(terminal(&outcomes[0]), &Err("unknown Ktool".into()));
chat.complete_dispatch(outcomes).unwrap();
assert!(
matches!(chat.boxes()[2].content(), BoxContent::KtoolReturn { result: Err(error), .. } if error == "unknown Ktool")
);
}
#[test]
fn queued_user_drains_after_synchronous_returns_and_calls_stay_immutable() {
let mut chat = Chatend::new();
let calls = calls(&mut chat, &[("CurrentTime", "{}"), ("NoSuchTool", "{}")]);
let call_boxes = chat.boxes()[1..3].to_vec();
assert!(
chat.accept_user("arrived during dispatch".into())
.unwrap()
.is_none()
);
chat.complete_dispatch(dispatch(&calls)).unwrap();
assert_eq!(&chat.boxes()[1..3], call_boxes.as_slice());
assert!(matches!(
chat.boxes()[3].content(),
BoxContent::KtoolReturn { .. }
));
assert!(matches!(
chat.boxes()[4].content(),
BoxContent::KtoolReturn { .. }
));
assert!(
matches!(chat.boxes()[5].content(), BoxContent::User(text) if text == "arrived during dispatch")
);
}