use super::*;
use crate::agent::prompt::APPLY_SYSTEM_PROMPT;
use crate::config::OrchestratorConfig;
#[test]
fn test_agent_runner_creation() {
let config = OrchestratorConfig {
apply_command: Some("test apply {change_id}".to_string()),
..Default::default()
};
let runner = AgentRunner::new(config);
assert_eq!(
runner.config().get_apply_command().unwrap(),
"test apply {change_id}"
);
}
#[test]
fn test_agent_runner_with_custom_config() {
let config = OrchestratorConfig {
apply_command: Some("custom-agent apply {change_id}".to_string()),
archive_command: Some("custom-agent archive {change_id}".to_string()),
analyze_command: Some("custom-agent analyze '{prompt}'".to_string()),
..Default::default()
};
let runner = AgentRunner::new(config);
assert_eq!(
runner.config().get_apply_command().unwrap(),
"custom-agent apply {change_id}"
);
assert_eq!(
runner.config().get_archive_command().unwrap(),
"custom-agent archive {change_id}"
);
}
#[tokio::test]
async fn test_run_apply_echo_command() {
let config = OrchestratorConfig {
apply_command: Some("echo {change_id}".to_string()),
..Default::default()
};
let mut runner = AgentRunner::new(config);
let result = runner.run_apply("test-change").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_run_archive_echo_command() {
let config = OrchestratorConfig {
archive_command: Some("echo {change_id}".to_string()),
..Default::default()
};
let runner = AgentRunner::new(config);
let result = runner.run_archive("test-change").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_analyze_dependencies_echo_command() {
let config = OrchestratorConfig {
analyze_command: Some("echo '{prompt}'".to_string()),
..Default::default()
};
let runner = AgentRunner::new(config);
let result = runner.analyze_dependencies("test prompt").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().trim(), "test prompt");
}
#[tokio::test]
async fn test_run_apply_streaming() {
let config = OrchestratorConfig {
apply_command: Some("echo test".to_string()),
..Default::default()
};
let mut runner = AgentRunner::new(config);
let result = runner.run_apply_streaming("test-change", None).await;
assert!(result.is_ok());
let (mut child, mut rx, _start) = result.unwrap();
let mut lines = Vec::new();
while let Some(line) = rx.recv().await {
lines.push(line);
}
let status = child.wait().await.unwrap();
assert!(status.success());
assert!(!lines.is_empty());
}
#[tokio::test]
async fn test_run_apply_with_prompt_expansion() {
let config = OrchestratorConfig {
apply_command: Some("echo {change_id} {prompt}".to_string()),
apply_prompt: Some("prompt-marker".to_string()),
..Default::default()
};
let mut runner = AgentRunner::new(config);
let result = runner.run_apply_streaming("my-change", None).await;
assert!(result.is_ok());
let (mut child, mut rx, _start) = result.unwrap();
let mut lines = Vec::new();
while let Some(line) = rx.recv().await {
lines.push(line);
}
let status = child.wait().await.unwrap();
assert!(status.success());
let output: String = lines
.iter()
.map(|l| match l {
OutputLine::Stdout(s) => s.clone(),
OutputLine::Stderr(s) => s.clone(),
})
.collect();
assert!(output.contains("my-change"));
assert!(output.contains("prompt-marker"));
}
#[tokio::test]
async fn test_run_apply_with_default_prompt() {
let config = OrchestratorConfig {
apply_command: Some("echo {prompt}".to_string()),
apply_prompt: None, ..Default::default()
};
let mut runner = AgentRunner::new(config);
let result = runner.run_apply_streaming("my-change", None).await;
assert!(result.is_ok());
let (mut child, mut rx, _start) = result.unwrap();
let mut lines = Vec::new();
while let Some(line) = rx.recv().await {
lines.push(line);
}
let status = child.wait().await.unwrap();
assert!(status.success());
}
#[tokio::test]
async fn test_run_archive_with_empty_default_prompt() {
let config = OrchestratorConfig {
archive_command: Some("echo {prompt}".to_string()),
archive_prompt: None, ..Default::default()
};
let runner = AgentRunner::new(config);
let result = runner.run_archive_streaming("my-change", None).await;
assert!(result.is_ok());
let (mut child, mut rx, _start) = result.unwrap();
let mut lines = Vec::new();
while let Some(line) = rx.recv().await {
lines.push(line);
}
let status = child.wait().await.unwrap();
assert!(status.success());
}
#[tokio::test]
async fn test_run_apply_streaming_with_prompt() {
let config = OrchestratorConfig {
apply_command: Some("echo {change_id} {prompt}".to_string()),
apply_prompt: Some("prompt-marker".to_string()),
..Default::default()
};
let mut runner = AgentRunner::new(config);
let result = runner.run_apply_streaming("my-change", None).await;
assert!(result.is_ok());
let (mut child, mut rx, _start) = result.unwrap();
let mut lines = Vec::new();
while let Some(line) = rx.recv().await {
lines.push(line);
}
let status = child.wait().await.unwrap();
assert!(status.success());
let output: String = lines
.iter()
.map(|l| match l {
OutputLine::Stdout(s) => s.clone(),
OutputLine::Stderr(s) => s.clone(),
})
.collect();
assert!(output.contains("my-change"));
assert!(output.contains("prompt-marker"));
}
#[test]
fn test_build_apply_prompt_with_all_parts() {
let user_prompt = "Focus on implementation.";
let history_context = "Previous attempt failed.";
let acceptance_tail = "";
let result = build_apply_prompt("my-change", user_prompt, history_context, acceptance_tail);
assert!(result.contains("Focus on implementation."));
assert!(result.contains("Previous attempt failed."));
}
#[test]
fn test_build_apply_prompt_with_empty_user_prompt() {
let user_prompt = "";
let history_context = "Previous attempt failed.";
let acceptance_tail = "";
let result = build_apply_prompt("my-change", user_prompt, history_context, acceptance_tail);
assert!(result.contains("Previous attempt failed."));
}
#[test]
fn test_build_apply_prompt_with_empty_history() {
let user_prompt = "Focus on implementation.";
let history_context = "";
let acceptance_tail = "";
let result = build_apply_prompt("my-change", user_prompt, history_context, acceptance_tail);
assert!(result.contains("Focus on implementation."));
}
#[test]
fn test_build_apply_prompt_with_only_system_prompt() {
let user_prompt = "";
let history_context = "";
let acceptance_tail = "";
let result = build_apply_prompt("my-change", user_prompt, history_context, acceptance_tail);
assert!(result.contains("load skills: cflx-apply"));
assert!(result.contains("Apply change id: my-change"));
assert!(result.contains(APPLY_SYSTEM_PROMPT));
}
#[test]
fn test_build_apply_prompt_with_acceptance_tail() {
let user_prompt = "Focus on implementation.";
let history_context = "<last_apply attempt=\"1\">\nstatus: failed\n</last_apply>";
let acceptance_tail =
"<last_acceptance_output>\nTest failure detected\n</last_acceptance_output>";
let result = build_apply_prompt("my-change", user_prompt, history_context, acceptance_tail);
assert!(result.contains("Focus on implementation."));
assert!(result.contains("<last_acceptance_output>"));
assert!(result.contains("Test failure detected"));
assert!(result.contains("<last_apply attempt=\"1\">"));
let user_pos = result.find("Focus on implementation.").unwrap();
let acceptance_pos = result.find("<last_acceptance_output>").unwrap();
let history_pos = result.find("<last_apply attempt=\"1\">").unwrap();
assert!(
user_pos < acceptance_pos,
"User prompt should come before acceptance tail"
);
assert!(
acceptance_pos < history_pos,
"Acceptance tail should come before history"
);
}
#[test]
fn test_build_apply_prompt_with_acceptance_tail_priority() {
use super::build_last_acceptance_output_context;
let stdout_tail = Some("stdout content");
let stderr_tail = Some("stderr content");
let context = build_last_acceptance_output_context(stdout_tail, stderr_tail);
assert!(context.contains("stdout content"));
assert!(context.contains("stderr content"));
let context = build_last_acceptance_output_context(None, stderr_tail);
assert!(context.contains("stderr content"));
assert!(!context.contains("stdout"));
let context = build_last_acceptance_output_context(None, None);
assert!(context.is_empty());
}
#[test]
fn test_apply_system_prompt_content() {
assert_eq!(APPLY_SYSTEM_PROMPT, "");
}
#[test]
fn test_build_archive_prompt_with_all_parts() {
let user_prompt = "Please archive this change";
let history_context = "<last_archive attempt=\"1\">\nstatus: failed\n</last_archive>";
let result = build_archive_prompt("my-change", user_prompt, history_context);
assert!(result.contains("load skills: cflx-archive"));
assert!(result.contains("Archive change id: my-change"));
assert!(result.contains("Please archive this change"));
assert!(result.contains("<last_archive attempt=\"1\">"));
assert!(result.contains("status: failed"));
}
#[test]
fn test_build_archive_prompt_with_empty_user_prompt() {
let user_prompt = "";
let history_context = "<last_archive attempt=\"1\">\nstatus: failed\n</last_archive>";
let result = build_archive_prompt("my-change", user_prompt, history_context);
assert!(result.contains("<last_archive attempt=\"1\">"));
assert!(!result.contains("\n\n\n")); }
#[test]
fn test_build_archive_prompt_with_empty_history() {
let user_prompt = "Please archive this change";
let history_context = "";
let result = build_archive_prompt("my-change", user_prompt, history_context);
assert!(result.contains("load skills: cflx-archive"));
assert!(result.contains("Archive change id: my-change"));
assert!(result.contains("Please archive this change"));
}
#[test]
fn test_build_archive_prompt_both_empty() {
let user_prompt = "";
let history_context = "";
let result = build_archive_prompt("my-change", user_prompt, history_context);
assert!(result.contains("load skills: cflx-archive"));
assert!(result.contains("Archive change id: my-change"));
}