Update osiris dependency to use osiris-core package

This commit is contained in:
Timur Gordon
2025-11-04 13:09:13 +01:00
parent 2ea963ddf5
commit 4158d02f3d
14 changed files with 1282 additions and 1723 deletions

View File

@@ -1,118 +1,212 @@
use std::process::Command;
use std::time::Duration;
use tokio::time::timeout;
use tokio::time::sleep;
use runner_rust::{JobBuilder, Client};
use runner_rust::job::JobSignature;
use uuid::Uuid;
use rhai::Engine;
/// Test the SAL runner in script mode with a simple ping script
/// Helper function to create a SAL engine for testing
fn create_test_sal_engine() -> Engine {
// Create a basic Rhai engine for testing
Engine::new()
}
/// Test job execution with empty signatures array
/// This verifies that jobs without signatures can execute successfully
#[tokio::test]
async fn test_sal_runner_script_mode_ping() {
let output = timeout(
Duration::from_secs(10),
run_sal_runner_script_mode("test_sal_ping")
).await;
match output {
Ok(result) => {
assert!(result.is_ok(), "SAL runner should execute successfully");
let stdout = result.unwrap();
assert!(stdout.contains("pong"),
"Output should contain 'pong' response: {}", stdout);
}
Err(_) => panic!("Test timed out after 10 seconds"),
}
async fn test_job_execution_without_signatures() {
let redis_url = "redis://localhost:6379";
let runner_id = format!("test-runner-{}", Uuid::new_v4());
// Create client
let mut client = Client::builder()
.redis_url(redis_url)
.build()
.await
.expect("Failed to create client");
// Create job with empty signatures array
let job = JobBuilder::new()
.caller_id("test_caller")
.context_id("test_context")
.payload("print(\"Hello from unsigned job\");")
.runner(&runner_id)
.executor("rhai")
.timeout(30)
.build()
.expect("Job creation should succeed");
let job_id = job.id.clone();
// Verify signatures array is empty
assert!(job.signatures.is_empty(), "Job should have no signatures");
// Save job to Redis
client.store_job_in_redis(&job).await
.expect("Failed to save job to Redis");
// Queue the job
client.dispatch_job(&job_id, &runner_id).await
.expect("Failed to queue job");
// Spawn runner in background
let runner_id_clone = runner_id.clone();
let redis_url_clone = redis_url.to_string();
let runner_handle = tokio::spawn(async move {
let (shutdown_tx, shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
// Run for 5 seconds then shutdown
tokio::spawn(async move {
sleep(Duration::from_secs(5)).await;
let _ = shutdown_tx.send(()).await;
});
runner_rust::spawn_sync_runner(
runner_id_clone,
redis_url_clone,
shutdown_rx,
create_test_sal_engine,
).await
});
// Wait for job to be processed
sleep(Duration::from_secs(3)).await;
// Check job result
let result = client.get_result(&job_id).await
.expect("Failed to get job result");
assert!(result.is_some(), "Job should have produced a result");
// Cleanup
let _ = runner_handle.await;
client.delete_job(&job_id).await.ok();
}
/// Test the OSIS runner in script mode with a simple ping script
/// Test job signature verification with valid signatures
/// This verifies that jobs with valid signatures are accepted
#[tokio::test]
async fn test_osis_runner_script_mode_ping() {
let output = timeout(
Duration::from_secs(10),
run_osis_runner_script_mode("test_osis_ping")
).await;
match output {
Ok(result) => {
assert!(result.is_ok(), "OSIS runner should execute successfully");
let stdout = result.unwrap();
assert!(stdout.contains("pong"),
"Output should contain 'pong' response: {}", stdout);
}
Err(_) => panic!("Test timed out after 10 seconds"),
}
async fn test_job_signature_verification() {
use secp256k1::{Secp256k1, SecretKey, Message};
use sha2::{Sha256, Digest};
let redis_url = "redis://localhost:6379";
let runner_id = format!("test-runner-{}", Uuid::new_v4());
// Create client
let mut client = Client::builder()
.redis_url(redis_url)
.build()
.await
.expect("Failed to create client");
// Generate a keypair for signing
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[0xcd; 32])
.expect("32 bytes, within curve order");
let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
// Create job
let mut job = JobBuilder::new()
.caller_id("test_caller")
.context_id("test_context")
.payload("print(\"Hello from signed job\");")
.runner(&runner_id)
.executor("rhai")
.timeout(30)
.build()
.expect("Job creation should succeed");
let job_id = job.id.clone();
// Sign the job
let canonical = job.canonical_representation();
let mut hasher = Sha256::new();
hasher.update(canonical.as_bytes());
let hash = hasher.finalize();
let message = Message::from_digest_slice(&hash)
.expect("32 bytes");
let signature = secp.sign_ecdsa(&message, &secret_key);
// Add signature to job
job.signatures.push(JobSignature {
public_key: hex::encode(public_key.serialize()),
signature: hex::encode(signature.serialize_compact()),
});
// Verify the job has a signature
assert_eq!(job.signatures.len(), 1, "Job should have one signature");
// Verify signatures are valid
job.verify_signatures()
.expect("Signature verification should succeed");
// Save and queue job
client.store_job_in_redis(&job).await
.expect("Failed to save job to Redis");
client.dispatch_job(&job_id, &runner_id).await
.expect("Failed to queue job");
// Spawn runner
let runner_id_clone = runner_id.clone();
let redis_url_clone = redis_url.to_string();
let runner_handle = tokio::spawn(async move {
let (shutdown_tx, shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
tokio::spawn(async move {
sleep(Duration::from_secs(5)).await;
let _ = shutdown_tx.send(()).await;
});
runner_rust::spawn_sync_runner(
runner_id_clone,
redis_url_clone,
shutdown_rx,
create_test_sal_engine,
).await
});
// Wait for processing
sleep(Duration::from_secs(3)).await;
// Check result
let result = client.get_result(&job_id).await
.expect("Failed to get job result");
assert!(result.is_some(), "Signed job should have produced a result");
// Cleanup
let _ = runner_handle.await;
client.delete_job(&job_id).await.ok();
}
/// Helper function to run SAL runner in script mode
async fn run_sal_runner_script_mode(
runner_id: &str
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let output = Command::new("cargo")
.args(&[
"run", "--bin", "runner_sal", "--",
runner_id,
"-s", "ping"
])
.output()?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("Command failed: {}", stderr).into())
}
}
/// Helper function to run OSIS runner in script mode
async fn run_osis_runner_script_mode(
runner_id: &str
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let output = Command::new("cargo")
.args(&[
"run", "--bin", "runner_osis", "--",
runner_id,
"-s", "ping"
])
.output()?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("Command failed: {}", stderr).into())
}
}
/// Test basic compilation and help output
/// Test job with invalid signature is rejected
#[tokio::test]
async fn test_sal_runner_help() {
let output = Command::new("cargo")
.args(&["run", "--bin", "runner_sal", "--", "--help"])
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Help command should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Usage") || stdout.contains("USAGE"),
"Help output should contain usage information");
async fn test_job_invalid_signature_rejected() {
// Create job with invalid signature
let mut job = JobBuilder::new()
.caller_id("test_caller")
.context_id("test_context")
.payload("print(\"This should fail\");")
.runner("test-runner")
.executor("rhai")
.build()
.expect("Job creation should succeed");
// Add invalid signature
job.signatures.push(JobSignature {
public_key: "04invalid_public_key".to_string(),
signature: "invalid_signature".to_string(),
});
// Verify signatures should fail
let result = job.verify_signatures();
assert!(result.is_err(), "Invalid signature should be rejected");
}
/// Test basic compilation and help output for OSIS runner
#[tokio::test]
async fn test_osis_runner_help() {
let output = Command::new("cargo")
.args(&["run", "--bin", "runner_osis", "--", "--help"])
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Help command should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Usage") || stdout.contains("USAGE"),
"Help output should contain usage information");
}
/// Test library functionality - job creation and basic operations
/// Test job creation and serialization
#[tokio::test]
async fn test_job_creation_and_serialization() {
use runner_rust::JobBuilder;
let job = JobBuilder::new()
.caller_id("test_caller")
.context_id("test_context")
@@ -127,4 +221,17 @@ async fn test_job_creation_and_serialization() {
assert_eq!(job.payload, "ping");
assert_eq!(job.runner, "default");
assert_eq!(job.executor, "rhai");
assert!(job.signatures.is_empty(), "Default job should have no signatures");
// Test serialization
let json = serde_json::to_string(&job)
.expect("Job should serialize to JSON");
// Test deserialization
let deserialized: runner_rust::Job = serde_json::from_str(&json)
.expect("Job should deserialize from JSON");
assert_eq!(job.id, deserialized.id);
assert_eq!(job.caller_id, deserialized.caller_id);
assert_eq!(job.signatures.len(), deserialized.signatures.len());
}