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

nordisk/gunnar

xtask/tests/cve_report.rs

//! The CVE gate's verdict as data (`xtask/src/cve.rs`): it is clean only when every
//! sub-check exited 0, the report names each failure and the advisory database's age,
//! and the age is read from the newest fetch of any database under `$CARGO_HOME`.
//!
//! Its own test binary: one test points `$CARGO_HOME` at a scratch directory.

use std::path::PathBuf;
use std::time::Duration;

use xtask::cve::{advisory_db_age, advisory_db_dir, Check, CveReport};

fn check(name: &'static str, exit: Option<i32>, output: &str) -> Check {
    Check {
        name,
        exit,
        output: output.to_string(),
    }
}

fn report(adv: Option<i32>, bans: Option<i32>, sources: Option<i32>, output: &str) -> CveReport {
    CveReport {
        advisories: check("advisories", adv, output),
        bans: check("bans", bans, ""),
        sources: check("sources", sources, ""),
        db_age: Some(Duration::from_secs(3 * 24 * 60 * 60 + 5)),
        offline: true,
    }
}

const FIRED: &str = "error[vulnerability]: a flaw\n  ┌─ Cargo.lock:1:1\n  │\n  ├ ID: RUSTSEC-2024-0421\n  ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2024-0421\n";

#[test]
fn only_three_clean_exits_are_a_clean_gate() {
    assert!(report(Some(0), Some(0), Some(0), "").is_clean());
    assert!(!report(Some(1), Some(0), Some(0), FIRED).is_clean());
    assert!(!report(Some(0), Some(2), Some(0), "").is_clean());
    assert!(
        !report(Some(0), Some(0), None, "").is_clean(),
        "a killed check is not a pass"
    );
    assert!(!check("x", None, "").passed());
}

#[test]
fn the_report_names_each_failure_the_firing_ids_and_the_database() {
    let page = report(Some(1), Some(0), None, FIRED).render();
    assert!(page.contains("  advisories: FAILED (exit 1)\n"), "{page}");
    assert!(page.contains("  bans:       ok\n"), "{page}");
    assert!(
        page.contains("  sources:    FAILED (killed, no exit code)\n"),
        "{page}"
    );
    assert!(
        page.contains("  advisories firing: RUSTSEC-2024-0421\n"),
        "{page}"
    );
    assert!(
        page.contains("  advisory db: 3 day(s) old, read offline\n"),
        "{page}"
    );

    let mut clean = report(Some(0), Some(0), Some(0), "");
    clean.db_age = None;
    clean.offline = false;
    let page = clean.render();
    assert!(!page.contains("firing"), "{page}");
    assert!(
        page.contains("advisory db: NOT PRESENT, fetched for this run"),
        "{page}"
    );
}

struct Scratch(PathBuf);

impl Drop for Scratch {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}

/// The age is the newest `FETCH_HEAD` (else `HEAD`) of any database; no database is
/// `None`, never a fresh age.
#[test]
fn the_database_age_is_the_newest_fetch_under_cargo_home() {
    let home = std::env::temp_dir().join(format!("gunnar-cve-home-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&home);
    let _s = Scratch(home.clone());
    std::env::set_var("CARGO_HOME", &home);
    assert_eq!(advisory_db_dir(), home.join("advisory-dbs"));
    assert_eq!(advisory_db_age(), None, "no database directory at all");

    std::fs::create_dir_all(home.join("advisory-dbs/empty-0000")).unwrap();
    assert_eq!(advisory_db_age(), None, "a database with no git markers");

    let old = home.join("advisory-dbs/old-1111/.git");
    std::fs::create_dir_all(&old).unwrap();
    std::fs::write(old.join("HEAD"), "ref: refs/heads/main\n").unwrap();
    let past = std::time::SystemTime::now() - Duration::from_secs(30 * 24 * 60 * 60);
    std::fs::File::options()
        .write(true)
        .open(old.join("HEAD"))
        .unwrap()
        .set_modified(past)
        .unwrap();
    let age = advisory_db_age().expect("the cloned database");
    assert!(age >= Duration::from_secs(29 * 24 * 60 * 60), "{age:?}");

    let fresh = home.join("advisory-dbs/fresh-2222/.git");
    std::fs::create_dir_all(&fresh).unwrap();
    std::fs::write(fresh.join("FETCH_HEAD"), "x\n").unwrap();
    let age = advisory_db_age().expect("the fetched database");
    assert!(
        age < Duration::from_secs(60 * 60),
        "the newest database wins: {age:?}"
    );
    std::env::remove_var("CARGO_HOME");
}