specfold-core 0.4.0

Library behind specfold: typed Markdown documents, owners, a derived graph and checks.
Documentation
//! Unit tests for `guards`.

use super::*;
use crate::config::RawConfig;

fn config(root: &std::path::Path, guard: &str) -> Config {
    let raw: RawConfig = toml::from_str(&format!("[[guards]]\n{guard}")).unwrap();
    Config::from_raw(root.to_path_buf(), raw, false)
}

#[test]
fn scans_tree() {
    let tmp = tempfile::tempdir().unwrap();
    let r = tmp.path();
    std::fs::create_dir_all(r.join("libs/open/src")).unwrap();
    std::fs::create_dir_all(r.join("services/shop")).unwrap();
    let fake_key = format!("{}{}", "AK", "IA".to_string() + &"X".repeat(16));
    std::fs::write(r.join("libs/open/src/a.rs"), format!("// built for Acme-Internal\nlet k = \"{fake_key}\";\n"))
        .unwrap();
    std::fs::write(r.join("libs/open/.env"), "X=1\n").unwrap();
    std::fs::write(r.join("services/shop/b.rs"), "acme-internal is fine here\n").unwrap();
    std::fs::write(r.join("terms.txt"), "# private\nacme-internal\n").unwrap();
    let cfg = config(
        r,
        "name = \"public\"\npaths = [\"libs/open/**\"]\nrange = \"all\"\ndeny_terms_file = \"terms.txt\"\ndeny_files = [\"**/.env*\"]\nsecrets = true\n",
    );
    let mut f = run(&cfg, &[], None).unwrap();
    f.sort_by_key(|a| (a.path.clone(), a.line));
    let got: Vec<(String, usize)> = f.iter().map(|x| (x.path.clone(), x.line)).collect();
    assert_eq!(
        got,
        [
            ("libs/open/.env".to_string(), 1),
            ("libs/open/src/a.rs".to_string(), 1),
            ("libs/open/src/a.rs".to_string(), 2)
        ]
    );
    assert!(!f[1].message.contains("acme-internal"));
}

#[test]
fn patterns_skip_urls_exemptions_and_allowed_text() {
    let tmp = tempfile::tempdir().unwrap();
    let r = tmp.path();
    std::fs::create_dir_all(r.join("pub/docs")).unwrap();
    std::fs::write(
        r.join("pub/a.md"),
        "see https://example.com/internal/x\ninternal/build.sh\nthe internal/README example\n",
    )
    .unwrap();
    std::fs::write(r.join("pub/docs/b.md"), "internal/build.sh\n").unwrap();
    let cfg = config(
        r,
        "name = \"g\"\npaths = [\"pub/**\"]\nrange = \"all\"\nallow = [\"internal/readme\"]\n[[guards.deny_regex]]\nre = 'internal/[a-z.]+'\nlabel = \"internal path\"\nnot_in_url = true\nexempt = [\"pub/docs/**\"]\nmust_hit = [\"run internal/build.sh\"]\nmust_pass = [\"https://example.com/internal/x\"]\n",
    );
    let f = run(&cfg, &[], None).unwrap();
    let got: Vec<(String, usize)> = f.iter().map(|x| (x.path.clone(), x.line)).collect();
    assert_eq!(got, [("pub/a.md".to_string(), 2)], "{f:?}");
    assert!(f[0].message.contains("internal path"));
}

#[test]
fn a_fixture_that_no_longer_holds_stops_the_run() {
    let tmp = tempfile::tempdir().unwrap();
    let cfg = config(
        tmp.path(),
        "name = \"g\"\nrange = \"all\"\n[[guards.deny_regex]]\nre = 'secret-[0-9]+'\nmust_hit = [\"secret-abc\"]\n",
    );
    let e = run(&cfg, &[], None).unwrap_err().to_string();
    assert!(e.contains("does not match its must_hit fixture"), "{e}");
}

#[test]
fn a_whole_tree_run_over_nothing_fails_and_gzip_is_read() {
    use std::io::Write;
    let tmp = tempfile::tempdir().unwrap();
    let r = tmp.path();
    let none = config(r, "name = \"g\"\npaths = [\"missing/**\"]\nrange = \"all\"\ndeny_terms = [\"x\"]\n");
    assert!(run(&none, &[], None).unwrap()[0].message.contains("scanned no file"));
    std::fs::create_dir_all(r.join("pkg")).unwrap();
    let mut gz = flate2::write::GzEncoder::new(vec![], flate2::Compression::default());
    gz.write_all(b"hello acme-internal\n").unwrap();
    std::fs::write(r.join("pkg/a.txt.gz"), gz.finish().unwrap()).unwrap();
    let cfg = config(
        r,
        "name = \"g\"\npaths = [\"pkg/**\"]\nrange = \"all\"\ndeny_terms = [\"acme-internal\"]\ndecompress = true\n",
    );
    assert_eq!(run(&cfg, &[], None).unwrap().len(), 1);
}

#[test]
fn an_empty_or_missing_published_path_is_a_hit() {
    let tmp = tempfile::tempdir().unwrap();
    let r = tmp.path();
    std::fs::create_dir_all(r.join("pub/full")).unwrap();
    std::fs::create_dir_all(r.join("pub/empty")).unwrap();
    std::fs::write(r.join("pub/full/a.md"), "fine\n").unwrap();
    let raw: RawConfig = toml::from_str(
        "[public]\npaths = [\"pub/full\", \"pub/empty\", \"pub/gone\"]\n[[guards]]\nname = \"g\"\npublic = true\nrange = \"all\"\ndeny_terms = [\"x-internal\"]\n",
    )
    .unwrap();
    let cfg = Config::from_raw(r.to_path_buf(), raw, false);
    let mut got: Vec<(String, bool)> =
        run(&cfg, &[], None).unwrap().into_iter().map(|f| (f.path, f.message.contains("not present"))).collect();
    got.sort();
    assert_eq!(got, [("pub/empty".to_string(), false), ("pub/gone".to_string(), true)]);
}