prodigy 0.4.4

Turn ad-hoc Claude sessions into reproducible development pipelines with parallel AI agents
Documentation
//! Unit tests for session coordinator

#[cfg(test)]
mod tests {
    use crate::cook::coordinators::session::{DefaultSessionCoordinator, SessionCoordinator};
    use crate::cook::session::SessionStatus;
    use crate::storage::GlobalStorage;
    use crate::unified_session::SessionManager as UnifiedSessionManager;
    use std::path::PathBuf;
    use std::sync::Arc;
    use tempfile::TempDir;

    #[tokio::test]
    async fn test_start_session() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Test - Use proper session ID format
        let result = coordinator.start_session("session-test-123").await;

        // Verify
        assert!(result.is_ok());

        // Verify session ID is stored (should match workflow_id)
        let info = coordinator.get_session_info().await.unwrap();
        assert!(info.session_id.starts_with("session-"));
        assert_eq!(info.session_id, "session-test-123");
    }

    // Removing test_start_session_failure as it requires mocking which isn't available
    // with the current architecture

    #[tokio::test]
    async fn test_update_status() {
        // Test valid status transitions using pure function approach
        // Each test case is independent to avoid state contamination
        let test_cases = vec![
            (
                "Running to Paused",
                SessionStatus::InProgress,
                SessionStatus::Interrupted,
            ),
            (
                "Running to Completed",
                SessionStatus::InProgress,
                SessionStatus::Completed,
            ),
            (
                "Running to Failed",
                SessionStatus::InProgress,
                SessionStatus::Failed,
            ),
        ];

        for (description, initial_status, target_status) in test_cases {
            // Setup fresh coordinator for each test case
            let temp_dir = TempDir::new().unwrap();
            let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
            let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
            let working_dir = PathBuf::from("/test");
            let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

            // Start session
            coordinator
                .start_session(&format!("session-test-{}", description.replace(" ", "-")))
                .await
                .unwrap();

            // Set initial status
            coordinator.update_status(initial_status).await.unwrap();

            // Test transition to target status
            let result = coordinator.update_status(target_status.clone()).await;
            assert!(
                result.is_ok(),
                "Failed to transition {}: {:?}",
                description,
                result.err()
            );
        }
    }

    #[tokio::test]
    async fn test_track_iteration() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Start session first
        coordinator.start_session("session-test-456").await.unwrap();

        // Test tracking multiple iterations
        for i in 1..=5 {
            let result = coordinator.track_iteration(i).await;
            assert!(result.is_ok());
        }

        // Just verify the operations succeeded
        // We can't easily verify internal state without mocking
    }

    #[tokio::test]
    async fn test_complete_session_success() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Start session and track some work
        coordinator.start_session("test-session").await.unwrap();
        coordinator.track_iteration(1).await.unwrap();
        coordinator.track_iteration(2).await.unwrap();

        // Test successful completion
        let result = coordinator.complete_session(true).await;
        assert!(result.is_ok());

        // Verify the session was completed
        let info = coordinator.get_session_info().await.unwrap();
        assert_eq!(info.status, SessionStatus::Completed);
    }

    #[tokio::test]
    async fn test_complete_session_failure() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Start session
        coordinator.start_session("test-session").await.unwrap();

        // Test failed completion
        let result = coordinator.complete_session(false).await;
        assert!(result.is_ok());

        // Verify the session was marked as failed
        let info = coordinator.get_session_info().await.unwrap();
        assert_eq!(info.status, SessionStatus::Failed);
    }

    #[tokio::test]
    async fn test_get_session_info() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Test before starting session
        let info = coordinator.get_session_info().await.unwrap();
        assert_eq!(info.session_id, "unknown");
        assert_eq!(info.status, SessionStatus::InProgress);

        // Start session and test again
        coordinator.start_session("session-my-test").await.unwrap();
        coordinator
            .update_status(SessionStatus::InProgress)
            .await
            .unwrap();

        let info = coordinator.get_session_info().await.unwrap();
        assert!(info.session_id.starts_with("session-"));
        assert_eq!(info.status, SessionStatus::InProgress);
    }

    #[tokio::test]
    async fn test_resume_session() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Start session and track iterations
        coordinator
            .start_session("session-resumable-test")
            .await
            .unwrap();
        coordinator.track_iteration(1).await.unwrap();
        coordinator.track_iteration(2).await.unwrap();
        coordinator.track_iteration(3).await.unwrap();

        // Get the actual session ID
        let info = coordinator.get_session_info().await.unwrap();
        let session_id = info.session_id;

        // Test resuming
        let result = coordinator.resume_session(&session_id).await.unwrap();
        assert!(result.is_some());
        assert_eq!(result.unwrap(), 3); // Should return iteration count

        // Complete session and test resuming again
        coordinator.complete_session(true).await.unwrap();
        let result = coordinator.resume_session(&session_id).await.unwrap();
        assert!(result.is_none()); // Cannot resume completed session
    }

    #[tokio::test]
    async fn test_session_lifecycle() {
        // Setup
        let temp_dir = TempDir::new().unwrap();
        let storage = GlobalStorage::new_with_root(temp_dir.path().to_path_buf()).unwrap();
        let session_manager = Arc::new(UnifiedSessionManager::new(storage).await.unwrap());
        let working_dir = PathBuf::from("/test");
        let coordinator = DefaultSessionCoordinator::new(session_manager.clone(), working_dir);

        // Full lifecycle test
        // 1. Start
        coordinator
            .start_session("session-lifecycle-test")
            .await
            .unwrap();

        // 2. Update status to in progress
        coordinator
            .update_status(SessionStatus::InProgress)
            .await
            .unwrap();

        // 3. Track multiple iterations
        for i in 1..=10 {
            coordinator.track_iteration(i).await.unwrap();
        }

        // 4. Complete successfully
        coordinator.complete_session(true).await.unwrap();

        // Verify final state
        let info = coordinator.get_session_info().await.unwrap();
        assert!(info.session_id.starts_with("session-"));
        assert_eq!(info.status, SessionStatus::Completed);
    }

    // Removing test_error_propagation as it requires mocking which isn't available
    // with the current architecture
}