secunit-core 0.6.1

Registry, evidence, hashing, and verification primitives for secunit.
Documentation
//! Validate every YAML/JSON file under `docs/examples/` against the
//! published schemas. If an example drifts from the schema (or vice
//! versa) this test fails loudly.

use std::path::{Path, PathBuf};

use secunit_core::schemas::Schema;
use serde_json::Value;

fn examples_root() -> PathBuf {
    // CARGO_MANIFEST_DIR is crates/secunit-core; repo root is two up.
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../..")
        .join("docs/examples")
        .canonicalize()
        .expect("docs/examples must exist relative to the secunit-core crate")
}

fn read_yaml(path: &Path) -> Value {
    let text = std::fs::read_to_string(path).unwrap_or_else(|e| panic!("read {path:?}: {e}"));
    serde_yaml::from_str(&text).unwrap_or_else(|e| panic!("parse YAML {path:?}: {e}"))
}

fn read_json(path: &Path) -> Value {
    let text = std::fs::read_to_string(path).unwrap_or_else(|e| panic!("read {path:?}: {e}"));
    serde_json::from_str(&text).unwrap_or_else(|e| panic!("parse JSON {path:?}: {e}"))
}

fn assert_valid(schema: Schema, path: &Path, value: &Value) {
    let errs = schema.validate(value);
    assert!(
        errs.is_empty(),
        "{path:?} failed {schema:?} schema:\n  {}",
        errs.join("\n  ")
    );
}

#[test]
fn controls_validate() {
    let root = examples_root().join("controls");
    let mut count = 0;
    for entry in std::fs::read_dir(&root).unwrap() {
        let path = entry.unwrap().path();
        if path.extension().and_then(|s| s.to_str()) != Some("yaml") {
            continue;
        }
        assert_valid(Schema::Control, &path, &read_yaml(&path));
        count += 1;
    }
    assert!(count > 0, "no control examples found under {root:?}");
}

#[test]
fn inventory_validates() {
    let path = examples_root().join("inventory.yaml");
    assert_valid(Schema::Inventory, &path, &read_yaml(&path));
}

#[test]
fn schedule_validates() {
    let path = examples_root().join("schedule.yaml");
    assert_valid(Schema::Schedule, &path, &read_yaml(&path));
}

#[test]
fn state_validates() {
    let path = examples_root().join("state.json");
    assert_valid(Schema::State, &path, &read_json(&path));
}

#[test]
fn manifests_validate() {
    let root = examples_root().join("evidence");
    let mut count = 0;
    for entry in walkdir::WalkDir::new(&root) {
        let entry = entry.unwrap();
        if entry.file_name() == "manifest.json" {
            assert_valid(Schema::Manifest, entry.path(), &read_json(entry.path()));
            count += 1;
        }
    }
    assert!(count > 0, "no manifest examples found under {root:?}");
}

#[test]
fn risk_index_validates() {
    let path = examples_root().join("risks/index.json");
    assert_valid(Schema::RiskIndex, &path, &read_json(&path));
}

#[test]
fn risk_event_logs_validate() {
    let root = examples_root().join("risks");
    let mut lines = 0;
    for entry in walkdir::WalkDir::new(&root) {
        let entry = entry.unwrap();
        if entry.file_name() != "events.jsonl" {
            continue;
        }
        let text = std::fs::read_to_string(entry.path())
            .unwrap_or_else(|e| panic!("read {:?}: {e}", entry.path()));
        for line in text.lines().filter(|l| !l.trim().is_empty()) {
            let value: Value = serde_json::from_str(line)
                .unwrap_or_else(|e| panic!("parse event in {:?}: {e}", entry.path()));
            assert_valid(Schema::RiskEvent, entry.path(), &value);
            lines += 1;
        }
    }
    assert!(lines > 0, "no risk event examples found under {root:?}");
}