//! Integration tests for data download workflow //! //! These tests validate the core download workflow: //! 1. Schedule download request //! 2. Download data from Databento (mocked) //! 3. Validate data quality //! 4. Upload to MinIO //! 5. Update job status //! //! TDD: These tests are written FIRST and should FAIL until implementation is complete. #[path = "common/workflow_types.rs"] mod workflow_types; #[path = "common/mock_service.rs"] mod mock_service; use mock_service::{create_test_service, create_test_service_with_corrupted_data}; use workflow_types::ScheduleDownloadRequest; use tempfile::TempDir; // Import proto enum for DownloadStatus use data_acquisition_service::proto::DownloadStatus; // Helper function to create test download request fn create_test_download_request() -> ScheduleDownloadRequest { ScheduleDownloadRequest::new_test_request() } /// Test: Schedule a download and verify it's created with PENDING status #[tokio::test] async fn test_schedule_download_creates_pending_job() { // Arrange: Create test service instance let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service(temp_dir.path()).await; let request = create_test_download_request(); // Act: Schedule download let response = service .schedule_download(request) .await .expect("Schedule download failed"); // Assert: Job created with PENDING status assert!(!response.job_id.is_empty(), "Job ID should not be empty"); assert_eq!( response.status, DownloadStatus::Pending as i32, "Initial status should be PENDING" ); assert!( response.estimated_cost_usd > 0.0, "Should have estimated cost" ); } /// Test: Download workflow progresses through all states #[tokio::test] async fn test_download_workflow_progresses_through_states() { // Arrange let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service(temp_dir.path()).await; let request = create_test_download_request(); // Act: Schedule download let schedule_response = service .schedule_download(request) .await .expect("Schedule failed"); let job_id = schedule_response.job_id.clone(); // Wait for download to start tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; // Assert: Job should progress to DOWNLOADING let status1 = service .get_download_status(job_id.clone()) .await .expect("Get status failed"); assert!( status1.job_details.status == DownloadStatus::Downloading as i32 || status1.job_details.status == DownloadStatus::Validating as i32, "Job should be downloading or validating, got: {}", status1.job_details.status ); // Wait for completion (with timeout) for _ in 0..100 { let status = service .get_download_status(job_id.clone()) .await .expect("Get status failed"); if status.job_details.status == DownloadStatus::Completed as i32 { // Assert: Final state should be COMPLETED assert_eq!(status.job_details.progress_percentage, 100.0); assert!(status.job_details.completed_at > 0); assert!(!status.job_details.minio_path.is_empty()); assert!(status.job_details.records_count > 0); return; // Success! } tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; } panic!("Download did not complete within timeout"); } /// Test: Get download status returns accurate progress #[tokio::test] async fn test_get_download_status_returns_accurate_progress() { // Arrange let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service(temp_dir.path()).await; let request = create_test_download_request(); // Act: Schedule download let schedule_response = service .schedule_download(request) .await .expect("Schedule failed"); let status_response = service .get_download_status(schedule_response.job_id.clone()) .await .expect("Get status failed"); // Assert: Status response has valid fields assert_eq!(status_response.job_details.job_id, schedule_response.job_id); assert_eq!(status_response.job_details.dataset, "GLBX.MDP3"); assert_eq!(status_response.job_details.symbols.len(), 2); assert!(status_response.job_details.progress_percentage >= 0.0); assert!(status_response.job_details.progress_percentage <= 100.0); } /// Test: List download jobs with pagination #[tokio::test] async fn test_list_download_jobs_with_pagination() { // Arrange: Create multiple download jobs let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service(temp_dir.path()).await; let mut job_ids = vec![]; for i in 0..5 { let mut request = create_test_download_request(); request.description = format!("Test download {}", i); let response = service .schedule_download(request) .await .expect("Schedule failed"); job_ids.push(response.job_id); } // Act: List with pagination let list_response = service .list_download_jobs(1, 3, None, None, None) .await .expect("List failed"); // Assert: Pagination works correctly assert_eq!(list_response.jobs.len(), 3, "Should return 3 jobs per page"); assert_eq!(list_response.total_count, 5, "Should have 5 total jobs"); assert_eq!(list_response.page, 1); assert_eq!(list_response.page_size, 3); } /// Test: Cancel download job #[tokio::test] async fn test_cancel_download_job() { // Arrange let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service(temp_dir.path()).await; let request = create_test_download_request(); let schedule_response = service .schedule_download(request) .await .expect("Schedule failed"); let job_id = schedule_response.job_id.clone(); // Act: Cancel job let cancel_response = service .cancel_download(job_id.clone(), "Test cancellation".to_string()) .await .expect("Cancel failed"); // Assert: Job is cancelled assert!(cancel_response.success, "Cancel should succeed"); let status = service .get_download_status(job_id) .await .expect("Get status failed"); assert_eq!(status.job_details.status, DownloadStatus::Cancelled as i32); } /// Test: Data quality validation detects issues #[tokio::test] async fn test_data_quality_validation_detects_issues() { // Arrange: Service with mock corrupted data let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service_with_corrupted_data(temp_dir.path()).await; let request = create_test_download_request(); // Act: Schedule download (will get corrupted data) let schedule_response = service .schedule_download(request) .await .expect("Schedule failed"); // Wait for completion tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; // Assert: Quality score should be low let status = service .get_download_status(schedule_response.job_id) .await .expect("Get status failed"); assert!( status.job_details.data_quality_score < 0.95, "Quality score should be low for corrupted data" ); assert!(status.job_details.invalid_records > 0); } /// Test: Cost estimation is accurate #[tokio::test] async fn test_cost_estimation_is_accurate() { // Arrange let temp_dir = TempDir::new().expect("Failed to create temp dir"); let service = create_test_service(temp_dir.path()).await; // Test different date ranges let test_cases = vec![ (1, 0.0, 2.0), // 1 day: $0-2 (7, 5.0, 15.0), // 7 days: $5-15 (30, 20.0, 60.0), // 30 days: $20-60 ]; for (days, min_cost, max_cost) in test_cases { let mut request = create_test_download_request(); request.start_date = "2024-01-01".to_string(); request.end_date = format!("2024-01-{:02}", days + 1); // Act let response = service .schedule_download(request) .await .expect("Schedule failed"); // Assert: Cost is in expected range assert!( response.estimated_cost_usd >= min_cost, "Cost {} should be >= {}", response.estimated_cost_usd, min_cost ); assert!( response.estimated_cost_usd <= max_cost, "Cost {} should be <= {}", response.estimated_cost_usd, max_cost ); } }