use super::*;
fn action(n: u64) -> ActionId {
ActionId::new([n as u8; 12], n)
}
fn fails<T>(result: Result<T, TransitionError>, error: TransitionError) -> bool {
result.err() == Some(error)
}
fn action_box(value: &ChatBox, action_id: ActionId, returned: bool) -> bool {
match value.content() {
BoxContent::KtoolCall {
action_id: found, ..
} => !returned && *found == action_id,
BoxContent::KtoolReturn {
action_id: found, ..
} => returned && *found == action_id,
_ => false,
}
}
#[test]
fn canonical_boxes_and_round_shapes() {
let mut chat = Chatend::new();
chat.accept_system("s".into()).unwrap();
chat.accept_user("u".into()).unwrap();
chat.accept_attachment().unwrap();
let frontier = chat.start_round().unwrap();
assert!(frontier == Some(chat.boxes()[2].id()));
assert!(matches!(
chat.boxes()[3].content(),
BoxContent::Kennedy {
complete: false,
..
}
));
chat.append_kennedy_text("a").unwrap();
chat.collect_provider_call("n".into(), "{}".into()).unwrap();
chat.append_kennedy_text("b").unwrap();
assert!(chat.complete_provider_output(&[action(1)]).unwrap().len() == 1);
chat.complete_dispatch(vec![DispatchOutcome::Terminal(Ok("r".into()))])
.unwrap();
let stable = chat.boxes()[3].clone();
assert!(
chat.boxes()
.iter()
.zip(1..)
.all(|(value, id)| value.id().get() == id)
);
assert!(
matches!(chat.boxes()[0].content(), BoxContent::System(text) if text == "s")
&& matches!(chat.boxes()[1].content(), BoxContent::User(text) if text == "u")
&& matches!(chat.boxes()[2].content(), BoxContent::Attachment)
&& matches!(
chat.boxes()[3].content(),
BoxContent::Kennedy { text, complete: true } if text == "ab"
)
&& action_box(&chat.boxes()[4], action(1), false)
&& action_box(&chat.boxes()[5], action(1), true)
);
chat.start_round().unwrap();
chat.collect_provider_call("only".into(), "[]".into())
.unwrap();
chat.complete_provider_output(&[action(2)]).unwrap();
assert!(matches!(
chat.boxes()[6].content(),
BoxContent::Kennedy { text, complete: true } if text.is_empty()
));
chat.complete_dispatch(vec![DispatchOutcome::Pending])
.unwrap();
chat.start_round().unwrap();
chat.append_kennedy_text("text").unwrap();
assert!(chat.accept_system("late".into()).unwrap().is_none());
assert!(chat.complete_provider_output(&[]).unwrap().is_empty());
assert!(
matches!(chat.boxes().last().unwrap().content(), BoxContent::System(text) if text == "late")
);
assert!(chat.boxes()[3] == stable);
}
#[test]
fn barrier_and_async_acceptance_order() {
let mut chat = Chatend::new();
chat.start_round().unwrap();
chat.collect_provider_call("prior".into(), "{}".into())
.unwrap();
chat.complete_provider_output(&[action(1)]).unwrap();
chat.complete_dispatch(vec![DispatchOutcome::Pending])
.unwrap();
chat.start_round().unwrap();
let held = chat.boxes().len();
assert!(chat.accept_user("queued".into()).unwrap().is_none());
assert!(
chat.accept_async_return(action(1), Ok("prior".into()))
.unwrap()
.is_none()
);
assert!(chat.boxes().len() == held);
chat.collect_provider_call("one".into(), "1".into())
.unwrap();
chat.collect_provider_call("two".into(), "2".into())
.unwrap();
chat.complete_provider_output(&[action(2), action(3)])
.unwrap();
let synchronous = vec![DispatchOutcome::Terminal(Ok("sync".into())); 2];
chat.complete_dispatch(synchronous).unwrap();
assert!(
action_box(&chat.boxes()[5], action(2), true)
&& action_box(&chat.boxes()[6], action(3), true)
&& matches!(chat.boxes()[7].content(), BoxContent::User(text) if text == "queued")
&& action_box(&chat.boxes()[8], action(1), true)
);
chat.start_round().unwrap();
chat.collect_provider_call("x".into(), "x".into()).unwrap();
chat.collect_provider_call("y".into(), "y".into()).unwrap();
chat.complete_provider_output(&[action(4), action(5)])
.unwrap();
chat.complete_dispatch(vec![DispatchOutcome::Pending; 2])
.unwrap();
let first = chat.boxes().len();
chat.accept_async_return(action(5), Ok("five".into()))
.unwrap();
chat.accept_async_return(action(4), Ok("four".into()))
.unwrap();
assert!(
action_box(&chat.boxes()[first], action(5), true)
&& action_box(&chat.boxes()[first + 1], action(4), true)
);
}
#[test]
fn rejections_are_transactional() {
let mut chat = Chatend::new();
let orphan = chat.accept_async_return(action(9), Ok("x".into()));
assert!(fails(orphan, TransitionError::UnknownAction));
chat.start_round().unwrap();
chat.collect_provider_call("a".into(), "a".into()).unwrap();
chat.collect_provider_call("b".into(), "b".into()).unwrap();
let before = chat.boxes().to_vec();
assert!(fails(
chat.complete_provider_output(&[action(1)]),
TransitionError::ActionCount
));
assert!(chat.boxes() == before.as_slice());
let duplicate_actions = chat.complete_provider_output(&[action(1), action(1)]);
assert!(fails(duplicate_actions, TransitionError::DuplicateAction));
assert!(chat.boxes() == before.as_slice());
chat.complete_provider_output(&[action(1), action(2)])
.unwrap();
let before_dispatch = chat.boxes().to_vec();
assert!(fails(
chat.complete_dispatch(vec![DispatchOutcome::Pending]),
TransitionError::OutcomeCount
));
assert!(chat.boxes() == before_dispatch.as_slice());
chat.complete_dispatch(vec![DispatchOutcome::Pending; 2])
.unwrap();
assert!(
chat.accept_async_return(action(1), Ok("done".into()))
.unwrap()
.is_some()
);
let duplicate_return = chat.accept_async_return(action(1), Ok("again".into()));
assert!(fails(duplicate_return, TransitionError::DuplicateReturn));
}