// Integration tests for Phase 3: Workflow orchestration // Tests the complete workflow system end-to-end use std::sync::Arc; use vapora_agents::{ config::{AgentConfig, RegistryConfig}, coordinator::AgentCoordinator, registry::AgentRegistry, }; use vapora_backend::{ api::websocket::WorkflowBroadcaster, audit::AuditTrail, services::{WorkflowService, WorkflowServiceError}, workflow::{ engine::WorkflowEngine, executor::StepExecutor, parser::WorkflowParser, scheduler::Scheduler, state::{Phase, StepStatus, Workflow, WorkflowStatus, WorkflowStep}, }, }; #[tokio::test] async fn test_workflow_state_transitions() { let mut workflow = Workflow::new("wf-1".to_string(), "Test Workflow".to_string(), vec![]); // Test valid transitions assert!(workflow.transition(WorkflowStatus::Planning).is_ok()); assert_eq!(workflow.status, WorkflowStatus::Planning); assert!(workflow.transition(WorkflowStatus::InProgress).is_ok()); assert_eq!(workflow.status, WorkflowStatus::InProgress); assert!(workflow.started_at.is_some()); assert!(workflow.transition(WorkflowStatus::Completed).is_ok()); assert_eq!(workflow.status, WorkflowStatus::Completed); assert!(workflow.completed_at.is_some()); } #[tokio::test] async fn test_workflow_parser() { let yaml = r#" workflow: id: test-workflow title: Test Workflow phases: - id: phase1 name: Design Phase parallel: false estimated_hours: 2.0 steps: - id: step1 name: Create design agent: architect depends_on: [] parallelizable: false - id: phase2 name: Implementation parallel: true estimated_hours: 8.0 steps: - id: step2 name: Implement backend agent: developer depends_on: [] parallelizable: true - id: step3 name: Implement frontend agent: developer depends_on: [] parallelizable: true "#; let result = WorkflowParser::parse_string(yaml); assert!(result.is_ok()); let workflow = result.unwrap(); assert_eq!(workflow.id, "test-workflow"); assert_eq!(workflow.phases.len(), 2); assert!(workflow.phases[1].parallel); assert_eq!(workflow.phases[1].steps.len(), 2); } #[tokio::test] async fn test_dependency_resolution() { let steps = vec![ WorkflowStep { id: "a".to_string(), name: "Step A".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec![], can_parallelize: true, started_at: None, completed_at: None, result: None, error: None, }, WorkflowStep { id: "b".to_string(), name: "Step B".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec!["a".to_string()], can_parallelize: true, started_at: None, completed_at: None, result: None, error: None, }, WorkflowStep { id: "c".to_string(), name: "Step C".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec!["a".to_string()], can_parallelize: true, started_at: None, completed_at: None, result: None, error: None, }, ]; let result = Scheduler::resolve_dependencies(&steps); assert!(result.is_ok()); let levels = result.unwrap(); assert_eq!(levels.len(), 2); assert_eq!(levels[0], vec!["a"]); assert_eq!(levels[1].len(), 2); // b and c can execute in parallel } #[tokio::test] async fn test_workflow_engine() { let registry = Arc::new(AgentRegistry::new(5)); let config = AgentConfig { registry: RegistryConfig { max_agents_per_role: 5, health_check_interval: 30, agent_timeout: 300, }, agents: vec![], }; let coordinator = Arc::new( AgentCoordinator::new(config, registry) .await .expect("coordinator creation failed"), ); let executor = StepExecutor::new(coordinator); let engine = WorkflowEngine::new(executor); let workflow = Workflow::new( "engine-test".to_string(), "Engine Test".to_string(), vec![Phase { id: "p1".to_string(), name: "Phase 1".to_string(), status: StepStatus::Pending, parallel: false, estimated_hours: 1.0, steps: vec![WorkflowStep { id: "s1".to_string(), name: "Step 1".to_string(), agent_role: "developer".to_string(), status: StepStatus::Pending, depends_on: vec![], can_parallelize: true, started_at: None, completed_at: None, result: None, error: None, }], }], ); let id = workflow.id.clone(); let result = engine.register_workflow(workflow).await; assert!(result.is_ok()); let retrieved = engine.get_workflow(&id).await; assert!(retrieved.is_some()); assert_eq!(retrieved.unwrap().id, id); } #[tokio::test] async fn test_workflow_service_integration() { let registry = Arc::new(AgentRegistry::new(5)); let config = AgentConfig { registry: RegistryConfig { max_agents_per_role: 5, health_check_interval: 30, agent_timeout: 300, }, agents: vec![], }; let coordinator = Arc::new( AgentCoordinator::new(config, registry) .await .expect("coordinator creation failed"), ); let executor = StepExecutor::new(coordinator); let engine = Arc::new(WorkflowEngine::new(executor)); let broadcaster = Arc::new(WorkflowBroadcaster::new()); let audit = Arc::new(AuditTrail::new()); let service = WorkflowService::new(engine, broadcaster, audit.clone()); let _workflow = Workflow::new( "service-test".to_string(), "Service Test".to_string(), vec![Phase { id: "p1".to_string(), name: "Test Phase".to_string(), status: StepStatus::Pending, parallel: false, estimated_hours: 1.0, steps: vec![], }], ); // Need at least one step for valid workflow let workflow = Workflow::new( "service-test".to_string(), "Service Test".to_string(), vec![Phase { id: "p1".to_string(), name: "Test Phase".to_string(), status: StepStatus::Pending, parallel: false, estimated_hours: 1.0, steps: vec![WorkflowStep { id: "s1".to_string(), name: "Test Step".to_string(), agent_role: "developer".to_string(), status: StepStatus::Pending, depends_on: vec![], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }], }], ); let id = workflow.id.clone(); let result: Result = service.create_workflow(workflow).await; assert!(result.is_ok()); // Check audit trail let audit_entries: Vec<_> = service.get_audit_trail(&id).await; assert!(!audit_entries.is_empty()); } #[tokio::test] async fn test_websocket_broadcaster() { let broadcaster = WorkflowBroadcaster::new(); let mut rx = broadcaster.subscribe(); let update = vapora_backend::api::websocket::WorkflowUpdate::new( "wf-1".to_string(), "in_progress".to_string(), 50, "Test update".to_string(), ); broadcaster.send_update(update.clone()); let received = rx.recv().await.unwrap(); assert_eq!(received.workflow_id, "wf-1"); assert_eq!(received.progress, 50); } #[tokio::test] async fn test_audit_trail() { let audit = AuditTrail::new(); audit .log_event( "wf-1".to_string(), "workflow_started".to_string(), "system".to_string(), serde_json::json!({"test": "data"}), ) .await; let entries = audit.get_workflow_audit("wf-1").await; assert_eq!(entries.len(), 1); assert_eq!(entries[0].event_type, "workflow_started"); } #[tokio::test] async fn test_circular_dependency_detection() { let steps = vec![ WorkflowStep { id: "a".to_string(), name: "A".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec!["c".to_string()], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }, WorkflowStep { id: "b".to_string(), name: "B".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec!["a".to_string()], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }, WorkflowStep { id: "c".to_string(), name: "C".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec!["b".to_string()], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }, ]; let result = Scheduler::resolve_dependencies(&steps); assert!(result.is_err()); } #[tokio::test] async fn test_workflow_progress_calculation() { let workflow = Workflow::new( "progress-test".to_string(), "Progress Test".to_string(), vec![Phase { id: "p1".to_string(), name: "Phase 1".to_string(), status: StepStatus::Running, parallel: false, estimated_hours: 1.0, steps: vec![ WorkflowStep { id: "s1".to_string(), name: "Step 1".to_string(), agent_role: "dev".to_string(), status: StepStatus::Completed, depends_on: vec![], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }, WorkflowStep { id: "s2".to_string(), name: "Step 2".to_string(), agent_role: "dev".to_string(), status: StepStatus::Running, depends_on: vec![], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }, WorkflowStep { id: "s3".to_string(), name: "Step 3".to_string(), agent_role: "dev".to_string(), status: StepStatus::Pending, depends_on: vec![], can_parallelize: false, started_at: None, completed_at: None, result: None, error: None, }, ], }], ); assert_eq!(workflow.progress_percent(), 33); // 1 of 3 completed }