use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
mod common;
use common::paths;
use common::utils;
fn run_vex2pdf(
input_path: &str,
output_dir: &Path,
extra_args: Option<Vec<&str>>,
) -> std::process::Output {
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(output_dir)
.args(extra_args.unwrap_or(vec![]))
.arg(input_path)
.output()
.expect("Failed to execute command");
let output = output;
if !output.stderr.is_empty() {
eprintln!("stderr: {}", utils::bytes_to_str(&output.stderr));
}
assert!(
output.status.success(),
"Command failed with status: {}",
output.status
);
utils::assert_output_contains(&output.stdout, "Successfully generated PDF:");
output
}
fn get_expected_pdf_name(input_path: &str) -> String {
let path = Path::new(input_path);
let stem = path.file_stem().unwrap().to_str().unwrap();
format!("{}.pdf", stem)
}
#[test]
fn test_simple_bom_with_one_vuln() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::SIMPLE_BOM_PATH, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::SIMPLE_BOM_PATH);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vdr_minimal_with_vulns() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VDR_MINIMAL_WITH_VULNS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_MINIMAL_WITH_VULNS);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vdr_with_ghsa_entries() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VDR_WITH_GHSA_ENTRIES, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_GHSA_ENTRIES);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vdr_with_many_vulns() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VDR_WITH_MANY_VULNS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_MANY_VULNS);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vdr_with_no_vulns() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VDR_WITH_NO_VULNS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_NO_VULNS);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vdr_with_links_as_versions() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VDR_WITH_LINKS_AS_VERSIONS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_LINKS_AS_VERSIONS);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vex_with_links_as_versions() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VEX_WITH_LINKS_AS_VERSIONS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VEX_WITH_LINKS_AS_VERSIONS);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_novulns_directory() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_NOVULNS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_NOVULNS);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vdr_simple_xml() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VDR_SIMPLE_XML, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_SIMPLE_XML);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_vex_simple_xml() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::BOM_VEX_SIMPLE_XML, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VEX_SIMPLE_XML);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_sample_vex_xml() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
run_vex2pdf(paths::SAMPLE_VEX_XML, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::SAMPLE_VEX_XML);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
utils::assert_pdf_checksum_matches(&generated_pdf);
}
#[test]
fn test_batch_run_test_directory() {
let temp_input_dir = TempDir::new().expect("Failed to create temp input dir");
let temp_output_dir = TempDir::new().expect("Failed to create temp output dir");
let src_dir = Path::new(paths::SOURCE_BOMS_BASE_ARTIFACTS_DIR);
let files_copied = utils::copy_directory_files(
src_dir,
temp_input_dir.path(),
Some(vec!["_titles_override"]),
)
.expect("Failed to copy files");
assert!(
files_copied > 0,
"No files were copied from run_test directory"
);
let expected_count = utils::count_processable_files(temp_input_dir.path());
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_output_dir.path())
.arg(temp_input_dir.path())
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Batch processing failed: {}",
utils::bytes_to_str(&output.stderr)
);
utils::assert_pdf_count(temp_output_dir.path(), expected_count);
for entry in std::fs::read_dir(temp_output_dir.path()).unwrap() {
let entry = entry.unwrap();
let generated_pdf = entry.path();
if generated_pdf.extension().and_then(|e| e.to_str()) == Some("pdf") {
utils::assert_pdf_checksum_matches(&generated_pdf);
}
}
}
#[test]
fn test_batch_run_test_xml_directory() {
let temp_input_dir = TempDir::new().expect("Failed to create temp input dir");
let temp_output_dir = TempDir::new().expect("Failed to create temp output dir");
let src_dir = Path::new(paths::SOURCE_BOMS_XML_ARTIFACTS_DIR);
let files_copied = utils::copy_directory_files(
src_dir,
temp_input_dir.path(),
Some(vec!["_titles_override"]),
)
.expect("Failed to copy files");
assert!(
files_copied > 0,
"No files were copied from run_test_xml directory"
);
let expected_count = utils::count_processable_files(temp_input_dir.path());
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_output_dir.path())
.arg(temp_input_dir.path())
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Batch processing failed: {}",
utils::bytes_to_str(&output.stderr)
);
utils::assert_pdf_count(temp_output_dir.path(), expected_count);
for entry in std::fs::read_dir(temp_output_dir.path()).unwrap() {
let entry = entry.unwrap();
let generated_pdf = entry.path();
if generated_pdf.extension().and_then(|e| e.to_str()) == Some("pdf") {
utils::assert_pdf_checksum_matches(&generated_pdf);
}
}
}
#[test]
fn test_batch_no_args_current_directory() {
let temp_work_dir = TempDir::new().expect("Failed to create temp work dir");
std::fs::copy(
paths::SIMPLE_BOM_PATH,
temp_work_dir.path().join("test1.json"),
)
.expect("Failed to copy test file");
std::fs::copy(
paths::BOM_VDR_WITH_NO_VULNS,
temp_work_dir.path().join("test2.json"),
)
.expect("Failed to copy test file");
std::fs::copy(
paths::BOM_VDR_SIMPLE_XML,
temp_work_dir.path().join("test3.xml"),
)
.expect("Failed to copy test file");
let expected_count = 3;
let output = Command::new(paths::PATH_TO_EXE)
.current_dir(temp_work_dir.path())
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Batch processing in current dir failed: {}",
utils::bytes_to_str(&output.stderr)
);
utils::assert_pdf_count(temp_work_dir.path(), expected_count);
utils::assert_pdf_created(&temp_work_dir.path().join("test1.pdf"));
utils::assert_pdf_created(&temp_work_dir.path().join("test2.pdf"));
utils::assert_pdf_created(&temp_work_dir.path().join("test3.pdf"));
}
#[test]
fn test_batch_non_recursive_scanning() {
let temp_input_dir = TempDir::new().expect("Failed to create temp input dir");
let temp_output_dir = TempDir::new().expect("Failed to create temp output dir");
let subdir = temp_input_dir.path().join("subdir");
std::fs::create_dir(&subdir).expect("Failed to create subdir");
std::fs::copy(paths::SIMPLE_BOM_PATH, subdir.join("nested.json"))
.expect("Failed to copy to subdir");
std::fs::copy(
paths::BOM_VDR_WITH_NO_VULNS,
temp_input_dir.path().join("top_level1.json"),
)
.expect("Failed to copy to main dir");
std::fs::copy(
paths::BOM_VDR_SIMPLE_XML,
temp_input_dir.path().join("top_level2.xml"),
)
.expect("Failed to copy to main dir");
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_output_dir.path())
.arg(temp_input_dir.path())
.output()
.expect("Failed to execute command");
assert!(output.status.success());
utils::assert_pdf_count(temp_output_dir.path(), 2);
utils::assert_pdf_created(&temp_output_dir.path().join("top_level1.pdf"));
utils::assert_pdf_created(&temp_output_dir.path().join("top_level2.pdf"));
assert!(
!temp_output_dir.path().join("nested.pdf").exists(),
"Should not process files in subdirectories"
);
}
#[test]
fn test_batch_empty_directory() {
let temp_empty_dir = TempDir::new().expect("Failed to create temp empty dir");
let temp_output_dir = TempDir::new().expect("Failed to create temp output dir");
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_output_dir.path())
.arg(temp_empty_dir.path())
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Empty directory processing should succeed"
);
utils::assert_pdf_count(temp_output_dir.path(), 0);
let stdout = utils::bytes_to_str(&output.stdout);
assert!(
stdout.contains("Found 0 JSON files") || stdout.contains("Processed 0 files"),
"Should report 0 files processed"
);
}
#[test]
fn test_output_directory_argument() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_dir.path())
.arg(paths::SIMPLE_BOM_PATH)
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let pdf_name = get_expected_pdf_name(paths::SIMPLE_BOM_PATH);
let generated_pdf = temp_dir.path().join(&pdf_name);
utils::assert_pdf_created(&generated_pdf);
}
#[test]
fn test_invalid_output_directory_fails() {
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg("/nonexistent/directory/that/does/not/exist")
.arg(paths::SIMPLE_BOM_PATH)
.output()
.expect("Failed to execute command");
assert!(!output.status.success());
assert!(!output.stderr.is_empty());
}
#[test]
fn test_invalid_property_missing_value_returns_nonzero_exit() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_dir.path())
.arg(paths::BOM_VDR_WITH_INVALID_PROPERTY)
.output()
.expect("Failed to execute command");
assert!(
!output.status.success(),
"Expected non-zero exit on malformed BoM, got: {}\nstdout: {}\nstderr: {}",
output.status,
utils::bytes_to_str(&output.stdout),
utils::bytes_to_str(&output.stderr),
);
let stderr = utils::bytes_to_str(&output.stderr);
assert!(
stderr.contains("missing field `value`"),
"Expected per-file parse error in stderr, got: {stderr}"
);
assert!(
stderr.contains("failed to process"),
"Expected aggregate processing-failure message in stderr, got: {stderr}"
);
utils::assert_pdf_count(temp_dir.path(), 0);
}
#[test]
fn test_nonexistent_input_file_fails() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg(temp_dir.path())
.arg("/nonexistent/file.json")
.output()
.expect("Failed to execute command");
assert!(!output.status.success());
}
#[test]
fn test_json_with_analysis_renders_correctly() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let _ = run_vex2pdf(paths::BOM_VDR_WITH_ANALYSIS, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_ANALYSIS);
let pdf_path = temp_dir.path().join(&pdf_name);
assert!(
pdf_path.exists(),
"Expected PDF not found: {}",
pdf_path.display()
);
utils::assert_pdf_checksum_matches(&pdf_path);
}
#[test]
fn test_xml_with_analysis_renders_correctly() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let _ = run_vex2pdf(paths::BOM_VDR_WITH_ANALYSIS_XML, temp_dir.path(), None);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_ANALYSIS_XML);
let pdf_path = temp_dir.path().join(&pdf_name);
assert!(
pdf_path.exists(),
"Expected PDF not found: {}",
pdf_path.display()
);
utils::assert_pdf_checksum_matches(&pdf_path);
}
#[test]
fn test_err_output() {
let output = Command::new(paths::PATH_TO_EXE)
.arg("-d")
.arg("/path/to/unknown")
.output()
.expect("failed to run executable");
let stderr_str = String::from_utf8_lossy(&output.stderr);
assert!(stderr_str.contains("Problem setting up working environment"));
}
#[test]
fn test_license_output() {
let output = Command::new(paths::PATH_TO_EXE)
.arg("--license")
.output()
.expect("failed to run executable");
let stderr_str = String::from_utf8_lossy(&output.stdout);
assert!(stderr_str.contains(
"VEX2PDF is licensed under either MIT or Apache License, Version 2.0 at your option."
));
assert!(stderr_str.contains("license text can be found under: https://gitlab.com/jurassicLizard/vex2pdf/-/blob/master/README.md#license"));
assert!(stderr_str.contains("SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007"));
assert!(stderr_str.contains("DEALINGS IN THE FONT SOFTWARE"));
}
#[test]
fn test_version_long_output() {
let output = Command::new(paths::PATH_TO_EXE)
.arg("--version")
.output()
.expect("failed to run executable");
let stdout_str = String::from_utf8_lossy(&output.stdout);
assert!(stdout_str.contains("vex2pdf"));
assert!(stdout_str.contains("CycloneDX (VEX) to PDF Converter"));
assert!(stdout_str.contains("Copyright (c) 2025 Salem B. - MIT Or Apache 2.0 License"));
}
#[test]
fn test_version_info_on_startup() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let output = Command::new(paths::PATH_TO_EXE)
.current_dir(temp_dir.path())
.output()
.expect("failed to run executable");
let stdout_str = String::from_utf8_lossy(&output.stdout);
assert!(stdout_str.contains("vex2pdf"));
assert!(stdout_str.contains("CycloneDX (VEX) to PDF Converter"));
assert!(stdout_str.contains("Copyright (c) 2025 Salem B. - MIT Or Apache 2.0 License"));
}
#[test]
fn test_report_title_override_via_cli() {
let temp_dir = TempDir::new().expect("Failed to create temporary directory");
let _ = run_vex2pdf(
paths::BOM_VDR_WITH_MANY_VULNS_TITLES_OVERRIDE,
temp_dir.path(),
Some(vec!["-t", "Title override", "-n", "Meta name override"]),
);
let pdf_name = get_expected_pdf_name(paths::BOM_VDR_WITH_MANY_VULNS_TITLES_OVERRIDE);
let pdf_path = temp_dir.path().join(&pdf_name);
assert!(
pdf_path.exists(),
"Expected PDF not found: {}",
pdf_path.display()
);
utils::assert_pdf_checksum_matches(&pdf_path);
}