use anyhow::Result;
use serial_test::serial;
use std::fs;
use tempfile::TempDir;
use xchecker::fixup::{FixupMode, FixupParser};
use xchecker::orchestrator::{OrchestratorConfig, PhaseOrchestrator};
use xchecker::types::PhaseId;
fn setup_test_environment(test_name: &str) -> Result<(PhaseOrchestrator, TempDir)> {
let temp_dir = TempDir::new()?;
let spec_id = format!("test-fixup-{}", test_name);
unsafe {
std::env::set_var("XCHECKER_HOME", temp_dir.path());
}
let orchestrator = PhaseOrchestrator::new(&spec_id)?;
Ok((orchestrator, temp_dir))
}
#[tokio::test]
#[serial]
async fn test_fixup_command_parsing() -> Result<()> {
let (orchestrator, _temp_dir) = setup_test_environment("command-parsing")?;
let mut config_map = std::collections::HashMap::new();
config_map.insert("apply_fixups".to_string(), "false".to_string());
let config = OrchestratorConfig {
dry_run: true,
config: config_map,
full_config: None,
selectors: None,
strict_validation: false,
redactor: Default::default(),
hooks: None,
};
let artifacts_dir = orchestrator
.artifact_manager()
.base_path()
.join("artifacts");
fs::create_dir_all(&artifacts_dir)?;
fs::write(
artifacts_dir.join("00-requirements.md"),
"# Requirements\nTest requirements",
)?;
fs::write(
artifacts_dir.join("00-requirements.core.yaml"),
"spec_id: test\nphase: requirements",
)?;
fs::write(artifacts_dir.join("10-design.md"), "# Design\nTest design")?;
fs::write(
artifacts_dir.join("10-design.core.yaml"),
"spec_id: test\nphase: design",
)?;
fs::write(artifacts_dir.join("20-tasks.md"), "# Tasks\nTest tasks")?;
fs::write(
artifacts_dir.join("20-tasks.core.yaml"),
"spec_id: test\nphase: tasks",
)?;
let review_content = r#"# Review Document
## Analysis
The requirements need some updates.
## FIXUP PLAN:
```diff
--- a/artifacts/00-requirements.md
+++ b/artifacts/00-requirements.md
@@ -1,2 +1,3 @@
# Requirements
Test requirements
+Additional requirement
```
"#;
fs::write(artifacts_dir.join("30-review.md"), review_content)?;
fs::write(
artifacts_dir.join("30-review.core.yaml"),
"spec_id: test\nphase: review",
)?;
let receipts_dir = orchestrator.artifact_manager().base_path().join("receipts");
fs::create_dir_all(&receipts_dir)?;
for phase in &["requirements", "design", "tasks", "review"] {
let receipt = format!(
r#"{{
"schema_version": "1",
"emitted_at": "2024-01-01T00:00:00Z",
"canonicalization_backend": "jcs-rfc8785",
"phase": "{}",
"exit_code": 0
}}"#,
phase
);
fs::write(
receipts_dir.join(format!("{}-20240101_000000.json", phase)),
receipt,
)?;
}
let result = orchestrator
.resume_from_phase(PhaseId::Fixup, &config)
.await;
assert!(
result.is_ok() || result.is_err(),
"Fixup command should be wired (success or expected error)"
);
println!("✓ Fixup command parsing test completed");
Ok(())
}
#[test]
fn test_apply_fixups_flag_handling() -> Result<()> {
let sandbox = TempDir::new()?;
let _parser_preview = FixupParser::new(FixupMode::Preview, sandbox.path().to_path_buf())?;
assert_eq!(
std::mem::discriminant(&FixupMode::Preview),
std::mem::discriminant(&FixupMode::Preview)
);
let _parser_apply = FixupParser::new(FixupMode::Apply, sandbox.path().to_path_buf())?;
assert_eq!(
std::mem::discriminant(&FixupMode::Apply),
std::mem::discriminant(&FixupMode::Apply)
);
println!("✓ Apply fixups flag handling test passed");
Ok(())
}
#[test]
fn test_review_output_loading() -> Result<()> {
let sandbox = TempDir::new()?;
let parser = FixupParser::new(FixupMode::Preview, sandbox.path().to_path_buf())?;
let review_content = r#"# Review Document
## FIXUP PLAN:
```diff
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-old line
+new line
```
"#;
assert!(
parser.has_fixup_markers(review_content),
"Should detect FIXUP PLAN: marker"
);
let diffs = parser.parse_diffs(review_content)?;
assert_eq!(diffs.len(), 1, "Should parse one diff block");
assert_eq!(
diffs[0].target_file, "test.txt",
"Should extract target file"
);
println!("✓ Review output loading test passed");
Ok(())
}
#[test]
fn test_fixup_plan_derivation() -> Result<()> {
let sandbox = TempDir::new()?;
let parser = FixupParser::new(FixupMode::Preview, sandbox.path().to_path_buf())?;
let review_content = r#"# Review Document
## FIXUP PLAN:
```diff
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-old
+new
```
```diff
--- a/file2.txt
+++ b/file2.txt
@@ -1 +1 @@
-old2
+new2
```
"#;
let diffs = parser.parse_diffs(review_content)?;
assert_eq!(diffs.len(), 2, "Should parse two diff blocks");
assert_eq!(diffs[0].target_file, "file1.txt");
assert_eq!(diffs[1].target_file, "file2.txt");
println!("✓ FixupPlan derivation test passed");
Ok(())
}
#[test]
fn test_plan_validation() -> Result<()> {
let temp_dir = TempDir::new()?;
let parser = FixupParser::new(FixupMode::Preview, temp_dir.path().to_path_buf())?;
let test_file = temp_dir.path().join("test.txt");
fs::write(&test_file, "original content\n")?;
let review_content = r#"# Review Document
## FIXUP PLAN:
```diff
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-original content
+modified content
```
"#
.to_string();
let diffs = parser.parse_diffs(&review_content)?;
let preview = parser.preview_changes(&diffs)?;
assert_eq!(preview.target_files.len(), 1);
assert_eq!(preview.target_files[0], "test.txt");
println!("✓ Plan validation test passed");
Ok(())
}
#[test]
fn test_preview_mode_no_modifications() -> Result<()> {
let temp_dir = TempDir::new()?;
let parser = FixupParser::new(FixupMode::Preview, temp_dir.path().to_path_buf())?;
let test_file = temp_dir.path().join("test.txt");
let original_content = "original content\n";
fs::write(&test_file, original_content)?;
let review_content = r#"# Review Document
## FIXUP PLAN:
```diff
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-original content
+modified content
```
"#;
let diffs = parser.parse_diffs(review_content)?;
let preview = parser.preview_changes(&diffs)?;
assert_eq!(preview.target_files.len(), 1);
assert!(preview.change_summary.contains_key("test.txt"));
let current_content = fs::read_to_string(&test_file)?;
assert_eq!(
current_content, original_content,
"File should not be modified in preview mode"
);
println!("✓ Preview mode no modifications test passed");
Ok(())
}
#[test]
fn test_apply_mode_modifies_files() -> Result<()> {
let temp_dir = TempDir::new()?;
let parser = FixupParser::new(FixupMode::Apply, temp_dir.path().to_path_buf())?;
let test_file = temp_dir.path().join("test.txt");
fs::write(&test_file, "original content\n")?;
let review_content = r#"# Review Document
## FIXUP PLAN:
```diff
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-original content
+modified content
```
"#;
let diffs = parser.parse_diffs(review_content)?;
let result = parser.apply_changes(&diffs)?;
assert_eq!(result.applied_files.len(), 1);
assert_eq!(result.applied_files[0].path, "test.txt");
assert!(result.applied_files[0].applied);
let current_content = fs::read_to_string(&test_file)?;
assert_eq!(
current_content, "modified content\n",
"File should be modified in apply mode"
);
let backup_file = temp_dir.path().join("test.bak");
assert!(backup_file.exists(), "Backup file should be created");
let backup_content = fs::read_to_string(&backup_file)?;
assert_eq!(
backup_content, "original content\n",
"Backup should contain original content"
);
println!("✓ Apply mode modifies files test passed");
Ok(())
}
#[test]
fn test_run_all_fixup_command_tests() -> Result<()> {
println!("Running all fixup command integration tests...");
test_apply_fixups_flag_handling()?;
test_review_output_loading()?;
test_fixup_plan_derivation()?;
test_plan_validation()?;
test_preview_mode_no_modifications()?;
test_apply_mode_modifies_files()?;
println!("✅ All fixup command integration tests passed!");
Ok(())
}