Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

mikronova/modgunn

tests/cli_zone.rs

//! **ZONE routing tests** — the shared constellation-wide environment axis (the ONE
//! `nornir_zone::Zone` leaf, consumed not twinned, L5) threaded through modgunn's
//! CLI `--zone demo|test|prod` into its warehouse **data namespace**.
//!
//! The contract these pin (RED-when-broken):
//! * modgunn's OWN data lands in the selected zone's isolated namespace
//!   `Zone::namespace("modgunn")` = `modgunn_<zone>` (so a `test` populate never
//!   touches `demo`/`prod`);
//! * `--zone` defaults to `demo`, so every pre-zone invocation is unchanged (additive);
//! * the pre-zone [`Warehouse::open`] still addresses the legacy `nornir` namespace
//!   (existing library consumers unaffected);
//! * an unknown `--zone` value is a clear hard error, never a silent wrong environment.
//!
//! Gated on `warehouse` (the binary's `required-features`): without it the CLI is not
//! built and this crate compiles to nothing.
#![cfg(feature = "warehouse")]

use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

use modgunn::core::Severity;
use modgunn::scan::Advisory;
use modgunn::warehouse::{Warehouse, Zone};

/// A unique scratch catalog root (skade is single-writer per catalog).
fn temp_root(tag: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let dir = std::env::temp_dir().join(format!("modgunn-zone-{tag}-{nanos}"));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

/// Run the compiled `modgunn` binary with `args`; return `(stdout, stderr, code)`.
fn run(args: &[&str]) -> (String, String, i32) {
    let out = Command::new(env!("CARGO_BIN_EXE_modgunn"))
        .args(args)
        .output()
        .expect("spawn modgunn binary");
    (
        String::from_utf8_lossy(&out.stdout).into_owned(),
        String::from_utf8_lossy(&out.stderr).into_owned(),
        out.status.code().unwrap_or(-1),
    )
}

/// **RED-when-broken: the zone threads through to the data namespace.**
/// `open_in_zone(Test)` MUST route modgunn's own tables to `modgunn_test`, `Demo` to
/// `modgunn_demo` (the default), `Prod` to `modgunn_prod` — and each is distinct, so
/// two zones never collide. The legacy `open` MUST stay on the pre-zone `nornir`
/// namespace (existing consumers unchanged). If the zone failed to thread, these
/// namespaces would coincide (isolation lost) or drift from `modgunn_<zone>`.
#[test]
fn zone_routes_modgunn_namespace() {
    let root = temp_root("ns");

    let demo = Warehouse::open_in_zone(&root, Zone::Demo).unwrap();
    assert_eq!(demo.namespace(), "modgunn_demo", "Demo → modgunn_demo");
    drop(demo);

    let test = Warehouse::open_in_zone(&root, Zone::Test).unwrap();
    assert_eq!(test.namespace(), "modgunn_test", "Test → modgunn_test");
    drop(test);

    let prod = Warehouse::open_in_zone(&root, Zone::Prod).unwrap();
    assert_eq!(prod.namespace(), "modgunn_prod", "Prod → modgunn_prod");
    drop(prod);

    // The pre-zone entry stays on the legacy shared namespace (byte-for-byte additive).
    let legacy = Warehouse::open(&root).unwrap();
    assert_eq!(
        legacy.namespace(),
        "nornir",
        "legacy open keeps the nornir namespace"
    );
    drop(legacy);

    // The leaf's own default is Demo, so an absent `--zone` lands in modgunn_demo.
    assert_eq!(Zone::default(), Zone::Demo);
    assert_eq!(Zone::default().namespace("modgunn"), "modgunn_demo");

    std::fs::remove_dir_all(&root).ok();
}

/// **RED-when-broken: the CLI `--zone` ISOLATES modgunn's data end-to-end.**
/// Seed advisories into the *test* zone in-process, then drive the real binary:
/// `warehouse stats --zone test` MUST see them (the CLI threaded `--zone test` into
/// the `modgunn_test` namespace), while the DEFAULT (no `--zone`, i.e. demo) MUST see
/// zero — the two zones are isolated. If the CLI ignored `--zone`, the test-zone read
/// would come back empty (RED) or the demo read would leak the test data (RED).
#[test]
fn cli_zone_isolates_advisory_data() {
    let root = temp_root("iso");
    let root_s = root.to_str().unwrap().to_string();

    // Populate ONLY the test zone (modgunn_test namespace).
    {
        let wh = Warehouse::open_in_zone(&root, Zone::Test).unwrap();
        wh.ensure_modgunn_tables().unwrap();
        wh.append_advisories(
            "rev-test",
            &[Advisory::basic(
                "CVE-2023-9999",
                "cargo",
                "openssl",
                vec!["0.10.55".into()],
                Severity::High,
                "test-zone advisory",
            )],
        )
        .unwrap();
    } // drop releases the writer lock before the subprocess opens the same root.

    // The CLI, told `--zone test`, reads the SAME modgunn_test namespace → sees it.
    let (out_test, err_test, code_test) = run(&[
        "warehouse",
        "stats",
        "--warehouse",
        &root_s,
        "--zone",
        "test",
    ]);
    assert_eq!(
        code_test, 0,
        "stats --zone test exits 0 (stderr: {err_test})"
    );
    assert!(
        out_test.contains("1 advisories"),
        "the test zone sees its own advisory: {out_test}"
    );

    // The DEFAULT (demo) zone is a DIFFERENT namespace (modgunn_demo) → sees nothing.
    let (out_demo, _e, code_demo) = run(&["warehouse", "stats", "--warehouse", &root_s]);
    assert_eq!(code_demo, 0, "default (demo) stats exits 0");
    assert!(
        out_demo.contains("0 advisories"),
        "the demo zone is isolated from the test zone's data: {out_demo}"
    );

    // Explicit `--zone demo` matches the default (also isolated / empty).
    let (out_demo2, _e, _c) = run(&[
        "warehouse",
        "stats",
        "--warehouse",
        &root_s,
        "--zone",
        "demo",
    ]);
    assert!(
        out_demo2.contains("0 advisories"),
        "explicit demo == default: {out_demo2}"
    );

    std::fs::remove_dir_all(&root).ok();
}

/// **RED-when-broken: an unknown `--zone` value is a clear hard error.** A typo'd zone
/// must NOT silently fall through to demo (which could hide data in the wrong
/// environment): the CLI exits non-zero and names the valid zones.
#[test]
fn cli_rejects_unknown_zone() {
    let root = temp_root("badzone");
    let root_s = root.to_str().unwrap().to_string();

    let (_out, err, code) = run(&[
        "warehouse",
        "stats",
        "--warehouse",
        &root_s,
        "--zone",
        "staging-typo",
    ]);
    assert_ne!(
        code, 0,
        "an unknown --zone fails (never a silent demo fall-through)"
    );
    assert!(
        err.contains("unknown --zone"),
        "error names the bad flag: {err}"
    );
    assert!(
        err.contains("demo") && err.contains("test") && err.contains("prod"),
        "error lists the valid zones: {err}"
    );

    std::fs::remove_dir_all(&root).ok();
}