use anyhow::Result; use common_contracts::messages::{SyncStateCommand, TaskType, WorkflowEvent}; use common_contracts::workflow_types::TaskStatus; use serde_json::json; use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; use uuid::Uuid; use workflow_orchestrator_service::dag_scheduler::DagScheduler; use workflow_orchestrator_service::logging::LogBufferManager; use workflow_orchestrator_service::state::AppState; use workflow_orchestrator_service::workflow::WorkflowEngine; use workflow_orchestrator_service::config::AppConfig; use futures::stream::StreamExt; // Note: This test requires a running NATS server. // Set NATS_URL environment variable if needed, otherwise defaults to localhost:4222 #[tokio::test] async fn test_workflow_rehydration_flow() -> Result<()> { // 1. Setup NATS let nats_url = std::env::var("NATS_URL").unwrap_or_else(|_| "nats://localhost:4222".to_string()); let nats_client = async_nats::connect(&nats_url).await; if nats_client.is_err() { println!("Skipping test: NATS not available at {}", nats_url); return Ok(()); } let nats_client = nats_client?; // 2. Setup AppState (Mocking Dependencies) let config = AppConfig { nats_addr: nats_url.clone(), data_persistence_service_url: "http://localhost:3001".to_string(), // Mock URL workflow_data_path: "/tmp/workflow_data".to_string(), server_port: 0, }; let (log_tx, _) = tokio::sync::broadcast::channel(100); let log_manager = Arc::new(LogBufferManager::new("/tmp/workflow_logs")); let state = Arc::new(AppState::new(config, log_manager.clone(), log_tx).await?); let engine = WorkflowEngine::new(state.clone(), nats_client.clone()); // 3. Construct a Fake Workflow State (In-Memory) let req_id = Uuid::new_v4(); let task_id = "task:fake_analysis".to_string(); let mut dag = DagScheduler::new(req_id, "init_commit".to_string()); dag.add_node( task_id.clone(), Some("Fake Analysis".to_string()), TaskType::Analysis, "fake.routing".to_string(), json!({"some": "config"}) ); // Mark it as running so it captures stream dag.update_status(&task_id, TaskStatus::Running); // Insert into State state.workflows.insert(req_id, Arc::new(Mutex::new(dag))); // 4. Subscribe to Workflow Events (Simulating Frontend) let subject = common_contracts::subjects::NatsSubject::WorkflowProgress(req_id).to_string(); let mut sub = nats_client.subscribe(subject.clone()).await?; // 5. Simulate Receiving Stream Data & Logs // In real world, MessageConsumer calls these. Here we call Engine methods directly // to simulate "Consumer received NATS msg -> updated DAG". let log_msg = "[INFO] Starting deep analysis...".to_string(); let content_part1 = "Analysis Part 1...".to_string(); let content_part2 = "Analysis Part 2 [Done]".to_string(); engine.handle_task_log(task_id.clone(), log_msg.clone(), req_id).await?; engine.handle_task_stream_update(task_id.clone(), content_part1.clone(), req_id).await?; engine.handle_task_stream_update(task_id.clone(), content_part2.clone(), req_id).await?; println!("State injected. Now simulating Page Refresh (SyncState)..."); // 6. Simulate Page Refresh -> Send SyncStateCommand let sync_cmd = SyncStateCommand { request_id: req_id }; // We can call handle_sync_state directly or publish command. // Let's call directly to ensure we test the logic, but verify the OUTPUT via NATS subscription. engine.handle_sync_state(sync_cmd).await?; // 7. Verify Snapshot Received on NATS let mut snapshot_received = false; // We might receive other events, loop until snapshot or timeout let timeout = tokio::time::sleep(Duration::from_secs(2)); tokio::pin!(timeout); loop { tokio::select! { Some(msg) = sub.next() => { if let Ok(event) = serde_json::from_slice::(&msg.payload) { match event { WorkflowEvent::WorkflowStateSnapshot { task_states, .. } => { println!("Received Snapshot!"); // Verify Task State if let Some(ts) = task_states.get(&task_id) { // Check Logs assert!(ts.logs.contains(&log_msg), "Snapshot missing logs"); // Check Content let full_content = format!("{}{}", content_part1, content_part2); assert_eq!(ts.content.as_ref().unwrap(), &full_content, "Snapshot content mismatch"); println!("Snapshot verification passed!"); snapshot_received = true; break; } else { panic!("Task state not found in snapshot"); } }, _ => println!("Ignored other event: {:?}", event), } } } _ = &mut timeout => { break; } } } assert!(snapshot_received, "Did not receive WorkflowStateSnapshot within timeout"); Ok(()) }