//! Integration test for timeout handling (T049) //! //! This test verifies that the system handles timeouts gracefully when a client //! fails to respond to an agent request within the timeout period. //! //! Test scenario: //! 1. Register a pending agent request //! 2. Wait for timeout (using reduced timeout for testing) //! 3. Verify timeout occurs //! 4. Verify cleanup happens correctly //! 5. Verify no resource leaks use dirigent_acp_api::agent_requests::AgentRequestTracker; use serde_json::json; use tokio::time::{timeout, Duration}; #[tokio::test] async fn test_timeout_basic() { // Create tracker let tracker = AgentRequestTracker::new(); let client_id = "test-client"; let request_id = json!(0); // Register request let receiver = tracker.register(client_id, request_id.clone()); assert_eq!(tracker.pending_count(), 1); // Wait for timeout (use short timeout for testing) let result = timeout(Duration::from_millis(100), receiver).await; // Should timeout assert!(result.is_err(), "Expected timeout but request completed"); // Manually trigger cleanup (in production, event bridge does this) tracker.timeout(client_id, request_id); // Verify cleanup assert_eq!(tracker.pending_count(), 0); } #[tokio::test] async fn test_timeout_cleanup() { let tracker = AgentRequestTracker::new(); let client_id = "test-client"; let request_id = json!(123); // Register request let receiver = tracker.register(client_id, request_id.clone()); assert_eq!(tracker.pending_count(), 1); // Trigger timeout before waiting tracker.timeout(client_id, request_id); // Verify cleanup happened assert_eq!(tracker.pending_count(), 0); // Receiver should get error (channel closed) let result = receiver.await; assert!(result.is_err(), "Expected receiver to get error after timeout"); } #[tokio::test] async fn test_timeout_multiple_clients() { let tracker = AgentRequestTracker::new(); let client1 = "client-1"; let client2 = "client-2"; // Register requests for both clients let rx1 = tracker.register(client1, json!(0)); let rx2 = tracker.register(client2, json!(0)); assert_eq!(tracker.pending_count(), 2); assert_eq!(tracker.client_pending_count(client1), 1); assert_eq!(tracker.client_pending_count(client2), 1); // Timeout only client1's request tracker.timeout(client1, json!(0)); // Verify only client1's request is removed assert_eq!(tracker.pending_count(), 1); assert_eq!(tracker.client_pending_count(client1), 0); assert_eq!(tracker.client_pending_count(client2), 1); // Client1's receiver should error assert!(rx1.await.is_err()); // Complete client2's request normally let response = json!({"result": "success"}); let result = tracker.complete(client2, json!(0), response); assert!(result.is_ok()); // Client2's receiver should get response let received = rx2.await.unwrap(); assert_eq!(received, json!({"result": "success"})); // All cleaned up assert_eq!(tracker.pending_count(), 0); } #[tokio::test] async fn test_timeout_no_double_cleanup() { let tracker = AgentRequestTracker::new(); let client_id = "test-client"; let request_id = json!(0); // Register request let _receiver = tracker.register(client_id, request_id.clone()); assert_eq!(tracker.pending_count(), 1); // First timeout - should remove tracker.timeout(client_id, request_id.clone()); assert_eq!(tracker.pending_count(), 0); // Second timeout - should be no-op (not panic) tracker.timeout(client_id, request_id); assert_eq!(tracker.pending_count(), 0); } #[tokio::test] async fn test_timeout_race_with_complete() { let tracker = AgentRequestTracker::new(); let client_id = "test-client"; let request_id = json!(0); // Register request let receiver = tracker.register(client_id, request_id.clone()); assert_eq!(tracker.pending_count(), 1); // Complete the request let response = json!({"result": "success"}); let result = tracker.complete(client_id, request_id.clone(), response.clone()); assert!(result.is_ok()); assert_eq!(tracker.pending_count(), 0); // Try to timeout after completion - should be no-op tracker.timeout(client_id, request_id); assert_eq!(tracker.pending_count(), 0); // Receiver should still get the response let received = receiver.await.unwrap(); assert_eq!(received, response); } #[tokio::test] async fn test_concurrent_timeouts() { let tracker = AgentRequestTracker::new(); let client_id = "test-client"; // Register 10 requests let mut receivers = Vec::new(); for i in 0..10 { let rx = tracker.register(client_id, json!(i)); receivers.push((i, rx)); } assert_eq!(tracker.pending_count(), 10); // Spawn tasks to timeout each request after random delays let tracker_clone = tracker.clone(); let timeout_handles: Vec<_> = (0..10) .map(|i| { let tracker = tracker_clone.clone(); tokio::spawn(async move { // Small random-ish delay based on index tokio::time::sleep(Duration::from_millis((i * 10) as u64)).await; tracker.timeout(client_id, json!(i)); }) }) .collect(); // Wait for all timeouts to complete for handle in timeout_handles { handle.await.unwrap(); } // All should be cleaned up assert_eq!(tracker.pending_count(), 0); // All receivers should get errors for (_i, rx) in receivers { assert!(rx.await.is_err()); } } #[tokio::test] async fn test_timeout_with_actual_delay() { // This test uses actual time delays to verify timeout behavior more realistically let tracker = AgentRequestTracker::new(); let client_id = "test-client"; let request_id = json!(0); let start = std::time::Instant::now(); // Register request let receiver = tracker.register(client_id, request_id.clone()); // Spawn task to timeout after 200ms let tracker_clone = tracker.clone(); tokio::spawn(async move { tokio::time::sleep(Duration::from_millis(200)).await; tracker_clone.timeout(client_id, json!(0)); }); // Wait on receiver with longer timeout let result = timeout(Duration::from_secs(1), receiver).await; let elapsed = start.elapsed(); // Should complete due to timeout() call, not tokio::time::timeout assert!(result.is_ok(), "Should complete when timeout() is called"); assert!(result.unwrap().is_err(), "Receiver should get error"); // Should take approximately 200ms assert!( elapsed >= Duration::from_millis(180) && elapsed < Duration::from_millis(300), "Expected ~200ms but took {:?}", elapsed ); // Should be cleaned up assert_eq!(tracker.pending_count(), 0); }