use super::*;
#[test]
fn test_output_validation_failed_retries_within_limit() {
let state = create_test_state();
let new_state = reduce(
state,
PipelineEvent::development_output_validation_failed(0, 0),
);
assert_eq!(new_state.phase, PipelinePhase::Development);
assert_eq!(new_state.continuation.invalid_output_attempts, 1);
assert_eq!(new_state.agent_chain.current_agent_index, 0);
assert_eq!(
new_state.agent_chain.current_mode,
crate::agents::DrainMode::XsdRetry
);
}
#[test]
fn test_output_validation_failed_increments_attempt_counter() {
let state = {
let base = create_test_state();
PipelineState {
continuation: ContinuationState {
invalid_output_attempts: 1,
..base.continuation.clone()
},
..base
}
};
let new_state = reduce(
state,
PipelineEvent::development_output_validation_failed(0, 1),
);
assert_eq!(new_state.phase, PipelinePhase::Development);
assert_eq!(new_state.continuation.invalid_output_attempts, 2);
assert_eq!(new_state.agent_chain.current_agent_index, 0);
}
#[test]
fn test_output_validation_failed_switches_agent_at_limit() {
use crate::reducer::state::ContinuationState;
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 1,
max_xsd_retry_count: 2,
..ContinuationState::new()
},
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::development_output_validation_failed(0, 0),
);
assert_eq!(new_state.continuation.invalid_output_attempts, 0);
assert!(
new_state.agent_chain.current_agent_index > 0,
"Should switch to next agent after max invalid output attempts"
);
}
#[test]
fn test_output_validation_failed_resets_counter_on_agent_switch() {
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 1,
max_xsd_retry_count: 2,
invalid_output_attempts: 2,
..ContinuationState::new()
},
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::development_output_validation_failed(0, 0),
);
assert_eq!(
new_state.continuation.invalid_output_attempts, 0,
"Counter should reset when switching agents"
);
}
#[test]
fn test_output_validation_failed_stays_in_development_phase() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Development,
..base
}
};
let new_state = reduce(
state,
PipelineEvent::development_output_validation_failed(0, 0),
);
assert_eq!(
new_state.phase,
PipelinePhase::Development,
"Should stay in Development phase for retry"
);
}
#[test]
fn test_output_validation_failed_respects_configured_xsd_retry_limit() {
use crate::reducer::state::ContinuationState;
let state = PipelineState {
phase: PipelinePhase::Development,
iteration: 1,
continuation: ContinuationState {
xsd_retry_count: 1,
max_xsd_retry_count: 5,
..ContinuationState::new()
},
agent_chain: AgentChainState::initial().with_agents(
vec!["agent1".to_string(), "agent2".to_string()],
vec![vec![], vec![]],
AgentRole::Developer,
),
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::development_output_validation_failed(1, 0),
);
assert_eq!(
new_state.agent_chain.current_agent_index, 0,
"Configured XSD retry limit should allow retries before agent fallback"
);
assert!(
new_state.continuation.xsd_retry_pending,
"Should request XSD retry while under configured limit"
);
}
#[test]
fn test_review_output_validation_failed_increments_state_counter() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 2,
..base
}
};
let new_state = reduce(
state,
PipelineEvent::review_output_validation_failed(0, 0, None),
);
assert_eq!(new_state.phase, PipelinePhase::Review);
assert_eq!(new_state.reviewer_pass, 0);
assert_eq!(new_state.continuation.invalid_output_attempts, 1);
assert_eq!(
new_state.agent_chain.current_mode,
crate::agents::DrainMode::XsdRetry
);
}
#[test]
fn test_review_output_validation_failed_switches_agent_after_limit() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 2,
continuation: ContinuationState {
same_agent_retry_count: 1,
same_agent_retry_pending: true,
same_agent_retry_reason: Some(SameAgentRetryReason::InternalError),
xsd_retry_count: 1,
max_xsd_retry_count: 2,
invalid_output_attempts: 2,
..ContinuationState::new()
},
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::review_output_validation_failed(0, 0, None),
);
assert_eq!(new_state.phase, PipelinePhase::Review);
assert_eq!(new_state.reviewer_pass, 0);
assert_eq!(
new_state.continuation.invalid_output_attempts, 0,
"Counter should reset when switching agents"
);
assert!(
new_state.agent_chain.current_agent_index > 0,
"Should switch to next agent after max invalid output attempts"
);
assert_eq!(
new_state.continuation.same_agent_retry_count, 0,
"Same-agent retry budget must not carry across agents"
);
assert!(
!new_state.continuation.same_agent_retry_pending,
"Same-agent retry pending must be cleared when switching agents"
);
assert!(
new_state.continuation.same_agent_retry_reason.is_none(),
"Same-agent retry reason must be cleared when switching agents"
);
}
#[test]
fn test_review_pass_completed_clean_exits_review_phase() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 2,
..base
}
};
let new_state = reduce(state, PipelineEvent::review_pass_completed_clean(0));
assert_eq!(
new_state.phase,
PipelinePhase::Review,
"Clean pass should not exit review when passes remain"
);
assert_eq!(new_state.reviewer_pass, 1);
assert!(!new_state.review_issues_found);
}
#[test]
fn test_review_pass_completed_clean_on_last_pass_clears_previous_phase() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 1,
previous_phase: Some(PipelinePhase::Development),
..base
}
};
let new_state = reduce(state, PipelineEvent::review_pass_completed_clean(0));
assert_eq!(new_state.phase, PipelinePhase::CommitMessage);
assert_eq!(new_state.previous_phase, Some(PipelinePhase::Review));
assert!(matches!(new_state.commit, CommitState::NotStarted));
}
#[test]
fn test_review_pass_started_does_not_reset_invalid_output_attempts_on_retry() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
continuation: ContinuationState {
invalid_output_attempts: 1,
..base.continuation.clone()
},
..base
}
};
let new_state = reduce(state, PipelineEvent::review_pass_started(0));
assert_eq!(new_state.reviewer_pass, 0);
assert_eq!(
new_state.continuation.invalid_output_attempts, 1,
"Retrying the same pass should not clear invalid output attempt counter"
);
}
#[test]
fn test_review_pass_started_preserves_agent_chain_on_retry() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 2,
continuation: ContinuationState {
xsd_retry_count: 1,
max_xsd_retry_count: 2,
..ContinuationState::new()
},
..base
}
};
let state = reduce(
state,
PipelineEvent::review_output_validation_failed(0, 0, None),
);
assert!(
state.agent_chain.current_agent_index > 0,
"Precondition: review_output_validation_failed should have switched agents when XSD retry limit reached"
);
let new_state = reduce(state.clone(), PipelineEvent::review_pass_started(0));
assert_eq!(
new_state.agent_chain.current_agent_index, state.agent_chain.current_agent_index,
"Retrying the same pass should preserve the current agent selection"
);
}
#[test]
fn test_review_pass_started_resets_invalid_output_attempts_for_new_pass() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
continuation: ContinuationState {
invalid_output_attempts: 2,
..base.continuation.clone()
},
..base
}
};
let new_state = reduce(state, PipelineEvent::review_pass_started(1));
assert_eq!(new_state.reviewer_pass, 1);
assert_eq!(new_state.continuation.invalid_output_attempts, 0);
}
#[test]
fn test_review_phase_completed_resets_commit_state() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
commit: CommitState::Committed {
hash: "abc123".to_string(),
},
..base
}
};
let new_state = reduce(state, PipelineEvent::review_phase_completed(true));
assert_eq!(new_state.phase, PipelinePhase::CommitMessage);
assert!(matches!(new_state.commit, CommitState::NotStarted));
assert_eq!(new_state.previous_phase, Some(PipelinePhase::Review));
}
#[test]
fn test_review_completed_no_issues_on_last_pass_resets_commit_state() {
let state = {
let base = create_test_state();
PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 1,
commit: CommitState::Committed {
hash: "abc123".to_string(),
},
..base
}
};
let new_state = reduce(state, PipelineEvent::review_completed(0, false));
assert_eq!(new_state.phase, PipelinePhase::CommitMessage);
assert!(matches!(new_state.commit, CommitState::NotStarted));
}