use super::*;
use crate::prompts::template_context::TemplateContext;
use crate::prompts::template_registry::TemplateRegistry;
use crate::prompts::PromptHistoryEntry;
use crate::reducer::event::{AgentEvent, PipelinePhase};
use std::fs;
use tempfile::tempdir;
#[test]
fn test_prepare_development_prompt_emits_template_invalid_event() {
let workspace = MemoryWorkspace::new_test()
.with_file("PROMPT.md", "Prompt with {{LITERAL}} braces")
.with_file(".agent/PLAN.md", "Plan content");
let mut fixture = TestFixture::with_workspace(workspace);
let ctx = fixture.ctx();
let mut handler = MainEffectHandler::new(PipelineState::initial(1, 1));
let materialize = handler
.materialize_development_inputs(&ctx, 0)
.expect("materialize_development_inputs should succeed");
handler.state = crate::reducer::reduce(handler.state.clone(), materialize.event);
for ev in materialize.additional_events {
handler.state = crate::reducer::reduce(handler.state.clone(), ev);
}
let result = handler
.prepare_development_prompt(&ctx, 0, PromptMode::Normal)
.expect("prepare_development_prompt should succeed");
assert!(matches!(
result.event,
PipelineEvent::Development(DevelopmentEvent::PromptPrepared { .. })
));
assert!(result.additional_events.iter().any(|ev| matches!(
ev,
PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered { .. })
)));
assert!(result.additional_events.iter().any(|ev| matches!(
ev,
PipelineEvent::PromptInput(PromptInputEvent::PromptCaptured {
key,
content_id: Some(id),
..
}) if key == "development_0" && id.len() == 64
)));
}
#[test]
fn test_prepare_development_prompt_emits_template_rendered_on_validation_failure() {
let tempdir = tempdir().expect("create temp dir");
let template_path = tempdir.path().join("developer_iteration_xml.txt");
fs::write(
&template_path,
"Prompt:\n{{PROMPT}}\nPlan:\n{{PLAN}}\nMissing: {{MISSING}}\n",
)
.expect("write developer template");
let workspace = MemoryWorkspace::new_test()
.with_file("PROMPT.md", "Prompt content")
.with_file(".agent/PLAN.md", "Plan content")
.with_dir(".agent/tmp");
let mut fixture = TestFixture::with_workspace(workspace);
fixture.template_context =
TemplateContext::new(TemplateRegistry::new(Some(tempdir.path().to_path_buf())));
let ctx = fixture.ctx();
let mut handler = MainEffectHandler::new(PipelineState::initial(1, 1));
let materialize = handler
.materialize_development_inputs(&ctx, 0)
.expect("materialize_development_inputs should succeed");
handler.state = crate::reducer::reduce(handler.state.clone(), materialize.event);
for ev in materialize.additional_events {
handler.state = crate::reducer::reduce(handler.state.clone(), ev);
}
let result = handler
.prepare_development_prompt(&ctx, 0, PromptMode::Normal)
.expect("prepare_development_prompt should succeed");
assert!(result.ui_events.iter().any(|ev| matches!(
ev,
crate::reducer::ui_event::UIEvent::PromptReplayHit { key, was_replayed: false }
if key == "development_0"
)));
match result.event {
PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase,
template_name,
log,
}) => {
assert_eq!(phase, PipelinePhase::Development);
assert_eq!(template_name, "developer_iteration_xml");
assert!(log.unsubstituted.contains(&"MISSING".to_string()));
}
other => panic!("expected TemplateRendered event, got {other:?}"),
}
assert!(
result.additional_events.iter().any(|event| matches!(
event,
PipelineEvent::Agent(AgentEvent::TemplateVariablesInvalid { missing_variables, .. })
if missing_variables.contains(&"MISSING".to_string())
)),
"expected TemplateVariablesInvalid with missing variables"
);
}
#[test]
fn test_prepare_development_prompt_normal_mode_ignores_continuation_state() {
let workspace = MemoryWorkspace::new_test()
.with_file("PROMPT.md", "Prompt")
.with_file(".agent/PLAN.md", "# Plan\n")
.with_dir(".agent/tmp");
let mut fixture = TestFixture::with_workspace(workspace);
let ctx = fixture.ctx();
let mut handler = MainEffectHandler::new(PipelineState {
continuation: ContinuationState {
continuation_attempt: 1,
..ContinuationState::new()
},
..PipelineState::initial(1, 1)
});
handler.state.prompt_history.insert(
"development_0_continuation_1".to_string(),
PromptHistoryEntry::from_string("{{UNRESOLVED}}".to_string()),
);
let materialize = handler
.materialize_development_inputs(&ctx, 0)
.expect("materialize_development_inputs should succeed");
handler.state = crate::reducer::reduce(handler.state.clone(), materialize.event);
for ev in materialize.additional_events {
handler.state = crate::reducer::reduce(handler.state.clone(), ev);
}
let result = handler
.prepare_development_prompt(&ctx, 0, PromptMode::Normal)
.expect("prepare_development_prompt should succeed");
assert!(
matches!(
result.event,
PipelineEvent::Development(DevelopmentEvent::PromptPrepared { .. })
),
"Expected PromptPrepared event when placeholders in PROMPT.md are ignored, got {:?}",
result.event
);
}
#[test]
fn test_prepare_development_prompt_detects_unresolved_partial() {
}
#[test]
fn test_prepare_development_prompt_returns_error_when_inputs_not_materialized() {
let workspace = MemoryWorkspace::new_test()
.with_file("PROMPT.md", "# Prompt\n")
.with_file(".agent/PLAN.md", "# Plan\n")
.with_dir(".agent/tmp");
let mut fixture = TestFixture::with_workspace(workspace);
let ctx = fixture.ctx();
let handler = MainEffectHandler::new(PipelineState::initial(1, 1));
let err = handler
.prepare_development_prompt(&ctx, 0, PromptMode::Normal)
.expect_err(
"prepare_development_prompt should return an error when inputs not materialized",
);
assert!(
err.to_string().contains("not materialized"),
"Expected error message about inputs not being materialized, got: {err}"
);
}