use crate::cook::execution::setup_executor::SetupPhaseExecutor;
use crate::cook::execution::SetupPhase;
use crate::cook::orchestrator::ExecutionEnvironment;
use crate::cook::workflow::{StepResult, WorkflowContext, WorkflowStep};
use anyhow::Result;
use async_trait::async_trait;
use std::path::PathBuf;
use std::sync::Arc;
struct WorkingDirCapturingExecutor {
captured_working_dir: Option<PathBuf>,
}
impl WorkingDirCapturingExecutor {
fn new() -> Self {
Self {
captured_working_dir: None,
}
}
fn get_captured_dir(&self) -> Option<&PathBuf> {
self.captured_working_dir.as_ref()
}
}
#[async_trait]
impl crate::cook::workflow::StepExecutor for WorkingDirCapturingExecutor {
async fn execute_step(
&mut self,
_step: &WorkflowStep,
env: &ExecutionEnvironment,
_context: &mut WorkflowContext,
) -> Result<StepResult> {
self.captured_working_dir = Some(env.working_dir.as_ref().clone());
Ok(StepResult {
success: true,
exit_code: Some(0),
stdout: "test output".to_string(),
stderr: String::new(),
json_log_location: None,
})
}
}
#[tokio::test]
async fn test_setup_phase_uses_worktree_directory() {
let main_repo = PathBuf::from("/Users/test/project");
let worktree_path = PathBuf::from("/Users/test/.prodigy/worktrees/session-123/worktree");
let setup_phase = SetupPhase {
commands: vec![WorkflowStep {
shell: Some("echo 'test'".to_string()),
..Default::default()
}],
timeout: None,
capture_outputs: Default::default(),
};
let mut setup_executor = SetupPhaseExecutor::new(&setup_phase);
let mut mock_executor = WorkingDirCapturingExecutor::new();
let env = ExecutionEnvironment {
working_dir: Arc::new(worktree_path.clone()),
project_dir: Arc::new(main_repo.clone()),
session_id: Arc::from("test-session"),
worktree_name: Some(Arc::from("test-worktree")),
};
let mut context = WorkflowContext::default();
let result = setup_executor
.execute(
&setup_phase.commands,
&mut mock_executor,
&env,
&mut context,
)
.await;
assert!(result.is_ok(), "Setup phase should succeed");
let captured_dir = mock_executor
.get_captured_dir()
.expect("Working directory should be captured");
assert_eq!(
captured_dir,
&worktree_path,
"Setup phase should execute in the worktree directory, not the main repo.\n\
Expected: {}\n\
Got: {}",
worktree_path.display(),
captured_dir.display()
);
}
#[tokio::test]
async fn test_setup_phase_preserves_environment_working_dir() {
let worktree_path = PathBuf::from("/tmp/test-worktree");
let main_repo = PathBuf::from("/tmp/main-repo");
let setup_phase = SetupPhase {
commands: vec![WorkflowStep {
shell: Some("pwd".to_string()),
..Default::default()
}],
timeout: None,
capture_outputs: Default::default(),
};
let mut setup_executor = SetupPhaseExecutor::new(&setup_phase);
let mut mock_executor = WorkingDirCapturingExecutor::new();
let env = ExecutionEnvironment {
working_dir: Arc::new(worktree_path.clone()),
project_dir: Arc::new(main_repo),
session_id: Arc::from("test"),
worktree_name: Some(Arc::from("test-worktree")),
};
let mut context = WorkflowContext::default();
setup_executor
.execute(
&setup_phase.commands,
&mut mock_executor,
&env,
&mut context,
)
.await
.unwrap();
let captured = mock_executor.get_captured_dir().unwrap();
assert_eq!(
captured, &worktree_path,
"Setup phase must not override the ExecutionEnvironment's working_dir.\n\
The worktree isolation guarantee depends on this."
);
}
#[allow(dead_code)]
const BUG_DOCUMENTATION: () = ();