use a2a_protocol_types::task::TaskState;
const ALL_STATES: [TaskState; 9] = [
TaskState::Unspecified,
TaskState::Submitted,
TaskState::Working,
TaskState::InputRequired,
TaskState::AuthRequired,
TaskState::Completed,
TaskState::Failed,
TaskState::Canceled,
TaskState::Rejected,
];
fn assert_transitions(from: TaskState, valid: &[TaskState]) {
for &target in &ALL_STATES {
let expected = valid.contains(&target);
let actual = from.can_transition_to(target);
assert_eq!(
actual, expected,
"{from} -> {target}: expected can_transition_to = {expected}, got {actual}"
);
}
}
#[test]
fn test_unspecified_transitions() {
assert_transitions(TaskState::Unspecified, &ALL_STATES);
assert!(
!TaskState::Unspecified.is_terminal(),
"Unspecified must not be terminal"
);
}
#[test]
fn test_submitted_transitions() {
let valid = [
TaskState::Working,
TaskState::Failed,
TaskState::Canceled,
TaskState::Rejected,
];
assert_transitions(TaskState::Submitted, &valid);
assert!(
!TaskState::Submitted.can_transition_to(TaskState::Unspecified),
"Submitted -> Unspecified must be invalid"
);
assert!(
!TaskState::Submitted.can_transition_to(TaskState::Submitted),
"Submitted -> Submitted must be invalid"
);
assert!(
!TaskState::Submitted.can_transition_to(TaskState::Completed),
"Submitted -> Completed must be invalid (must go through Working)"
);
assert!(
!TaskState::Submitted.can_transition_to(TaskState::InputRequired),
"Submitted -> InputRequired must be invalid"
);
assert!(
!TaskState::Submitted.can_transition_to(TaskState::AuthRequired),
"Submitted -> AuthRequired must be invalid"
);
assert!(
!TaskState::Submitted.is_terminal(),
"Submitted must not be terminal"
);
}
#[test]
fn test_working_transitions() {
let valid = [
TaskState::Completed,
TaskState::Failed,
TaskState::Canceled,
TaskState::InputRequired,
TaskState::AuthRequired,
];
assert_transitions(TaskState::Working, &valid);
assert!(
!TaskState::Working.can_transition_to(TaskState::Unspecified),
"Working -> Unspecified must be invalid"
);
assert!(
!TaskState::Working.can_transition_to(TaskState::Submitted),
"Working -> Submitted must be invalid"
);
assert!(
!TaskState::Working.can_transition_to(TaskState::Working),
"Working -> Working must be invalid"
);
assert!(
!TaskState::Working.can_transition_to(TaskState::Rejected),
"Working -> Rejected must be invalid"
);
assert!(
!TaskState::Working.is_terminal(),
"Working must not be terminal"
);
}
#[test]
fn test_input_required_transitions() {
let valid = [TaskState::Working, TaskState::Failed, TaskState::Canceled];
assert_transitions(TaskState::InputRequired, &valid);
assert!(
!TaskState::InputRequired.can_transition_to(TaskState::Unspecified),
"InputRequired -> Unspecified must be invalid"
);
assert!(
!TaskState::InputRequired.can_transition_to(TaskState::Submitted),
"InputRequired -> Submitted must be invalid"
);
assert!(
!TaskState::InputRequired.can_transition_to(TaskState::InputRequired),
"InputRequired -> InputRequired must be invalid (no self-loop)"
);
assert!(
!TaskState::InputRequired.can_transition_to(TaskState::AuthRequired),
"InputRequired -> AuthRequired must be invalid"
);
assert!(
!TaskState::InputRequired.can_transition_to(TaskState::Completed),
"InputRequired -> Completed must be invalid (must go through Working)"
);
assert!(
!TaskState::InputRequired.can_transition_to(TaskState::Rejected),
"InputRequired -> Rejected must be invalid"
);
assert!(
!TaskState::InputRequired.is_terminal(),
"InputRequired must not be terminal"
);
}
#[test]
fn test_auth_required_transitions() {
let valid = [TaskState::Working, TaskState::Failed, TaskState::Canceled];
assert_transitions(TaskState::AuthRequired, &valid);
assert!(
!TaskState::AuthRequired.can_transition_to(TaskState::Unspecified),
"AuthRequired -> Unspecified must be invalid"
);
assert!(
!TaskState::AuthRequired.can_transition_to(TaskState::Submitted),
"AuthRequired -> Submitted must be invalid"
);
assert!(
!TaskState::AuthRequired.can_transition_to(TaskState::InputRequired),
"AuthRequired -> InputRequired must be invalid"
);
assert!(
!TaskState::AuthRequired.can_transition_to(TaskState::AuthRequired),
"AuthRequired -> AuthRequired must be invalid (no self-loop)"
);
assert!(
!TaskState::AuthRequired.can_transition_to(TaskState::Completed),
"AuthRequired -> Completed must be invalid (must go through Working)"
);
assert!(
!TaskState::AuthRequired.can_transition_to(TaskState::Rejected),
"AuthRequired -> Rejected must be invalid"
);
assert!(
!TaskState::AuthRequired.is_terminal(),
"AuthRequired must not be terminal"
);
}
#[test]
fn test_completed_transitions() {
assert_transitions(TaskState::Completed, &[]);
assert!(
TaskState::Completed.is_terminal(),
"Completed must be terminal"
);
for &target in &ALL_STATES {
assert!(
!TaskState::Completed.can_transition_to(target),
"Completed -> {target} must be invalid (terminal state)"
);
}
}
#[test]
fn test_failed_transitions() {
assert_transitions(TaskState::Failed, &[]);
assert!(TaskState::Failed.is_terminal(), "Failed must be terminal");
for &target in &ALL_STATES {
assert!(
!TaskState::Failed.can_transition_to(target),
"Failed -> {target} must be invalid (terminal state)"
);
}
}
#[test]
fn test_canceled_transitions() {
assert_transitions(TaskState::Canceled, &[]);
assert!(
TaskState::Canceled.is_terminal(),
"Canceled must be terminal"
);
for &target in &ALL_STATES {
assert!(
!TaskState::Canceled.can_transition_to(target),
"Canceled -> {target} must be invalid (terminal state)"
);
}
}
#[test]
fn test_rejected_transitions() {
assert_transitions(TaskState::Rejected, &[]);
assert!(
TaskState::Rejected.is_terminal(),
"Rejected must be terminal"
);
for &target in &ALL_STATES {
assert!(
!TaskState::Rejected.can_transition_to(target),
"Rejected -> {target} must be invalid (terminal state)"
);
}
}
#[test]
fn test_full_transition_matrix() {
let unspecified_targets: Vec<TaskState> = ALL_STATES.to_vec();
let submitted_targets = vec![
TaskState::Working,
TaskState::Failed,
TaskState::Canceled,
TaskState::Rejected,
];
let working_targets = vec![
TaskState::Completed,
TaskState::Failed,
TaskState::Canceled,
TaskState::InputRequired,
TaskState::AuthRequired,
];
let input_required_targets = vec![TaskState::Working, TaskState::Failed, TaskState::Canceled];
let auth_required_targets = vec![TaskState::Working, TaskState::Failed, TaskState::Canceled];
let no_targets: Vec<TaskState> = vec![];
let table: Vec<(TaskState, &Vec<TaskState>)> = vec![
(TaskState::Unspecified, &unspecified_targets),
(TaskState::Submitted, &submitted_targets),
(TaskState::Working, &working_targets),
(TaskState::InputRequired, &input_required_targets),
(TaskState::AuthRequired, &auth_required_targets),
(TaskState::Completed, &no_targets),
(TaskState::Failed, &no_targets),
(TaskState::Canceled, &no_targets),
(TaskState::Rejected, &no_targets),
];
let mut tested = 0;
for &(from, valid_targets) in &table {
for &to in &ALL_STATES {
let expected = valid_targets.contains(&to);
let actual = from.can_transition_to(to);
assert_eq!(
actual, expected,
"Matrix check: {from} -> {to}: expected {expected}, got {actual}"
);
tested += 1;
}
}
assert_eq!(tested, 81, "Expected 81 transition pairs (9x9 matrix)");
}
#[test]
fn test_terminal_states_are_symmetric() {
let terminals = [
TaskState::Completed,
TaskState::Failed,
TaskState::Canceled,
TaskState::Rejected,
];
for &term in &terminals {
assert!(
term.is_terminal(),
"{term} must report is_terminal() == true"
);
for &target in &ALL_STATES {
assert!(
!term.can_transition_to(target),
"{term} -> {target} must be blocked (terminal state)"
);
}
}
}
#[test]
fn test_non_terminal_states_report_not_terminal() {
let non_terminals = [
TaskState::Unspecified,
TaskState::Submitted,
TaskState::Working,
TaskState::InputRequired,
TaskState::AuthRequired,
];
for &state in &non_terminals {
assert!(
!state.is_terminal(),
"{state} must report is_terminal() == false"
);
}
}
#[test]
fn test_no_self_transitions_except_unspecified() {
for &state in &ALL_STATES {
let can_self = state.can_transition_to(state);
if state == TaskState::Unspecified {
assert!(
can_self,
"Unspecified -> Unspecified should be allowed (wildcard rule)"
);
} else {
assert!(
!can_self,
"{state} -> {state} self-transition must be invalid"
);
}
}
}
#[test]
fn test_valid_transition_counts() {
let expected_counts: [(TaskState, usize); 9] = [
(TaskState::Unspecified, 9), (TaskState::Submitted, 4), (TaskState::Working, 5), (TaskState::InputRequired, 3), (TaskState::AuthRequired, 3), (TaskState::Completed, 0), (TaskState::Failed, 0), (TaskState::Canceled, 0), (TaskState::Rejected, 0), ];
for &(state, expected) in &expected_counts {
let actual = ALL_STATES
.iter()
.filter(|&&target| state.can_transition_to(target))
.count();
assert_eq!(
actual, expected,
"{state} should have {expected} valid outgoing transitions, got {actual}"
);
}
}