use super::*;
use crate::reducer::state::ReviewValidatedOutcome;
#[test]
fn test_determine_effect_review_phase_empty_chain() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
agent_chain: AgentChainState::initial(),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(
effect,
Effect::InitializeAgentChain {
drain: crate::agents::AgentDrain::Review,
..
}
));
}
#[test]
fn test_determine_effect_review_phase_exhausted_chain() {
let mut chain = AgentChainState::initial()
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review)
.with_max_cycles(3);
chain = chain.start_retry_cycle();
chain = chain.start_retry_cycle();
chain = chain.start_retry_cycle();
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
agent_chain: chain,
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(effect, Effect::SaveCheckpoint { .. }));
}
#[test]
fn test_determine_effect_review_exhausted_chain_after_checkpoint_aborts() {
let mut chain = AgentChainState::initial()
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review)
.with_max_cycles(3);
chain = chain.start_retry_cycle();
chain = chain.start_retry_cycle();
chain = chain.start_retry_cycle();
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
checkpoint_saved_count: 1,
agent_chain: chain,
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(effect, Effect::ReportAgentChainExhausted { .. }));
}
#[test]
fn test_determine_effect_review_phase_with_chain() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(effect, Effect::PrepareReviewContext { pass: 1 }));
}
#[test]
fn test_resume_scenario_at_final_review_pass_runs_work() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 2,
total_reviewer_passes: 2,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
review_context_prepared_pass: None,
review_prompt_prepared_pass: None,
review_required_files_cleaned_pass: None,
review_agent_invoked_pass: None,
review_issues_xml_extracted_pass: None,
review_validated_outcome: None,
review_issues_markdown_written_pass: None,
review_issue_snippets_extracted_pass: None,
review_issues_xml_archived_pass: None,
fix_prompt_prepared_pass: None,
fix_required_files_cleaned_pass: None,
fix_agent_invoked_pass: None,
fix_result_xml_extracted_pass: None,
fix_validated_outcome: None,
fix_result_xml_archived_pass: None,
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(effect, Effect::PrepareReviewContext { .. }));
}
#[test]
fn test_review_triggers_fix_when_issues_found() {
let mut state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareReviewContext { pass: 0 }),
"Expected PrepareReviewContext, got {effect:?}"
);
state = reduce(state, PipelineEvent::review_completed(0, true));
assert!(
state.review_issues_found,
"review_issues_found should be true"
);
let effect = determine_next_effect(&state);
assert!(
matches!(
effect,
Effect::InitializeAgentChain {
drain: crate::agents::AgentDrain::Fix,
..
}
),
"Expected fix drain initialization after issues found, got {effect:?}"
);
state = reduce(state, PipelineEvent::fix_attempt_completed(0, true));
assert!(
!state.review_issues_found,
"review_issues_found should be reset after fix"
);
assert_eq!(
state.reviewer_pass, 0,
"Pass stays at 0 until CommitCreated"
);
assert_eq!(
state.phase,
PipelinePhase::CommitMessage,
"Should go to CommitMessage phase after fix"
);
state = reduce(state, PipelineEvent::commit_generation_started());
state = reduce(
state,
PipelineEvent::commit_created("abc123".to_string(), "fix commit".to_string()),
);
assert_eq!(
state.reviewer_pass, 1,
"Should increment to next pass after commit"
);
assert_eq!(
state.phase,
PipelinePhase::Review,
"Should return to Review phase after commit"
);
}
#[test]
fn test_review_runs_exactly_n_passes() {
let mut state = PipelineState::initial(0, 3); state.agent_chain = state
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review);
let mut passes_run = Vec::new();
let max_steps = 30;
for _ in 0..max_steps {
let effect = determine_next_effect(&state);
match effect {
Effect::LockPromptPermissions => {
state = reduce(state, PipelineEvent::prompt_permissions_locked(None));
}
Effect::RestorePromptPermissions => {
state = reduce(state, PipelineEvent::prompt_permissions_restored());
}
Effect::InitializeAgentChain { drain, .. } => {
state = reduce(
state,
PipelineEvent::agent_chain_initialized(
drain,
vec![AgentName::from("claude")],
vec![],
3,
1000,
2.0,
60000,
),
);
}
Effect::PrepareReviewContext { pass } => {
passes_run.push(pass);
state = reduce(state, PipelineEvent::review_context_prepared(pass));
state = reduce(state, PipelineEvent::review_prompt_prepared(pass));
state = reduce(state, PipelineEvent::review_issues_xml_cleaned(pass));
state = reduce(state, PipelineEvent::review_agent_invoked(pass));
state = reduce(state, PipelineEvent::review_issues_xml_extracted(pass));
state = reduce(
state,
PipelineEvent::review_issues_xml_validated(
pass,
false,
true,
Vec::new(),
Some("ok".to_string()),
),
);
state = reduce(state, PipelineEvent::review_issues_markdown_written(pass));
state = reduce(state, PipelineEvent::review_issue_snippets_extracted(pass));
state = reduce(state, PipelineEvent::review_issues_xml_archived(pass));
state = reduce(state, PipelineEvent::review_pass_completed_clean(pass));
}
Effect::SaveCheckpoint { .. } => {
break;
}
_ => break,
}
}
assert_eq!(
passes_run.len(),
3,
"Should run exactly 3 review passes, ran: {passes_run:?}"
);
assert_eq!(passes_run, vec![0, 1, 2], "Should run passes 0-2");
assert_eq!(
state.phase,
PipelinePhase::CommitMessage,
"Should transition to CommitMessage after reviews"
);
}
#[test]
fn test_review_skips_fix_when_no_issues() {
let mut state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(effect, Effect::PrepareReviewContext { pass: 0 }));
state = reduce(state, PipelineEvent::review_completed(0, false));
assert!(
!state.review_issues_found,
"review_issues_found should be false"
);
assert_eq!(
state.reviewer_pass, 1,
"Should increment to next pass when no issues"
);
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareReviewContext { pass: 1 }),
"Expected PrepareReviewContext pass 1 when no issues, got {effect:?}"
);
}
#[test]
fn test_same_agent_retry_in_fix_drain_uses_fix_prompt_without_review_flags() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Fix)
.with_mode(crate::agents::DrainMode::SameAgentRetry),
continuation: crate::reducer::state::ContinuationState {
same_agent_retry_pending: true,
..crate::reducer::state::ContinuationState::default()
},
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareFixPrompt { pass: 1, .. }),
"expected fix retry prompt for fix drain, got {effect:?}"
);
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
continuation: crate::reducer::state::ContinuationState {
fix_continue_pending: true,
fix_continuation_attempt: 1,
..crate::reducer::state::ContinuationState::default()
},
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(
effect,
Effect::InitializeAgentChain {
drain: crate::agents::AgentDrain::Fix,
}
),
"fix continuation markers should reinitialize the fix drain when review is still loaded, got {effect:?}"
);
}
#[test]
fn test_determine_effect_review_phase_with_wrong_role_chain() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 1,
total_reviewer_passes: 2,
agent_chain: PipelineState::initial(5, 2).agent_chain.with_agents(
vec!["commit-agent".to_string()],
vec![vec![]],
AgentRole::Commit, ),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(
effect,
Effect::InitializeAgentChain {
drain: crate::agents::AgentDrain::Review,
..
}
),
"Expected InitializeAgentChain for the review drain, got {effect:?}"
);
}
#[test]
fn test_resume_at_final_review_pass_should_run_review_not_skip() {
let state = PipelineState {
phase: PipelinePhase::Review,
iteration: 3,
total_iterations: 3,
reviewer_pass: 2,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: PipelineState::initial(3, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
review_context_prepared_pass: None,
review_prompt_prepared_pass: None,
review_required_files_cleaned_pass: None,
review_agent_invoked_pass: None,
review_issues_xml_extracted_pass: None,
review_validated_outcome: None,
review_issues_markdown_written_pass: None,
review_issue_snippets_extracted_pass: None,
review_issues_xml_archived_pass: None,
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareReviewContext { .. }),
"Expected PrepareReviewContext, got {effect:?}"
);
}
#[test]
fn test_resume_at_final_review_pass_with_no_progress_should_run_review() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 2,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: AgentChainState::initial()
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
review_context_prepared_pass: None,
review_prompt_prepared_pass: None,
review_agent_invoked_pass: None,
review_issues_xml_archived_pass: None,
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareReviewContext { pass: 2 }),
"Expected PrepareReviewContext but got {effect:?}"
);
}
#[test]
fn test_resume_at_review_pass_zero_with_total_one_runs_work() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 0,
total_reviewer_passes: 1,
review_issues_found: false,
agent_chain: AgentChainState::initial()
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
review_agent_invoked_pass: None,
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareReviewContext { pass: 0 }),
"Expected PrepareReviewContext but got {effect:?}"
);
}
#[test]
fn test_review_pass_completed_applies_outcome_not_reruns() {
let state = PipelineState {
phase: PipelinePhase::Review,
reviewer_pass: 2,
total_reviewer_passes: 2,
review_issues_found: false,
agent_chain: PipelineState::initial(3, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Reviewer,
)
.with_drain(crate::agents::AgentDrain::Review),
review_issues_xml_archived_pass: Some(2),
review_context_prepared_pass: Some(2),
review_prompt_prepared_pass: Some(2),
review_required_files_cleaned_pass: Some(2),
review_agent_invoked_pass: Some(2),
review_issues_xml_extracted_pass: Some(2),
review_validated_outcome: Some(ReviewValidatedOutcome {
pass: 2,
issues_found: false,
clean_no_issues: true,
issues: Vec::new().into_boxed_slice(),
no_issues_found: Some("ok".to_string()),
}),
review_issues_markdown_written_pass: Some(2),
review_issue_snippets_extracted_pass: Some(2),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(
effect,
Effect::ApplyReviewOutcome {
pass: 2,
issues_found: false,
clean_no_issues: true
}
),
"Expected ApplyReviewOutcome for completed review, got {effect:?}"
);
}