use crate::subprocess::SubprocessManager;
use crate::worktree::{WorktreeManager, WorktreeState, WorktreeStatus};
use anyhow::Result;
use std::fs;
use tempfile::TempDir;
fn setup_test_repo() -> Result<TempDir> {
let temp_dir = TempDir::new()?;
std::process::Command::new("git")
.current_dir(temp_dir.path())
.args(["init"])
.output()?;
std::process::Command::new("git")
.current_dir(temp_dir.path())
.args(["config", "user.email", "test@example.com"])
.output()?;
std::process::Command::new("git")
.current_dir(temp_dir.path())
.args(["config", "user.name", "Test User"])
.output()?;
std::fs::write(temp_dir.path().join("README.md"), "# Test")?;
std::process::Command::new("git")
.current_dir(temp_dir.path())
.args(["add", "."])
.output()?;
std::process::Command::new("git")
.current_dir(temp_dir.path())
.args(["commit", "-m", "Initial commit"])
.output()?;
Ok(temp_dir)
}
#[tokio::test]
async fn test_list_sessions_includes_all_prodigy_branches() -> Result<()> {
let temp_dir = setup_test_repo()?;
let subprocess = SubprocessManager::production();
let manager = WorktreeManager::new(temp_dir.path().to_path_buf(), subprocess)?;
let session1 = manager.create_session().await?;
let merge_branch = "merge-prodigy-agent-test";
let worktree_dir = manager.base_dir.join("mapreduce-session");
std::process::Command::new("git")
.current_dir(&manager.repo_path)
.args([
"worktree",
"add",
"-b",
merge_branch,
&worktree_dir.to_string_lossy(),
])
.output()?;
let sessions = manager.list_sessions().await?;
assert!(sessions.len() >= 2, "Should find at least 2 sessions");
let has_regular = sessions.iter().any(|s| s.branch.starts_with("prodigy-"));
let has_merge = sessions
.iter()
.any(|s| s.branch.starts_with("merge-prodigy-"));
assert!(has_regular, "Should find regular MMM session");
assert!(has_merge, "Should find MapReduce merge session");
manager.cleanup_session(&session1.name, true).await?;
std::process::Command::new("git")
.current_dir(&manager.repo_path)
.args([
"worktree",
"remove",
"--force",
&worktree_dir.to_string_lossy(),
])
.output()?;
Ok(())
}
#[tokio::test]
async fn test_list_sessions_with_metadata_fallback() -> Result<()> {
let temp_dir = setup_test_repo()?;
let subprocess = SubprocessManager::production();
let manager = WorktreeManager::new(temp_dir.path().to_path_buf(), subprocess)?;
let metadata_dir = manager.base_dir.join(".metadata");
fs::create_dir_all(&metadata_dir)?;
let orphaned_state = WorktreeState {
session_id: "orphaned-session".to_string(),
worktree_name: "orphaned-session".to_string(),
branch: "prodigy-orphaned-branch".to_string(),
original_branch: String::new(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
status: WorktreeStatus::InProgress,
iterations: crate::worktree::IterationInfo {
completed: 2,
max: 10,
},
stats: crate::worktree::WorktreeStats::default(),
merged: false,
merged_at: None,
error: None,
merge_prompt_shown: false,
merge_prompt_response: None,
interrupted_at: None,
interruption_type: None,
last_checkpoint: None,
resumable: false,
};
let state_path = metadata_dir.join("session-orphaned-session.json");
fs::write(&state_path, serde_json::to_string_pretty(&orphaned_state)?)?;
let orphaned_dir = manager.base_dir.join("orphaned-session");
fs::create_dir_all(&orphaned_dir)?;
fs::write(orphaned_dir.join(".git"), "gitdir: /fake/path")?;
let normal_session = manager.create_session().await?;
let sessions = manager.list_sessions().await?;
assert!(
sessions.len() >= 2,
"Should find at least 2 sessions, but found {}",
sessions.len()
);
let has_normal = sessions.iter().any(|s| s.name == normal_session.name);
let has_orphaned = sessions.iter().any(|s| s.name == "orphaned-session");
assert!(has_normal, "Should find normal session");
assert!(has_orphaned, "Should find orphaned session from metadata");
manager.cleanup_session(&normal_session.name, true).await?;
fs::remove_dir_all(&orphaned_dir)?;
Ok(())
}
#[tokio::test]
async fn test_metadata_sessions_exclude_cleaned_up() -> Result<()> {
let temp_dir = setup_test_repo()?;
let subprocess = SubprocessManager::production();
let manager = WorktreeManager::new(temp_dir.path().to_path_buf(), subprocess)?;
let metadata_dir = manager.base_dir.join(".metadata");
fs::create_dir_all(&metadata_dir)?;
let cleaned_state = WorktreeState {
session_id: "cleaned-session".to_string(),
worktree_name: "cleaned-session".to_string(),
branch: "prodigy-cleaned-branch".to_string(),
original_branch: String::new(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
status: WorktreeStatus::CleanedUp, iterations: crate::worktree::IterationInfo {
completed: 5,
max: 5,
},
stats: crate::worktree::WorktreeStats::default(),
merged: true,
merged_at: Some(chrono::Utc::now()),
error: None,
merge_prompt_shown: false,
merge_prompt_response: None,
interrupted_at: None,
interruption_type: None,
last_checkpoint: None,
resumable: false,
};
let state_path = metadata_dir.join("session-cleaned-session.json");
fs::write(&state_path, serde_json::to_string_pretty(&cleaned_state)?)?;
let cleaned_dir = manager.base_dir.join("cleaned-session");
fs::create_dir_all(&cleaned_dir)?;
let sessions = manager.list_sessions().await?;
let has_cleaned = sessions.iter().any(|s| s.name == "cleaned-session");
assert!(!has_cleaned, "Should NOT find cleaned up session");
fs::remove_dir_all(&cleaned_dir)?;
Ok(())
}
#[tokio::test]
async fn test_mapreduce_branch_patterns() -> Result<()> {
let patterns = vec![
"prodigy-session-abc123", "merge-prodigy-agent-cook-123", "prodigy-agent-cook-123-item_0", "temp-master", ];
for pattern in patterns {
assert!(
pattern.contains("prodigy") || pattern == "temp-master",
"Branch {} should be recognized",
pattern
);
}
Ok(())
}
#[cfg(test)]
mod metadata_tests {
#[test]
fn test_metadata_file_filtering() {
let valid_files = vec![
"session-abc123.json",
"session-061f22c3-172c-41a2-99d2-53efeeeba0da.json",
];
let invalid_files = vec![
"cleanup.log",
"README.md",
"config.json",
"not-a-session.json",
];
for file in valid_files {
assert!(file.starts_with("session-") && file.ends_with(".json"));
}
for file in invalid_files {
assert!(!file.starts_with("session-") || !file.ends_with(".json"));
}
}
}