use serial_test::serial;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
use test_tag::tag;
fn should_run_claude_tests() -> bool {
if let Ok(value) = std::env::var("RUN_CLAUDE_TESTS")
&& (value == "1" || value.eq_ignore_ascii_case("true"))
{
return true;
}
std::env::var("ANTHROPIC_API_KEY").is_ok()
}
struct DirectoryGuard {
original_dir: PathBuf,
}
impl DirectoryGuard {
fn new(workspace: &PathBuf) -> Result<Self, std::io::Error> {
let original_dir = std::env::current_dir()?;
std::env::set_current_dir(workspace)?;
Ok(Self { original_dir })
}
}
impl Drop for DirectoryGuard {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original_dir);
}
}
#[derive(Debug)]
#[allow(dead_code)]
struct TestCase {
name: &'static str,
resource_dir: &'static str,
task_file: &'static str,
expected_outputs: Vec<&'static str>,
}
fn get_test_cases() -> Vec<TestCase> {
vec![
TestCase {
name: "simple_file_creation",
resource_dir: "test1-simple-file",
task_file: "task.md",
expected_outputs: vec!["hello.txt"],
},
TestCase {
name: "readme_creation",
resource_dir: "test2-readme-creation",
task_file: "task.md",
expected_outputs: vec!["README.md"],
},
TestCase {
name: "file_editing",
resource_dir: "test3-file-editing",
task_file: "task.md",
expected_outputs: vec!["existing_file.txt"],
},
TestCase {
name: "multi_task_execution",
resource_dir: "test4-multi-task",
task_file: "tasks.md",
expected_outputs: vec![
"hello.py",
"config.json",
"run.sh",
".gitignore",
"requirements.txt",
],
},
TestCase {
name: "task_references",
resource_dir: "test5-task-references",
task_file: "single_task.md",
expected_outputs: vec![], },
]
}
fn setup_test_workspace(
resource_dir: &str,
) -> Result<(TempDir, PathBuf), Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
let workspace_path = temp_dir.path().to_path_buf();
let resource_path = PathBuf::from("tests/resources").join(resource_dir);
if resource_path.exists() {
copy_dir_all(&resource_path, &workspace_path)?;
}
Ok((temp_dir, workspace_path))
}
fn copy_dir_all(src: &PathBuf, dst: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
if !dst.exists() {
fs::create_dir_all(dst)?;
}
for entry in fs::read_dir(src)? {
let entry = entry?;
let path = entry.path();
let dest_path = dst.join(entry.file_name());
if path.is_dir() {
copy_dir_all(&path, &dest_path)?;
} else {
fs::copy(&path, &dest_path)?;
}
}
Ok(())
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_claude_integration_with_temp_workspaces() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
for test_case in get_test_cases() {
println!("Running test case: {}", test_case.name);
let (_temp_dir, _workspace_path) = setup_test_workspace(test_case.resource_dir)
.unwrap_or_else(|_| panic!("Failed to setup workspace for {}", test_case.name));
let default_config = aca::cli::ConfigDiscovery::discover_config().unwrap_or_else(|_| {
eprintln!("Warning: Failed to discover config, using basic defaults");
aca::cli::DefaultAgentConfig::default()
});
let agent_config = default_config.to_agent_config(Some(_workspace_path.clone()));
let _dir_guard =
DirectoryGuard::new(&_workspace_path).expect("Failed to change to workspace directory");
let agent = aca::AgentSystem::new(agent_config)
.await
.unwrap_or_else(|_| panic!("Failed to create agent for {}", test_case.name));
let task_file_path = _workspace_path.join(test_case.task_file);
if !task_file_path.exists() {
eprintln!(
"Task file not found for {}: {:?}",
test_case.name, task_file_path
);
continue;
}
let task_content = match fs::read_to_string(&task_file_path) {
Ok(content) => content,
Err(e) => {
eprintln!("Failed to read task file for {}: {:?}", test_case.name, e);
continue;
}
};
let result = agent
.create_and_process_task(test_case.name, &task_content)
.await;
match result {
Ok(task_id) => {
println!(
"✅ {} completed successfully (Task ID: {})",
test_case.name, task_id
);
for expected_file in &test_case.expected_outputs {
let file_path = _workspace_path.join(expected_file);
if file_path.exists() {
println!(" ✅ Created: {}", expected_file);
} else {
println!(" ⚠️ Missing expected file: {}", expected_file);
}
}
println!(" Files in workspace:");
if let Ok(entries) = fs::read_dir(&_workspace_path) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
println!(
" - {}",
path.file_name()
.expect("Invalid file name")
.to_string_lossy()
);
}
}
}
}
Err(e) => {
eprintln!("❌ {} failed: {:?}", test_case.name, e);
}
}
}
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_single_task_with_references() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
let test_cases = get_test_cases();
let test_case = &test_cases[4];
println!("Running detailed test: {}", test_case.name);
let (_temp_dir, workspace_path) =
setup_test_workspace(test_case.resource_dir).expect("Failed to setup workspace");
println!("Workspace: {:?}", workspace_path);
println!("Available files:");
if let Ok(entries) = fs::read_dir(&workspace_path) {
for entry in entries.flatten() {
println!(" - {}", entry.file_name().to_string_lossy());
}
}
let default_config = aca::cli::ConfigDiscovery::discover_config().unwrap_or_else(|_| {
eprintln!("Warning: Failed to discover config, using basic defaults");
aca::cli::DefaultAgentConfig::default()
});
let agent_config = default_config.to_agent_config(Some(workspace_path.clone()));
let _dir_guard =
DirectoryGuard::new(&workspace_path).expect("Failed to change to workspace directory");
let agent = aca::AgentSystem::new(agent_config)
.await
.expect("Failed to create agent");
let task_file_path = workspace_path.join(test_case.task_file);
println!("Task file: {:?}", task_file_path);
let task_content = if let Ok(content) = fs::read_to_string(&task_file_path) {
println!("Task content:\n{}", content);
content
} else {
eprintln!("Failed to read task file: {:?}", task_file_path);
return;
};
let result = agent
.create_and_process_task(test_case.name, &task_content)
.await;
match result {
Ok(task_id) => {
println!("✅ Task completed (Task ID: {})", task_id);
println!("Files after execution:");
if let Ok(entries) = fs::read_dir(&workspace_path) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
let name = path.file_name().unwrap().to_string_lossy();
println!(" - {}", name);
if name.ends_with(".py")
&& let Ok(content) = fs::read_to_string(&path)
{
println!(" Content:\n{}", content);
}
}
}
}
}
Err(e) => {
eprintln!("❌ Task failed: {:?}", e);
}
}
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_multi_task_execution() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
let test_cases = get_test_cases();
let test_case = &test_cases[3];
println!("Running multi-task test: {}", test_case.name);
let (_temp_dir, _workspace_path) =
setup_test_workspace(test_case.resource_dir).expect("Failed to setup workspace");
let default_config = aca::cli::ConfigDiscovery::discover_config().unwrap_or_else(|_| {
eprintln!("Warning: Failed to discover config, using basic defaults");
aca::cli::DefaultAgentConfig::default()
});
let agent_config = default_config.to_agent_config(Some(_workspace_path.clone()));
let _dir_guard =
DirectoryGuard::new(&_workspace_path).expect("Failed to change to workspace directory");
let agent = aca::AgentSystem::new(agent_config)
.await
.expect("Failed to create agent");
let task_file_path = _workspace_path.join("tasks.md");
let task_content = match fs::read_to_string(&task_file_path) {
Ok(content) => content,
Err(e) => {
eprintln!("Failed to read multi-task file: {:?}", e);
return;
}
};
let result = agent
.create_and_process_task("Multi-Task Execution", &task_content)
.await;
match result {
Ok(task_id) => {
println!("✅ Multi-task execution completed (Task ID: {})", task_id);
for expected_file in &test_case.expected_outputs {
let file_path = _workspace_path.join(expected_file);
if file_path.exists() {
println!(" ✅ Created: {}", expected_file);
} else {
println!(" ⚠️ Missing: {}", expected_file);
}
}
println!(" Files in workspace:");
if let Ok(entries) = fs::read_dir(&_workspace_path) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
println!(" - {}", path.file_name().unwrap().to_string_lossy());
}
}
}
}
Err(e) => {
eprintln!("❌ Multi-task execution failed: {:?}", e);
}
}
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_cli_resume_functionality() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
use aca::cli::{args::ExecutionMode, tasks::TaskLoader};
use tempfile::TempDir;
println!("Testing CLI resume functionality");
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let workspace_path = temp_dir.path().to_path_buf();
let task_file = workspace_path.join("test_task.md");
fs::write(
&task_file,
"Create a simple test file named 'resume_test.txt' with content 'Resume test successful'",
)
.expect("Failed to write test task");
println!("✅ Testing ExecutionMode enum variants exist");
let test_mode = ExecutionMode::ListCheckpoints {
all_sessions: false,
};
match test_mode {
ExecutionMode::ListCheckpoints { all_sessions: _ } => {
println!(" ✅ ListCheckpoints variant exists")
}
_ => panic!("ListCheckpoints variant missing"),
}
match ExecutionMode::CreateCheckpoint("test".to_string()) {
ExecutionMode::CreateCheckpoint(_) => println!(" ✅ CreateCheckpoint variant exists"),
_ => panic!("CreateCheckpoint variant missing"),
}
match ExecutionMode::Resume(aca::cli::args::ResumeConfig {
checkpoint_id: Some("test".to_string()),
workspace_override: None,
verbose: false,
continue_latest: false,
}) {
ExecutionMode::Resume(_) => println!(" ✅ Resume variant exists"),
_ => panic!("Resume variant missing"),
}
let resume_config = aca::cli::args::ResumeConfig {
checkpoint_id: Some("test-checkpoint-123".to_string()),
workspace_override: Some(workspace_path.clone()),
verbose: true,
continue_latest: false,
};
println!(
" ✅ ResumeConfig created: checkpoint_id={:?}, workspace={:?}",
resume_config.checkpoint_id, resume_config.workspace_override
);
let task = TaskLoader::parse_single_file_task(&task_file).expect("Failed to parse task file");
assert!(
!task.description.is_empty(),
"Task description should not be empty"
);
assert!(
task.description.contains("resume_test.txt"),
"Task should contain expected filename"
);
println!(" ✅ Task loaded successfully: {}", task.description);
assert!(workspace_path.exists(), "Workspace should exist");
assert!(task_file.exists(), "Task file should exist");
println!(" ✅ Workspace setup verified: {:?}", workspace_path);
println!("⚠️ Full session manager integration tests pending AgentSystem finalization");
println!("✅ CLI resume functionality structure tests completed");
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_checkpoint_operations() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
use aca::cli::args::{ExecutionMode, ResumeConfig};
println!("Testing checkpoint operations");
let resume_config = ResumeConfig {
checkpoint_id: Some("manual-checkpoint-2024-09-22".to_string()),
workspace_override: None,
verbose: true,
continue_latest: false,
};
match ExecutionMode::Resume(resume_config) {
ExecutionMode::Resume(config) => {
assert!(
config.checkpoint_id.is_some(),
"Checkpoint ID should be set"
);
assert!(config.verbose, "Verbose flag should be set");
assert!(!config.continue_latest, "Continue latest should be false");
println!(" ✅ Resume config validation passed");
}
_ => panic!("Expected Resume execution mode"),
}
let continue_config = ResumeConfig {
checkpoint_id: None,
workspace_override: None,
verbose: false,
continue_latest: true,
};
match ExecutionMode::Resume(continue_config) {
ExecutionMode::Resume(config) => {
assert!(
config.checkpoint_id.is_none(),
"Checkpoint ID should be None for continue latest"
);
assert!(config.continue_latest, "Continue latest flag should be set");
println!(" ✅ Continue latest config validation passed");
}
_ => panic!("Expected Resume execution mode"),
}
let checkpoint_desc = "Manual checkpoint for testing Issue #09 implementation".to_string();
match ExecutionMode::CreateCheckpoint(checkpoint_desc.clone()) {
ExecutionMode::CreateCheckpoint(desc) => {
assert_eq!(desc, checkpoint_desc, "Checkpoint description should match");
println!(" ✅ Manual checkpoint creation config validated");
}
_ => panic!("Expected CreateCheckpoint execution mode"),
}
println!("✅ Checkpoint operations tests completed");
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_task_continuation_on_resume() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
use aca::cli::args::{ExecutionMode, ResumeConfig};
println!("Testing task continuation during resume");
let resume_config = ResumeConfig {
checkpoint_id: Some("test-checkpoint".to_string()),
workspace_override: None,
verbose: true,
continue_latest: false,
};
match ExecutionMode::Resume(resume_config) {
ExecutionMode::Resume(config) => {
assert!(
config.checkpoint_id.is_some(),
"Checkpoint ID should be set for resume"
);
assert!(config.verbose, "Verbose mode should be enabled for testing");
println!(" ✅ Resume configuration validated");
}
_ => panic!("Expected Resume execution mode"),
}
println!(" ✅ Task continuation logic structure validated");
println!("✅ Task continuation functionality tests completed");
}
#[tokio::test]
#[tag(claude)]
#[serial]
async fn test_conversational_state_persistence() {
if !should_run_claude_tests() {
eprintln!("skipping Claude integration test: RUN_CLAUDE_TESTS not enabled");
return;
}
use std::fs;
use tempfile::TempDir;
println!("Testing conversational state persistence with multi-task execution");
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let workspace_path = temp_dir.path().to_path_buf();
println!(" Workspace: {:?}", workspace_path);
let default_config = aca::cli::ConfigDiscovery::discover_config().unwrap_or_else(|_| {
println!(" Warning: Failed to discover config, using basic defaults");
aca::cli::DefaultAgentConfig::default()
});
let agent_config = default_config.to_agent_config(Some(workspace_path.clone()));
let _dir_guard =
DirectoryGuard::new(&workspace_path).expect("Failed to change to workspace directory");
let agent = aca::AgentSystem::new(agent_config)
.await
.expect("Failed to create agent system");
println!(" ✅ Agent system initialized with conversation persistence");
let task1_description = r#"
Create a Python class called `Calculator` in a file named `calculator.py`. The class should have:
- An `__init__` method that initializes a `value` attribute to 0
- A `add(self, number)` method that adds to the current value
- A `subtract(self, number)` method that subtracts from the current value
- A `get_value(self)` method that returns the current value
- A `reset(self)` method that sets value back to 0
"#;
println!(" 🔄 Executing Task 1: Create Calculator class");
let task1_id = agent
.create_and_process_task("Create Calculator Class", task1_description)
.await
.expect("Task 1 failed");
println!(" ✅ Task 1 completed (ID: {})", task1_id);
let calculator_path = workspace_path.join("calculator.py");
assert!(
calculator_path.exists(),
"calculator.py should exist after Task 1"
);
let calculator_content =
fs::read_to_string(&calculator_path).expect("Failed to read calculator.py");
assert!(
calculator_content.contains("class Calculator"),
"Should contain Calculator class definition"
);
assert!(
calculator_content.contains("def add"),
"Should contain add method"
);
assert!(
calculator_content.contains("def subtract"),
"Should contain subtract method"
);
println!(" ✅ Task 1 output validated: Calculator class created correctly");
let task2_description = r#"
Extend the Calculator class you just created with these additional methods:
- `multiply(self, number)` method that multiplies the current value
- `divide(self, number)` method that divides the current value (handle division by zero)
- `get_history(self)` method that returns a list of all operations performed
You should modify the existing calculator.py file. Make sure to keep track of operations in the history.
"#;
println!(" 🔄 Executing Task 2: Extend Calculator with history tracking");
let task2_id = agent
.create_and_process_task("Extend Calculator with History", task2_description)
.await
.expect("Task 2 failed");
println!(" ✅ Task 2 completed (ID: {})", task2_id);
let calculator_content_v2 =
fs::read_to_string(&calculator_path).expect("Failed to read updated calculator.py");
assert!(
calculator_content_v2.contains("def multiply"),
"Should contain multiply method"
);
assert!(
calculator_content_v2.contains("def divide"),
"Should contain divide method"
);
assert!(
calculator_content_v2.contains("def get_history"),
"Should contain get_history method"
);
assert!(
calculator_content_v2.contains("class Calculator"),
"Should still contain original Calculator class"
);
let line_count_v2 = calculator_content_v2.lines().count();
let line_count_v1 = calculator_content.lines().count();
assert!(
line_count_v2 > line_count_v1,
"Task 2 should have extended the file, not replaced it"
);
println!(
" ✅ Task 2 output validated: Calculator extended correctly, preserving original implementation"
);
let task3_description = r#"
Create comprehensive tests for the Calculator class in a file called `test_calculator.py`.
The tests should cover all the methods you implemented, including:
- Testing the basic arithmetic operations (add, subtract, multiply, divide)
- Testing the reset functionality
- Testing the history tracking feature
- Testing edge cases like division by zero
- Testing that the value starts at 0
Use Python's unittest framework. Make sure to import the Calculator class from the calculator module.
"#;
println!(" 🔄 Executing Task 3: Create comprehensive tests");
let task3_id = agent
.create_and_process_task("Create Calculator Tests", task3_description)
.await
.expect("Task 3 failed");
println!(" ✅ Task 3 completed (ID: {})", task3_id);
let test_path = workspace_path.join("test_calculator.py");
assert!(
test_path.exists(),
"test_calculator.py should exist after Task 3"
);
let test_content = fs::read_to_string(&test_path).expect("Failed to read test_calculator.py");
assert!(
test_content.contains("Calculator"),
"Tests should import/use Calculator class"
);
assert!(
test_content.contains("add") || test_content.contains("test_add"),
"Should test add method"
);
assert!(
test_content.contains("subtract") || test_content.contains("test_subtract"),
"Should test subtract method"
);
assert!(
test_content.contains("multiply") || test_content.contains("test_multiply"),
"Should test multiply method"
);
assert!(
test_content.contains("divide") || test_content.contains("test_divide"),
"Should test divide method"
);
assert!(
test_content.contains("history") || test_content.contains("test_history"),
"Should test history functionality"
);
assert!(
test_content.contains("unittest"),
"Should use unittest framework"
);
println!(" ✅ Task 3 output validated: Tests cover all implemented functionality");
assert!(
test_content.contains("from calculator import Calculator")
|| test_content.contains("import calculator")
|| test_content.to_lowercase().contains("calculator"),
"Tests should properly import the Calculator class, demonstrating conversation memory"
);
println!(" 📊 Conversation persistence validation:");
println!(" - Task 1: Created Calculator class with basic methods");
println!(" - Task 2: Extended class without losing original implementation");
println!(" - Task 3: Created tests covering ALL implemented methods");
println!(" - All tasks built upon previous context successfully");
assert!(
calculator_content_v2.len() > calculator_content.len(),
"Conversation memory failure: Task 2 should have extended Task 1, not replaced it"
);
assert!(
test_content.contains("multiply") || test_content.contains("divide"),
"Conversation memory failure: Task 3 should reference methods from Task 2"
);
println!("✅ Conversational state persistence test completed successfully");
println!(" All tasks demonstrated proper context continuity and conversation memory");
}