Files
supervisor/tests/job_api_integration_tests.rs
2025-08-27 10:07:53 +02:00

280 lines
8.1 KiB
Rust

//! 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<hero_supervisor_openrpc_client::Job, Box<dyn std::error::Error>> {
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);
}