#![cfg(feature = "oasis-validator")]
use csaf_core::storage::CsafStorage;
use csaf_core::validation::Severity;
use csaf_core::{export, import, oasis, validation};
use csaf_models::csaf_document::CsafDocument;
use csaf_models::settings::Settings;
const GOOD: &str = include_str!("../../../test/csaf/2026/003/ndaal-sa-2026-003.json");
fn good_doc() -> CsafDocument {
serde_json::from_str(GOOD).expect("fixture must parse")
}
fn settings_for(dir: &std::path::Path) -> Settings {
Settings {
export_directory: dir.to_string_lossy().into_owned(),
csaf_mode: "2.1".to_owned(),
..Settings::default()
}
}
fn hard_errors(json: &str) -> Vec<String> {
oasis::validate_oasis_json(json)
.expect("validator runs")
.into_iter()
.filter(|f| f.severity == Severity::Error)
.map(|f| format!("{}: {}", f.path, f.message))
.collect()
}
#[test]
fn oasis_accepts_the_conformant_fixture() {
assert!(
hard_errors(GOOD).is_empty(),
"conformant fixture must pass OASIS: {:?}",
hard_errors(GOOD)
);
assert!(oasis::is_oasis_valid(GOOD));
}
#[test]
fn oasis_rejects_cvss_v4_with_v3_style_impact_keys() {
let bad = GOOD
.replace("vulnConfidentialityImpact", "confidentialityImpact")
.replace("vulnIntegrityImpact", "integrityImpact")
.replace("vulnAvailabilityImpact", "availabilityImpact");
let doc: CsafDocument = serde_json::from_str(&bad).expect("parses");
assert!(
validation::validate(&doc)
.iter()
.all(|e| e.severity != Severity::Error),
"built-in validator should not flag the v4 key bug"
);
assert!(
!hard_errors(&bad).is_empty(),
"OASIS must reject cvss_v4 with v3-style impact keys"
);
}
#[test]
fn oasis_rejects_singular_cwe_on_a_2_1_document() {
let mut value: serde_json::Value = serde_json::from_str(GOOD).expect("parse");
value["vulnerabilities"][0]["cwe"] =
serde_json::json!({ "id": "CWE-79", "name": "Cross-site Scripting" });
let bad = serde_json::to_string(&value).expect("reserialize");
assert!(
!hard_errors(&bad).is_empty(),
"OASIS must reject a singular `cwe` on a CSAF 2.1 document"
);
}
#[test]
fn oasis_rejects_unparseable_and_versionless_input() {
assert!(oasis::validate_oasis_json("not json at all").is_err());
assert!(oasis::validate_oasis_json(r#"{"document":{}}"#).is_err());
}
#[test]
fn export_accepts_a_conformant_document() {
let dir = tempfile::tempdir().expect("tmpdir");
let path = export::export_document(&good_doc(), &settings_for(dir.path()))
.expect("conformant export must succeed");
assert!(path.exists(), "exported file must be on disk");
}
#[test]
fn export_refuses_an_oasis_invalid_document_and_writes_nothing() {
let dir = tempfile::tempdir().expect("tmpdir");
let mut doc = good_doc();
doc.document.lang = Some("invalid lang!!".to_owned());
let result = export::export_document(&doc, &settings_for(dir.path()));
assert!(
result.is_err(),
"export must refuse an OASIS-invalid advisory"
);
let expected = dir
.path()
.join("2026")
.join("003")
.join("ndaal-sa-2026-003.json");
assert!(
!expected.exists(),
"no advisory bytes must be written when the OASIS gate fails"
);
}
#[test]
fn import_admits_a_conformant_advisory() {
let dir = tempfile::tempdir().expect("tmpdir");
std::fs::write(dir.path().join("good.json"), GOOD).expect("write");
let storage = CsafStorage::open_temp().expect("storage");
let result = import::import_directory(dir.path(), &storage).expect("import runs");
assert_eq!(result.imported, 1, "conformant advisory must be imported");
assert_eq!(result.skipped, 0, "errors: {:?}", result.errors);
}
#[test]
fn import_admits_oasis_imperfect_advisory_soft_gate() {
let dir = tempfile::tempdir().expect("tmpdir");
let bad = GOOD.replace("vulnConfidentialityImpact", "confidentialityImpact");
std::fs::write(dir.path().join("bad.json"), bad).expect("write");
let storage = CsafStorage::open_temp().expect("storage");
let result = import::import_directory(dir.path(), &storage).expect("import runs");
assert_eq!(result.imported, 1, "soft import admits OASIS-imperfect docs");
assert_eq!(result.skipped, 0, "errors: {:?}", result.errors);
}
#[test]
fn import_then_export_roundtrip_stays_conformant() {
let in_dir = tempfile::tempdir().expect("tmpdir");
std::fs::write(in_dir.path().join("good.json"), GOOD).expect("write");
let storage = CsafStorage::open_temp().expect("storage");
let result = import::import_directory(in_dir.path(), &storage).expect("import runs");
assert_eq!(result.imported, 1);
let doc = storage
.get_document("ndaal-sa-2026-003")
.expect("lookup")
.expect("stored");
let out_dir = tempfile::tempdir().expect("tmpdir");
export::export_document(&doc, &settings_for(out_dir.path()))
.expect("round-tripped advisory must re-export conformantly");
}