sbom-tools 0.1.22

Semantic SBOM diff and analysis tool
Documentation
mod common;

use common::ffi_helpers::{consume_result, into_c_string};
use sbom_tools::ffi::{
    SbomToolsErrorCode, SbomToolsScoringProfile, sbom_tools_abi_version_json,
    sbom_tools_detect_format_json, sbom_tools_diff_sboms_json, sbom_tools_parse_sbom_path_json,
    sbom_tools_parse_sbom_str_json, sbom_tools_score_sbom_json,
};
use std::path::Path;

const FIXTURES_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures");

fn fixture_path(name: &str) -> std::path::PathBuf {
    Path::new(FIXTURES_DIR).join(name)
}

#[test]
fn ffi_reports_abi_version() {
    let payload = consume_result(sbom_tools_abi_version_json()).expect("ABI version should load");
    let value: serde_json::Value = serde_json::from_str(&payload).expect("valid JSON response");
    assert_eq!(value["abi_version"], "1");
    assert!(value["crate_version"].as_str().is_some());
}

#[test]
fn ffi_detects_format_and_parses_path() {
    let fixture = fixture_path("cyclonedx/minimal.cdx.json");
    let content = std::fs::read_to_string(&fixture).expect("fixture should load");
    let content = into_c_string(&content);
    let detection = consume_result(sbom_tools_detect_format_json(content.as_ptr()))
        .expect("format detection should succeed");
    assert!(detection.contains("CycloneDX"));

    let path = into_c_string(fixture.to_string_lossy().as_ref());
    let parsed = consume_result(sbom_tools_parse_sbom_path_json(path.as_ptr()))
        .expect("path parsing should succeed");
    let value: serde_json::Value = serde_json::from_str(&parsed).expect("parsed JSON should load");
    assert_eq!(value["document"]["format"], "CycloneDx");
}

#[test]
fn ffi_can_diff_and_score_normalized_sboms() {
    let old_path = fixture_path("demo-old.cdx.json");
    let new_path = fixture_path("demo-new.cdx.json");
    let old_input = into_c_string(&std::fs::read_to_string(&old_path).expect("old fixture"));
    let new_input = into_c_string(&std::fs::read_to_string(&new_path).expect("new fixture"));

    let old_json = consume_result(sbom_tools_parse_sbom_str_json(old_input.as_ptr()))
        .expect("old parse should succeed");
    let new_json = consume_result(sbom_tools_parse_sbom_str_json(new_input.as_ptr()))
        .expect("new parse should succeed");

    let old_json_c = into_c_string(&old_json);
    let new_json_c = into_c_string(&new_json);
    let diff_json = consume_result(sbom_tools_diff_sboms_json(
        old_json_c.as_ptr(),
        new_json_c.as_ptr(),
    ))
    .expect("diff should succeed");
    let diff_value: serde_json::Value = serde_json::from_str(&diff_json).expect("diff JSON");
    assert!(diff_value["summary"]["total_changes"].as_u64().unwrap_or(0) > 0);

    let score_json = consume_result(sbom_tools_score_sbom_json(
        new_json_c.as_ptr(),
        SbomToolsScoringProfile::Standard,
    ))
    .expect("score should succeed");
    let score_value: serde_json::Value = serde_json::from_str(&score_json).expect("score JSON");
    assert!(score_value["overall_score"].as_f64().unwrap_or(0.0) > 0.0);
}

#[test]
fn ffi_reports_validation_errors() {
    let invalid_json = into_c_string("{not-json}");
    let err = consume_result(sbom_tools_diff_sboms_json(
        invalid_json.as_ptr(),
        invalid_json.as_ptr(),
    ))
    .expect_err("invalid normalized JSON should fail");

    assert_eq!(err.0, SbomToolsErrorCode::Validation);
    assert!(err.1.contains("invalid normalized SBOM JSON"));
}