//! Integration tests for the job API //! //! These tests validate the complete job lifecycle using a real supervisor instance. //! They require Redis and a running supervisor to execute properly. use hero_supervisor_openrpc_client::{SupervisorClient, JobBuilder, JobResult}; use std::time::Duration; use tokio::time::sleep; use uuid::Uuid; /// Test helper to create a unique job for testing fn create_test_job(context: &str) -> Result> { JobBuilder::new() .caller_id("integration_test") .context_id(context) .payload("echo 'Test job output'") .executor("osis") .runner("osis_runner_1") .timeout(30) .env_var("TEST_VAR", "test_value") .build() .map_err(|e| e.into()) } /// Test helper to check if supervisor is available async fn is_supervisor_available() -> bool { match SupervisorClient::new("http://localhost:3030") { Ok(client) => client.discover().await.is_ok(), Err(_) => false, } } #[tokio::test] async fn test_jobs_create_and_start() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let secret = "user-secret-456"; let job = create_test_job("create_and_start").unwrap(); // Test jobs.create let job_id = client.jobs_create(secret, job).await.unwrap(); assert!(!job_id.is_empty()); // Test job.start let result = client.job_start(secret, &job_id).await; assert!(result.is_ok()); } #[tokio::test] async fn test_job_status_monitoring() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let secret = "user-secret-456"; let job = create_test_job("status_monitoring").unwrap(); let job_id = client.jobs_create(secret, job).await.unwrap(); client.job_start(secret, &job_id).await.unwrap(); // Test job.status let mut attempts = 0; let max_attempts = 10; while attempts < max_attempts { let status = client.job_status(&job_id).await.unwrap(); assert!(!status.job_id.is_empty()); assert!(!status.status.is_empty()); assert!(!status.created_at.is_empty()); if status.status == "completed" || status.status == "failed" { break; } attempts += 1; sleep(Duration::from_secs(1)).await; } } #[tokio::test] async fn test_job_result_retrieval() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let secret = "user-secret-456"; let job = create_test_job("result_retrieval").unwrap(); let job_id = client.jobs_create(secret, job).await.unwrap(); client.job_start(secret, &job_id).await.unwrap(); // Wait a bit for job to complete sleep(Duration::from_secs(3)).await; // Test job.result let result = client.job_result(&job_id).await.unwrap(); match result { JobResult::Success { success } => { assert!(!success.is_empty()); }, JobResult::Error { error } => { assert!(!error.is_empty()); } } } #[tokio::test] async fn test_job_run_immediate() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let secret = "user-secret-456"; let job = create_test_job("immediate_run").unwrap(); // Test job.run (immediate execution) let result = client.job_run(secret, job).await.unwrap(); match result { JobResult::Success { success } => { assert!(!success.is_empty()); }, JobResult::Error { error } => { assert!(!error.is_empty()); } } } #[tokio::test] async fn test_jobs_list() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); // Test jobs.list let job_ids = client.jobs_list().await.unwrap(); // Should return a vector (might be empty) assert!(job_ids.len() >= 0); } #[tokio::test] async fn test_authentication_failures() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let invalid_secret = "invalid-secret-123"; let job = create_test_job("auth_failure").unwrap(); // Test that invalid secrets fail let result = client.jobs_create(invalid_secret, job.clone()).await; assert!(result.is_err()); let result = client.job_run(invalid_secret, job.clone()).await; assert!(result.is_err()); let result = client.job_start(invalid_secret, "fake-job-id").await; assert!(result.is_err()); } #[tokio::test] async fn test_nonexistent_job_operations() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let fake_job_id = format!("nonexistent-{}", Uuid::new_v4()); // Test operations on nonexistent job should fail let result = client.job_status(&fake_job_id).await; assert!(result.is_err()); let result = client.job_result(&fake_job_id).await; assert!(result.is_err()); } #[tokio::test] async fn test_complete_workflow() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let secret = "user-secret-456"; let job = create_test_job("complete_workflow").unwrap(); // Complete workflow test let job_id = client.jobs_create(secret, job).await.unwrap(); client.job_start(secret, &job_id).await.unwrap(); // Monitor until completion let mut final_status = String::new(); for _ in 0..15 { let status = client.job_status(&job_id).await.unwrap(); final_status = status.status.clone(); if final_status == "completed" || final_status == "failed" || final_status == "timeout" { break; } sleep(Duration::from_secs(1)).await; } // Get final result let result = client.job_result(&job_id).await.unwrap(); match result { JobResult::Success { .. } => { assert_eq!(final_status, "completed"); }, JobResult::Error { .. } => { assert!(final_status == "failed" || final_status == "timeout"); } } } #[tokio::test] async fn test_batch_job_processing() { if !is_supervisor_available().await { println!("Skipping test - supervisor not available"); return; } let client = SupervisorClient::new("http://localhost:3030").unwrap(); let secret = "user-secret-456"; let job_count = 3; let mut job_ids = Vec::new(); // Create multiple jobs for i in 0..job_count { let job = JobBuilder::new() .caller_id("integration_test") .context_id(&format!("batch_job_{}", i)) .payload(&format!("echo 'Batch job {}'", i)) .executor("osis") .runner("osis_runner_1") .timeout(30) .build() .unwrap(); let job_id = client.jobs_create(secret, job).await.unwrap(); job_ids.push(job_id); } // Start all jobs for job_id in &job_ids { client.job_start(secret, job_id).await.unwrap(); } // Wait for all jobs to complete sleep(Duration::from_secs(5)).await; // Collect all results let mut results = Vec::new(); for job_id in &job_ids { let result = client.job_result(job_id).await.unwrap(); results.push(result); } // Verify we got results for all jobs assert_eq!(results.len(), job_count); }