rivox 1.0.0

Universal polyglot build coordination layer for Python, Rust, and Node monorepos
Documentation
use rivox::adapters::EcosystemAdapter;
use rivox::adapters::go::GoAdapter;
use rivox::adapters::gradle::GradleAdapter;
use rivox::adapters::python::PythonAdapter;
use rivox::cache::distributed::{CasEndpoint, DistributedCas};
use rivox::cache::local::LocalCas;
use rivox::cache::reapi::ReapiClient;
use rivox::cache::reapi_exec::{
    ActionSpec, CommandSpec, DigestVal, ExecuteRequest, ReapiExecClient,
};
use rivox::cache::remote_worker::RemoteWorker;
use rivox::conformance::suite::ConformanceSuite;
use rivox::graph::GraphNode;
use rivox::graph::builder::UnifiedGraph;
use rivox::graph::diff::diff_graphs;
use rivox::graph::scheduler::Scheduler;
use rivox::oci::OciBuilder;
use rivox::policy::{PolicyEngine, PolicyStatus};
use rivox::provenance::sigstore::sign_and_register_rekor;
use rivox::sandbox::get_default_sandbox;
use sha2::Digest;
use std::collections::HashMap;
use std::fs;
use tempfile::tempdir;

#[test]
fn test_python_conformance_equivalence() {
    let _adapter = PythonAdapter;
    let nodes1 = vec![GraphNode {
        ecosystem: "python".to_string(),
        package_name: "torch".to_string(),
        resolved_version: "2.1.0".to_string(),
        content_hash: "sha256:1234".to_string(),
        platform_markers: None,
        lock_ref: "uv.lock".to_string(),
    }];
    let nodes2 = nodes1.clone();

    assert!(ConformanceSuite::verify_equivalence(&nodes1, &nodes2).is_ok());
}

#[test]
fn test_go_adapter_lockfile_parse() {
    let dir = tempdir().unwrap();
    let lock_path = dir.path().join("go.sum");
    let content = "github.com/gin-gonic/gin v1.9.1 h1:4AAgXAIGfrXH3vXWyzBdp1pCMRamkpDQM3vSOAQDA38=\ngithub.com/gin-gonic/gin v1.9.1/go.mod h1:h4dYUFRTXIZoSlKGbyKj0nZzF7w0B+1K1J4F3vSOAQDA38=\n";
    fs::write(&lock_path, content).unwrap();

    let adapter = GoAdapter;
    let nodes = adapter.parse_lockfile(&lock_path).unwrap();
    assert_eq!(nodes.len(), 1);
    assert_eq!(nodes[0].package_name, "github.com/gin-gonic/gin");
    assert_eq!(nodes[0].resolved_version, "v1.9.1");
}

#[test]
fn test_gradle_adapter_lockfile_parse() {
    let dir = tempdir().unwrap();
    let lock_path = dir.path().join("gradle.lockfile");
    let content = "# This is a Gradle lockfile\norg.springframework.boot:spring-boot-starter-web:3.2.0=compileClasspath\n";
    fs::write(&lock_path, content).unwrap();

    let adapter = GradleAdapter;
    let nodes = adapter.parse_lockfile(&lock_path).unwrap();
    assert_eq!(nodes.len(), 1);
    assert_eq!(
        nodes[0].package_name,
        "org.springframework.boot:spring-boot-starter-web"
    );
    assert_eq!(nodes[0].resolved_version, "3.2.0");
}

#[test]
fn test_cas_prune_and_integrity() {
    let cas = LocalCas::new().unwrap();
    let dir = tempdir().unwrap();
    let test_file = dir.path().join("test.txt");
    fs::write(&test_file, "rivox content addressing").unwrap();

    let hash = "sha256:7798934526d11a7c413e1fb64c483d3126f58204689b142750faec899b821ee5";
    assert!(cas.store_file(hash, &test_file).is_ok());
    assert!(cas.has_blob(hash));

    let _pruned = cas.prune(30).unwrap();
}

#[tokio::test]
async fn test_reapi_client_validation() {
    let client = ReapiClient::new("http://localhost:8980".to_string());
    let blob = b"hello reapi".to_vec();
    let hash = format!("sha256:{}", hex::encode(sha2::Sha256::digest(&blob)));

    let result = client.upload_blobs(vec![(hash, blob)]).await;
    assert!(result.is_ok());
}

#[tokio::test]
async fn test_reapi_exec_client() {
    let client = ReapiExecClient::new("http://localhost:8980".to_string());
    let command = CommandSpec {
        arguments: vec!["cargo".to_string(), "--version".to_string()],
        environment_variables: HashMap::new(),
        output_files: vec![],
        output_directories: vec![],
    };
    let command_bytes = serde_json::to_vec(&command).unwrap();
    let command_digest = ReapiExecClient::compute_digest(&command_bytes);

    let action = ActionSpec {
        command_digest: command_digest.clone(),
        input_root_digest: DigestVal {
            hash: "sha256:0000000000000000000000000000000000000000000000000000000000000000"
                .to_string(),
            size_bytes: 0,
        },
        timeout_seconds: 60,
    };
    let action_bytes = serde_json::to_vec(&action).unwrap();
    let action_digest = ReapiExecClient::compute_digest(&action_bytes);

    let req = ExecuteRequest {
        action_digest,
        skip_cache_lookup: true,
        instance_name: "test".to_string(),
    };

    let resp = client
        .execute_action(&req, &action, &command)
        .await
        .unwrap();
    assert_eq!(resp.status_code, 0);
}

#[test]
fn test_remote_worker_path_traversal_rejection() {
    assert!(RemoteWorker::validate_path_security("valid/path.txt").is_ok());
    assert!(RemoteWorker::validate_path_security("../escape.txt").is_err());
    assert!(RemoteWorker::validate_path_security("/absolute/escape").is_err());
}

#[test]
fn test_distributed_cas_replication() {
    let local_cas = LocalCas::new().unwrap();
    let endpoints = vec![CasEndpoint {
        name: "primary".to_string(),
        url: "http://cas-primary.internal".to_string(),
        is_healthy: true,
        priority: 1,
    }];
    let dist_cas = DistributedCas::new(local_cas, endpoints);

    let dir = tempdir().unwrap();
    let test_file = dir.path().join("dist_test.txt");
    let content = b"distributed cas artifact";
    fs::write(&test_file, content).unwrap();

    let hash = format!("sha256:{}", hex::encode(sha2::Sha256::digest(content)));
    assert!(dist_cas.store_blob_with_failover(&hash, &test_file).is_ok());
}

#[test]
fn test_oci_builder_layout() {
    let dir = tempdir().unwrap();
    let output_dir = dir.path().join("oci_dist");
    let layout_dir = OciBuilder::build_oci_layout(&output_dir, "test_app", &[]).unwrap();

    assert!(layout_dir.join("oci-layout").exists());
    assert!(layout_dir.join("index.json").exists());
    assert!(layout_dir.join("blobs").join("sha256").exists());
}

#[test]
fn test_graph_diff_computation() {
    let node_old = GraphNode {
        ecosystem: "python".to_string(),
        package_name: "requests".to_string(),
        resolved_version: "2.28.0".to_string(),
        content_hash: "sha256:1111".to_string(),
        platform_markers: None,
        lock_ref: "uv.lock".to_string(),
    };

    let node_new = GraphNode {
        ecosystem: "python".to_string(),
        package_name: "requests".to_string(),
        resolved_version: "2.31.0".to_string(),
        content_hash: "sha256:2222".to_string(),
        platform_markers: None,
        lock_ref: "uv.lock".to_string(),
    };

    let diff = diff_graphs(&[node_old], &[node_new]);
    assert_eq!(diff.changed.len(), 1);
    assert!(diff.rebuild_required);
}

#[test]
fn test_policy_engine_enforcement() {
    let engine = PolicyEngine::new(rivox::policy::PolicyConfig::default());
    let allowed_node = GraphNode {
        ecosystem: "python".to_string(),
        package_name: "numpy".to_string(),
        resolved_version: "1.24.0".to_string(),
        content_hash: "sha256:abc".to_string(),
        platform_markers: None,
        lock_ref: "uv.lock".to_string(),
    };

    let res = engine.evaluate_graph(&[allowed_node]);
    assert_eq!(res.status, PolicyStatus::Pass);
}

#[test]
fn test_sandbox_default_executor() {
    let sandbox = get_default_sandbox();
    assert!(!sandbox.name().is_empty());
}

#[test]
fn test_scheduler_wavefront_levels() {
    let mut graph = UnifiedGraph::new();
    let node1 = GraphNode {
        ecosystem: "python".to_string(),
        package_name: "base".to_string(),
        resolved_version: "1.0".to_string(),
        content_hash: "sha256:1".to_string(),
        platform_markers: None,
        lock_ref: "uv.lock".to_string(),
    };
    let node2 = GraphNode {
        ecosystem: "rust".to_string(),
        package_name: "ext".to_string(),
        resolved_version: "0.1".to_string(),
        content_hash: "sha256:2".to_string(),
        platform_markers: None,
        lock_ref: "Cargo.lock".to_string(),
    };
    graph.add_nodes(vec![node1, node2]);

    let wavefronts = Scheduler::plan_wavefronts(&graph).unwrap();
    assert!(!wavefronts.is_empty());
}

#[test]
fn test_sigstore_rekor_attestation() {
    let statement = serde_json::json!({
        "buildType": "https://rivox.dev/attestations/v1",
        "subject": [{"name": "rivox-binary", "digest": {"sha256": "123456"}}]
    });

    let rekor_ref = sign_and_register_rekor(&statement).unwrap();
    assert!(rekor_ref.is_some());
    assert!(rekor_ref.unwrap().starts_with("rekor:"));
}