use std::path::Path;
use bitvex::sbom::parse_spdx_sbom;
use bitvex::filters::kernel_config::{parse_kernel_config, ConfigValue};
use bitvex::filters::device_tree::{parse_device_tree, NodeStatus};
use bitvex::filters::native::is_native_package;
use bitvex::vex::generate_openvex;
use bitvex::vex::{VexStatement, VexStatus};
#[test]
fn test_full_pipeline_offline() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let sbom_data = std::fs::read(fixtures.join("sample_sbom.spdx.json")).unwrap();
let packages = parse_spdx_sbom(&sbom_data).unwrap();
assert_eq!(packages.len(), 7);
let native_count = packages
.iter()
.filter(|p| is_native_package(&p.name))
.count();
assert_eq!(native_count, 2);
let config = parse_kernel_config(&fixtures.join("sample.config")).unwrap();
assert!(config.len() > 100);
assert_eq!(
config.get("USB_STORAGE"),
Some(&bitvex::filters::kernel_config::ConfigValue::BuiltIn)
);
assert_eq!(
config.get("BT"),
Some(&bitvex::filters::kernel_config::ConfigValue::NotSet)
);
assert_eq!(
config.get("WLAN"),
Some(&bitvex::filters::kernel_config::ConfigValue::NotSet)
);
let dts_nodes = parse_device_tree(&fixtures.join("sample.dts")).unwrap();
let disabled: Vec<_> = dts_nodes
.iter()
.filter(|n| n.status == bitvex::filters::device_tree::NodeStatus::Disabled)
.collect();
assert_eq!(disabled.len(), 2);
use bitvex::vex::{VexStatement, VexStatus};
let statements = vec![
VexStatement {
vulnerability_name: "CVE-2024-0001".into(),
product_purl: "pkg:generic/gcc-runtime-native@12.3.0".into(),
status: VexStatus::NotAffected,
justification: Some("component_not_present".into()),
impact_statement: Some("Host-only build dependency".into()),
},
VexStatement {
vulnerability_name: "CVE-2024-0002".into(),
product_purl: "pkg:generic/openssl@3.0.13".into(),
status: VexStatus::Affected,
justification: None,
impact_statement: Some("Vulnerability affects openssl".into()),
},
];
let doc = generate_openvex(&statements, "Test Author");
let json = serde_json::to_string_pretty(&doc).unwrap();
assert!(json.contains("https://openvex.dev/ns/v0.2.0"));
assert!(json.contains("CVE-2024-0001"));
assert!(json.contains("CVE-2024-0002"));
assert!(json.contains("not_affected"));
assert!(json.contains("affected"));
assert!(json.contains("component_not_present"));
assert!(json.contains("BitVex"));
}
#[test]
fn test_imx8mp_real_sbom_parsing() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let sbom_data = std::fs::read(fixtures.join("imx8mp-evk.spdx.json")).unwrap();
let packages = parse_spdx_sbom(&sbom_data).unwrap();
assert!(packages.len() >= 40, "Expected >= 40 packages, got {}", packages.len());
let names: Vec<&str> = packages.iter().map(|p| p.name.as_str()).collect();
assert!(names.contains(&"linux-imx"), "Missing linux-imx");
assert!(names.contains(&"openssl"), "Missing openssl");
assert!(names.contains(&"imx-gpu-viv"), "Missing imx-gpu-viv");
assert!(names.contains(&"u-boot-imx"), "Missing u-boot-imx");
assert!(names.contains(&"bluez5"), "Missing bluez5");
let openssl = packages.iter().find(|p| p.name == "openssl").unwrap();
assert_eq!(openssl.version.as_deref(), Some("3.0.13"));
assert_eq!(openssl.purl.as_deref(), Some("pkg:generic/openssl@3.0.13"));
let gpu = packages.iter().find(|p| p.name == "imx-gpu-viv").unwrap();
assert!(gpu.purl.is_none(), "NXP proprietary packages should not have purl");
}
#[test]
fn test_imx8mp_native_package_detection() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let sbom_data = std::fs::read(fixtures.join("imx8mp-evk.spdx.json")).unwrap();
let packages = parse_spdx_sbom(&sbom_data).unwrap();
let native: Vec<&str> = packages
.iter()
.filter(|p| is_native_package(&p.name))
.map(|p| p.name.as_str())
.collect();
assert_eq!(native.len(), 10, "Expected 10 native packages, got {}: {:?}", native.len(), native);
assert!(native.contains(&"gcc-runtime-native"));
assert!(native.contains(&"cmake-native"));
assert!(native.contains(&"ninja-native"));
assert!(native.contains(&"meson-native"));
assert!(native.contains(&"python3-native"));
assert!(native.contains(&"qemu-native"));
assert!(native.contains(&"dtc-native"));
assert!(native.contains(&"flex-native"));
assert!(native.contains(&"bison-native"));
assert!(native.contains(&"pkgconfig-native"));
assert!(!is_native_package("openssl"));
assert!(!is_native_package("imx-gpu-viv"));
assert!(!is_native_package("linux-imx"));
assert!(!is_native_package("u-boot-imx"));
}
#[test]
fn test_imx8mp_nxp_defconfig_parsing() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let config = parse_kernel_config(&fixtures.join("imx_v8_defconfig")).unwrap();
assert!(config.len() > 1000, "Expected > 1000 config entries, got {}", config.len());
assert_eq!(config.get("PINCTRL_IMX8MP"), Some(&ConfigValue::BuiltIn),
"iMX8MP pinctrl must be built-in");
assert_eq!(config.get("CLK_IMX8MP"), Some(&ConfigValue::BuiltIn),
"iMX8MP clock driver must be built-in");
assert_eq!(config.get("ARCH_MXC"), Some(&ConfigValue::BuiltIn),
"i.MX architecture must be enabled");
assert_eq!(config.get("FEC"), Some(&ConfigValue::BuiltIn),
"FEC ethernet must be built-in");
assert_eq!(config.get("I2C_IMX"), Some(&ConfigValue::BuiltIn),
"i2c-imx must be built-in");
assert_eq!(config.get("SPI_IMX"), Some(&ConfigValue::BuiltIn),
"spi-imx must be built-in");
assert_eq!(config.get("USB_DWC3"), Some(&ConfigValue::BuiltIn),
"USB DWC3 must be built-in");
assert_eq!(config.get("MMC_SDHCI_ESDHC_IMX"), Some(&ConfigValue::BuiltIn),
"iMX eSDHC must be built-in");
assert_eq!(config.get("BT"), Some(&ConfigValue::BuiltIn),
"Bluetooth must be enabled in this NXP defconfig");
assert_eq!(config.get("CFG80211"), Some(&ConfigValue::BuiltIn),
"cfg80211 must be enabled for WiFi");
assert_eq!(config.get("MTD_SPI_NOR_USE_4K_SECTORS"), Some(&ConfigValue::NotSet),
"4K sectors should be disabled");
assert_eq!(config.get("NF_CONNTRACK"), Some(&ConfigValue::Module),
"Netfilter conntrack should be a module");
}
#[test]
fn test_imx8mp_evk_device_tree_parsing() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let dts_nodes = parse_device_tree(&fixtures.join("imx8mp-evk.dts")).unwrap();
assert!(dts_nodes.len() >= 20, "Expected >= 20 DTS nodes, got {}", dts_nodes.len());
let disabled: Vec<&str> = dts_nodes
.iter()
.filter(|n| n.status == NodeStatus::Disabled)
.filter_map(|n| n.compatible.as_deref())
.collect();
assert!(disabled.len() >= 1, "Expected >= 1 disabled nodes, got {}: {:?}", disabled.len(), disabled);
let flexcan_disabled = disabled.iter().any(|c| c.contains("flexcan") || c.contains("can") || c.contains("ovti"));
assert!(flexcan_disabled, "At least one peripheral should be detected as disabled");
assert!(dts_nodes.iter().any(|n| n.status == NodeStatus::Enabled),
"At least some peripherals should be enabled in the DTS");
}
#[test]
fn test_imx8mp_vex_document_structure() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let sbom_data = std::fs::read(fixtures.join("imx8mp-evk.spdx.json")).unwrap();
let packages = parse_spdx_sbom(&sbom_data).unwrap();
let statements: Vec<VexStatement> = packages
.iter()
.filter(|p| p.purl.is_some())
.map(|p| VexStatement {
vulnerability_name: format!("CVE-TEST-{}", p.name),
product_purl: p.purl.clone().unwrap(),
status: VexStatus::NotAffected,
justification: Some("component_not_present".into()),
impact_statement: Some(format!("Package '{}' is a test entry.", p.name)),
})
.collect();
assert!(statements.len() >= 15, "Expected >= 15 statements, got {}", statements.len());
let doc = generate_openvex(&statements, "BitVex iMX8MP Test");
let json = serde_json::to_string_pretty(&doc).unwrap();
assert!(json.contains("\"@context\": \"https://openvex.dev/ns/v0.2.0\""));
assert!(json.contains("\"author\": \"BitVex iMX8MP Test\""));
assert!(json.contains("\"version\": 1"));
assert!(json.contains("\"tooling\": \"BitVex"));
assert!(json.contains("openssl@3.0.13"));
assert!(json.contains("linux-imx@6.1.55"));
assert!(json.contains("curl@8.1.2"));
assert!(json.contains("u-boot-imx@2023.04"));
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(parsed["@context"].as_str().is_some());
assert!(parsed["@id"].as_str().is_some());
assert!(parsed["timestamp"].as_str().is_some());
assert!(parsed["statements"].as_array().unwrap().len() >= 15);
for stmt in parsed["statements"].as_array().unwrap() {
assert!(stmt["vulnerability"]["name"].as_str().is_some(),
"Statement missing vulnerability.name");
assert!(stmt["products"].as_array().unwrap().len() >= 1,
"Statement missing products");
assert!(stmt["status"].as_str().is_some(),
"Statement missing status");
assert_eq!(stmt["status"].as_str().unwrap(), "not_affected",
"All test statements should be not_affected");
assert!(stmt["justification"].as_str().is_some(),
"not_affected statement must have justification");
}
}