use std::sync::Arc;
use super::*;
use crate::agents::AgentRole;
use crate::common::domain_types::AgentName;
use crate::reducer::event::CheckpointTrigger;
use crate::reducer::state::{ContinuationState, MAX_VALIDATION_RETRY_ATTEMPTS};
#[test]
fn test_commit_generation_started_sets_generating_state() {
let state = create_test_state();
let new_state = reduce(state.clone(), PipelineEvent::commit_generation_started());
assert_eq!(new_state.phase, state.phase);
assert!(matches!(
new_state.commit,
CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS
}
));
}
#[test]
fn test_commit_prompt_prepared_starts_generation_when_not_started() {
let state = PipelineState {
commit: CommitState::NotStarted,
..create_test_state()
};
let new_state = reduce(state, PipelineEvent::commit_prompt_prepared(1));
assert!(matches!(
new_state.commit,
CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS
}
));
}
#[test]
fn test_commit_message_generated_sets_commit_to_generated() {
let state = create_test_state();
let new_state = reduce(
state,
PipelineEvent::commit_message_generated("feat: add feature".to_string(), 1),
);
assert!(matches!(new_state.commit, CommitState::Generated { .. }));
}
#[test]
fn test_commit_message_generated_stores_message() {
let state = create_test_state();
let message = "fix: resolve bug".to_string();
let new_state = reduce(
state,
PipelineEvent::commit_message_generated(message.clone(), 1),
);
if let CommitState::Generated {
message: stored_msg,
} = new_state.commit
{
assert_eq!(stored_msg, message);
} else {
panic!("Expected CommitState::Generated");
}
}
#[test]
fn test_commit_created_sets_commit_to_committed() {
let state = create_test_state();
let new_state = reduce(
state,
PipelineEvent::commit_created("abc123".to_string(), "feat: test".to_string()),
);
assert!(matches!(new_state.commit, CommitState::Committed { .. }));
}
#[test]
fn test_commit_created_transitions_to_final_validation() {
let state = create_state_in_phase(PipelinePhase::CommitMessage);
let new_state = reduce(
state,
PipelineEvent::commit_created("abc123".to_string(), "feat: test".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::FinalValidation);
}
#[test]
fn test_commit_created_stores_hash() {
let state = create_test_state();
let hash = "abc123def456".to_string();
let new_state = reduce(
state,
PipelineEvent::commit_created(hash.clone(), "test".to_string()),
);
if let CommitState::Committed { hash: stored_hash } = new_state.commit {
assert_eq!(stored_hash, hash);
} else {
panic!("Expected CommitState::Committed");
}
}
#[test]
fn test_commit_message_validation_failed_retries() {
let state = PipelineState {
commit: CommitState::Generated {
message: "test".to_string(),
},
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid format".to_string(), 1),
);
assert!(matches!(
new_state.commit,
CommitState::Generating { attempt: 1, .. }
));
assert!(new_state.continuation.xsd_retry_pending);
}
#[test]
fn test_commit_message_validation_failed_exhausts_attempts_with_more_agents() {
let base_state = create_test_state();
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 0,
max_xsd_retry_count: 1,
..ContinuationState::new()
},
agent_chain: base_state.agent_chain.with_agents(
vec![
"commit-agent-1".to_string(),
"commit-agent-2".to_string(),
"commit-agent-3".to_string(),
],
vec![vec![], vec![], vec![]],
AgentRole::Commit,
),
commit: CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS,
},
..base_state
};
let new_state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid format".to_string(), 1),
);
assert_eq!(new_state.agent_chain.current_agent_index, 1);
assert!(matches!(
new_state.commit,
CommitState::Generating { attempt: 1, .. }
));
assert_eq!(new_state.continuation.xsd_retry_count, 0);
assert!(!new_state.continuation.xsd_retry_pending);
}
#[test]
fn test_commit_prompt_prepared_clears_xsd_retry_pending() {
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_pending: true,
..ContinuationState::new()
},
..create_state_in_phase(PipelinePhase::CommitMessage)
};
let new_state = reduce(state, PipelineEvent::commit_prompt_prepared(1));
assert!(
!new_state.continuation.xsd_retry_pending,
"commit prompt preparation should clear xsd_retry_pending to prevent infinite retry loops"
);
}
#[test]
fn test_commit_message_validation_failed_exhausts_all_agents() {
let base_state = create_test_state();
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 0,
max_xsd_retry_count: 1,
..ContinuationState::new()
},
agent_chain: base_state
.agent_chain
.with_agents(
vec![
"commit-agent-1".to_string(),
"commit-agent-2".to_string(),
"commit-agent-3".to_string(),
],
vec![vec![], vec![], vec![]],
AgentRole::Commit,
)
.switch_to_next_agent()
.switch_to_next_agent(), commit: CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS,
},
..base_state
};
assert_eq!(state.agent_chain.current_agent_index, 2);
assert_eq!(state.agent_chain.retry_cycle, 0);
let new_state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid format".to_string(), 1),
);
assert_eq!(new_state.agent_chain.current_agent_index, 0);
assert_eq!(new_state.agent_chain.retry_cycle, 1);
assert!(matches!(new_state.commit, CommitState::NotStarted));
}
#[test]
fn test_commit_message_validation_failed_with_single_agent() {
let base_state = create_test_state();
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 0,
max_xsd_retry_count: 1,
..ContinuationState::new()
},
agent_chain: base_state.agent_chain.with_agents(
vec!["commit-agent-1".to_string()],
vec![vec![]],
AgentRole::Commit,
),
commit: CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS,
},
..base_state
};
let new_state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid format".to_string(), 1),
);
assert!(matches!(new_state.commit, CommitState::NotStarted));
}
#[test]
fn test_commit_skipped_sets_commit_to_skipped() {
let state = create_test_state();
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes".to_string()),
);
assert!(matches!(new_state.commit, CommitState::Skipped));
}
#[test]
fn test_commit_skipped_transitions_to_final_validation() {
let state = create_state_in_phase(PipelinePhase::CommitMessage);
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::FinalValidation);
}
#[test]
fn test_commit_generation_failed_resets_commit_to_not_started() {
let state = PipelineState {
commit: CommitState::Generating {
attempt: 5,
max_attempts: 10,
},
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_generation_failed(
"Agent failed to generate valid commit message".to_string(),
),
);
assert!(matches!(new_state.commit, CommitState::NotStarted));
}
#[test]
fn test_checkpoint_saved_preserves_all_state() {
let state = PipelineState {
phase: PipelinePhase::Development,
iteration: 3,
reviewer_pass: 1,
commit: CommitState::Generated {
message: "test".to_string(),
},
..create_test_state()
};
let new_state = reduce(
state.clone(),
PipelineEvent::checkpoint_saved(CheckpointTrigger::PhaseTransition),
);
assert_eq!(new_state.phase, state.phase);
assert_eq!(new_state.iteration, state.iteration);
assert_eq!(new_state.reviewer_pass, state.reviewer_pass);
assert!(matches!(new_state.commit, CommitState::Generated { .. }));
}
#[test]
fn test_checkpoint_saved_with_different_triggers() {
let state = PipelineState {
phase: PipelinePhase::Review,
iteration: 2,
..create_test_state()
};
for trigger in [
CheckpointTrigger::PhaseTransition,
CheckpointTrigger::IterationComplete,
CheckpointTrigger::BeforeRebase,
CheckpointTrigger::Interrupt,
] {
let new_state = reduce(state.clone(), PipelineEvent::checkpoint_saved(trigger));
assert_eq!(new_state.phase, state.phase);
assert_eq!(new_state.iteration, state.iteration);
}
}
#[test]
fn test_commit_message_generated_increments_attempt() {
let state = create_test_state();
let new_state = reduce(
state,
PipelineEvent::commit_message_generated("first".to_string(), 1),
);
assert!(matches!(new_state.commit, CommitState::Generated { .. }));
if let CommitState::Generated { message } = &new_state.commit {
assert_eq!(message, "first");
}
let new_state2 = reduce(
new_state,
PipelineEvent::commit_message_generated("second".to_string(), 2),
);
assert!(matches!(new_state2.commit, CommitState::Generated { .. }));
if let CommitState::Generated { message } = &new_state2.commit {
assert_eq!(message, "second");
}
}
#[test]
fn test_commit_skipped_returns_to_planning_after_development() {
let state = PipelineState {
phase: PipelinePhase::CommitMessage,
previous_phase: Some(PipelinePhase::Development),
iteration: 0,
total_iterations: 3,
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes to commit".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::Planning);
assert_eq!(new_state.iteration, 1); assert!(new_state.previous_phase.is_none());
}
#[test]
fn test_commit_skipped_goes_to_review_after_last_development_iteration() {
let state = PipelineState {
phase: PipelinePhase::CommitMessage,
previous_phase: Some(PipelinePhase::Development),
iteration: 2, total_iterations: 3,
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes to commit".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::Review);
assert_eq!(new_state.iteration, 3);
}
#[test]
fn test_commit_created_skips_review_when_no_reviewer_passes_configured() {
let state = PipelineState {
phase: PipelinePhase::CommitMessage,
previous_phase: Some(PipelinePhase::Development),
iteration: 0,
total_iterations: 1,
total_reviewer_passes: 0,
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_created("abc123".to_string(), "test".to_string()),
);
assert_eq!(
new_state.phase,
PipelinePhase::FinalValidation,
"With total_reviewer_passes=0, pipeline should skip Review after last dev commit"
);
}
#[test]
fn test_commit_skipped_returns_to_review_after_fix_attempt() {
let state = PipelineState {
phase: PipelinePhase::CommitMessage,
previous_phase: Some(PipelinePhase::Review),
reviewer_pass: 0,
total_reviewer_passes: 2,
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes to commit".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::Review);
assert_eq!(new_state.reviewer_pass, 1); }
#[test]
fn test_commit_skipped_goes_to_final_validation_after_last_review() {
let state = PipelineState {
phase: PipelinePhase::CommitMessage,
previous_phase: Some(PipelinePhase::Review),
reviewer_pass: 1, total_reviewer_passes: 2,
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes to commit".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::FinalValidation);
}
#[test]
fn test_commit_skipped_no_previous_phase_goes_to_final_validation() {
let state = PipelineState {
phase: PipelinePhase::CommitMessage,
previous_phase: None,
..create_test_state()
};
let new_state = reduce(
state,
PipelineEvent::commit_skipped("No changes to commit".to_string()),
);
assert_eq!(new_state.phase, PipelinePhase::FinalValidation);
}
#[test]
fn test_commit_agent_chain_initialized_preserves_role() {
let state = create_test_state();
let agents = vec!["reviewer1".to_string()];
let new_state = reduce(
state,
PipelineEvent::agent_chain_initialized(
crate::agents::AgentDrain::Commit,
agents.iter().cloned().map(AgentName::from).collect(),
vec![],
3,
1000,
2.0,
60000,
),
);
assert_eq!(new_state.agent_chain.agents, Arc::from(agents));
assert_eq!(new_state.agent_chain.current_role, AgentRole::Commit);
}
#[test]
fn test_commit_agent_chain_fallback_works_with_reviewer_agents() {
let base_state = create_test_state();
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 0,
max_xsd_retry_count: 1,
..ContinuationState::new()
},
agent_chain: base_state.agent_chain.with_agents(
vec!["reviewer1".to_string(), "reviewer2".to_string()],
vec![vec![], vec![]],
AgentRole::Commit, ),
commit: CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS,
},
..base_state
};
assert_eq!(state.agent_chain.current_agent_index, 0);
assert_eq!(state.agent_chain.current_role, AgentRole::Commit);
let new_state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid format".to_string(), 1),
);
assert_eq!(new_state.agent_chain.current_agent_index, 1);
assert!(matches!(
new_state.commit,
CommitState::Generating { attempt: 1, .. }
));
}
#[test]
fn test_commit_agent_chain_empty_gives_up_immediately() {
let base_state = create_test_state();
let state = PipelineState {
continuation: ContinuationState {
xsd_retry_count: 0,
max_xsd_retry_count: 1,
..ContinuationState::new()
},
agent_chain: base_state.agent_chain.with_agents(
vec![], vec![],
AgentRole::Commit,
),
phase: PipelinePhase::CommitMessage,
commit: CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS,
},
..base_state
};
let new_state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid format".to_string(), 1),
);
assert!(matches!(new_state.commit, CommitState::NotStarted));
}
#[test]
fn test_commit_agent_role_preserved_across_retries() {
let base_state = create_test_state();
let mut state = PipelineState {
agent_chain: base_state.agent_chain.with_agents(
vec!["reviewer1".to_string()],
vec![vec![]],
AgentRole::Commit,
),
commit: CommitState::Generating {
attempt: 1,
max_attempts: MAX_VALIDATION_RETRY_ATTEMPTS,
},
..base_state
};
for i in 1..MAX_VALIDATION_RETRY_ATTEMPTS {
state = reduce(
state,
PipelineEvent::commit_message_validation_failed("Invalid".to_string(), i),
);
assert_eq!(state.agent_chain.current_role, AgentRole::Commit);
}
}