use crate::reducer::create_test_state;
use crate::reducer::determine_next_effect;
use crate::reducer::effect::Effect;
use crate::reducer::reduce;
use crate::reducer::AgentRole;
use crate::reducer::PipelineEvent;
use crate::reducer::PipelinePhase;
use crate::reducer::PipelineState;
use crate::reducer::PromptMode;
#[test]
fn test_planning_initializes_agent_chain_when_empty() {
let state = create_test_state();
let effect = determine_next_effect(&state);
assert!(matches!(
effect,
Effect::InitializeAgentChain {
drain: crate::agents::AgentDrain::Planning,
..
}
));
}
#[test]
fn test_planning_prepares_prompt_when_agents_ready() {
let state = PipelineState {
phase: PipelinePhase::Planning,
gitignore_entries_ensured: true,
context_cleaned: true, agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Developer,
)
.with_drain(crate::agents::AgentDrain::Planning),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(effect, Effect::MaterializePlanningInputs { .. }));
}
#[test]
fn test_planning_role_mismatch_initializes_developer_chain() {
let state = PipelineState {
phase: PipelinePhase::Planning,
gitignore_entries_ensured: true,
context_cleaned: true,
agent_chain: crate::reducer::state::AgentChainState::initial().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::Planning,
..
}
));
}
#[test]
fn test_planning_resume_with_legacy_development_drain_reinitializes_planning_flow() {
let state = PipelineState {
phase: PipelinePhase::Planning,
gitignore_entries_ensured: true,
context_cleaned: true,
agent_chain: crate::reducer::state::AgentChainState::initial()
.with_agents(
vec!["dev-agent".to_string()],
vec![vec![]],
AgentRole::Developer,
)
.with_drain(crate::agents::AgentDrain::Development),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(
effect,
Effect::InitializeAgentChain {
drain: crate::agents::AgentDrain::Planning,
..
}
));
}
#[test]
fn test_planning_prompt_uses_xsd_retry_mode_when_pending() {
let state = PipelineState {
phase: PipelinePhase::Planning,
gitignore_entries_ensured: true,
context_cleaned: true,
iteration: 0,
total_iterations: 1,
continuation: PipelineState::initial(1, 1)
.continuation
.trigger_xsd_retry(),
agent_chain: PipelineState::initial(1, 1).agent_chain.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Developer,
),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(matches!(
effect,
Effect::PreparePlanningPrompt {
iteration: 0,
prompt_mode: PromptMode::XsdRetry
}
));
}
#[test]
fn test_planning_emits_prepare_prompt_effect() {
let state = PipelineState {
phase: PipelinePhase::Planning,
gitignore_entries_ensured: true,
context_cleaned: true,
iteration: 0,
total_iterations: 5,
agent_chain: PipelineState::initial(5, 2)
.agent_chain
.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Developer,
)
.with_drain(crate::agents::AgentDrain::Planning),
..create_test_state()
};
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::MaterializePlanningInputs { .. }),
"Planning should emit MaterializePlanningInputs, got {effect:?}"
);
}
#[test]
fn test_planning_transitions_to_development_after_completion() {
let mut state = PipelineState {
phase: PipelinePhase::Planning,
iteration: 1,
total_iterations: 5,
agent_chain: PipelineState::initial(5, 2).agent_chain.with_agents(
vec!["claude".to_string()],
vec![vec![]],
AgentRole::Developer,
),
..create_test_state()
};
state = reduce(state, PipelineEvent::plan_generation_completed(1, true));
assert_eq!(
state.phase,
PipelinePhase::Development,
"Phase should transition to Development after PlanGenerationCompleted"
);
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PrepareDevelopmentContext { .. }),
"Expected PrepareDevelopmentContext, got {effect:?}"
);
}
#[test]
fn test_planning_markdown_written_invalidates_downstream_materialized_inputs() {
let mut state = PipelineState {
phase: PipelinePhase::Planning,
iteration: 0,
gitignore_entries_ensured: true,
context_cleaned: true,
prompt_inputs: crate::reducer::state::PromptInputsState {
development: Some(crate::reducer::state::MaterializedDevelopmentInputs {
iteration: 0,
prompt: crate::reducer::state::MaterializedPromptInput {
kind: crate::reducer::state::PromptInputKind::Prompt,
content_id_sha256: "prompt".to_string(),
consumer_signature_sha256: "sig".to_string(),
original_bytes: 1,
final_bytes: 1,
model_budget_bytes: None,
inline_budget_bytes: None,
representation: crate::reducer::state::PromptInputRepresentation::Inline,
reason: crate::reducer::state::PromptMaterializationReason::WithinBudgets,
},
plan: crate::reducer::state::MaterializedPromptInput {
kind: crate::reducer::state::PromptInputKind::Plan,
content_id_sha256: "old-plan".to_string(),
consumer_signature_sha256: "sig".to_string(),
original_bytes: 1,
final_bytes: 1,
model_budget_bytes: None,
inline_budget_bytes: None,
representation: crate::reducer::state::PromptInputRepresentation::Inline,
reason: crate::reducer::state::PromptMaterializationReason::WithinBudgets,
},
}),
review: Some(crate::reducer::state::MaterializedReviewInputs {
pass: 0,
plan: crate::reducer::state::MaterializedPromptInput {
kind: crate::reducer::state::PromptInputKind::Plan,
content_id_sha256: "old-plan".to_string(),
consumer_signature_sha256: "sig".to_string(),
original_bytes: 1,
final_bytes: 1,
model_budget_bytes: None,
inline_budget_bytes: None,
representation: crate::reducer::state::PromptInputRepresentation::Inline,
reason: crate::reducer::state::PromptMaterializationReason::WithinBudgets,
},
diff: crate::reducer::state::MaterializedPromptInput {
kind: crate::reducer::state::PromptInputKind::Diff,
content_id_sha256: "diff".to_string(),
consumer_signature_sha256: "sig".to_string(),
original_bytes: 1,
final_bytes: 1,
model_budget_bytes: None,
inline_budget_bytes: None,
representation: crate::reducer::state::PromptInputRepresentation::Inline,
reason: crate::reducer::state::PromptMaterializationReason::WithinBudgets,
},
}),
..Default::default()
},
..create_test_state()
};
state = reduce(state, PipelineEvent::planning_markdown_written(0));
assert!(
state.prompt_inputs.development.is_none(),
"Expected development inputs to be invalidated when PLAN.md is written"
);
assert!(
state.prompt_inputs.review.is_none(),
"Expected review inputs to be invalidated when PLAN.md is written"
);
}