gruff-rs 0.1.1

Rust static analyzer and quality linter for CI: dead-code, complexity, security, secrets, and architecture rules with deterministic SARIF/JSON output and baseline support.
use super::*;

#[test]
pub(crate) fn project_model_indexes_manifest_modules_items_and_calls_deterministically() {
    let _guard = analysis_lock();
    let dir = tempdir().expect("tempdir");
    fs::create_dir_all(dir.path().join("src")).expect("src dir");
    fs::write(dir.path().join("README.md"), "# Fixture\n").expect("readme write");
    fs::write(
        dir.path().join("Cargo.toml"),
        r#"[package]
name = "project-model-fixture"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1"
regex = { version = "1", default-features = false }

[dev-dependencies]
tempfile = "3"
"#,
    )
    .expect("manifest write");
    fs::write(
        dir.path().join("Cargo.lock"),
        r#"# This file is automatically @generated by Cargo.
version = 3

[[package]]
name = "project-model-fixture"
version = "0.1.0"
dependencies = [
 "serde",
]

[[package]]
name = "serde"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#,
    )
    .expect("lockfile write");
    fs::write(
        dir.path().join("src/lib.rs"),
        r#"pub mod api {
    pub struct Public;

    fn helper() {}

    pub fn call_helper() {
        helper();
    }

    impl Public {
        pub fn new() -> Self {
            Public
        }
    }
}

mod external;
"#,
    )
    .expect("lib write");
    fs::write(
        dir.path().join("src/external.rs"),
        "pub fn visible_external() {}\n",
    )
    .expect("external write");

    let first = project_context_for_test(dir.path());
    let second = project_context_for_test(dir.path());

    assert_eq!(first.manifest, second.manifest);
    assert_eq!(first.lockfile, second.lockfile);
    assert_eq!(first.modules, second.modules);
    assert_eq!(first.items, second.items);
    assert_eq!(first.call_names, second.call_names);
    assert!(first.diagnostics.is_empty(), "{:?}", first.diagnostics);

    let manifest = first.manifest.as_ref().expect("manifest summary");
    assert_eq!(manifest.file_path, "Cargo.toml");
    assert_eq!(
        manifest.package_name.as_deref(),
        Some("project-model-fixture")
    );
    assert!(manifest.dependencies.iter().any(|dependency| {
        dependency.section == "dependencies"
            && dependency.name == "serde"
            && dependency.requirement.as_deref() == Some("1")
    }));
    assert!(manifest.dependencies.iter().any(|dependency| {
        dependency.section == "dev-dependencies"
            && dependency.name == "tempfile"
            && dependency.requirement.as_deref() == Some("3")
    }));

    let lockfile = first.lockfile.as_ref().expect("lockfile summary");
    assert_eq!(lockfile.file_path, "Cargo.lock");
    assert!(lockfile
        .packages
        .iter()
        .any(|package| package.name == "serde" && package.version == "1.0.0"));

    assert!(first.modules.iter().any(|module| {
        module.file_path == "src/lib.rs"
            && module.module_path == "api"
            && module.public
            && module.inline
    }));
    assert!(first.modules.iter().any(|module| {
        module.file_path == "src/lib.rs"
            && module.module_path == "external"
            && !module.public
            && !module.inline
    }));
    assert!(first.items.iter().any(|item| {
        item.file_path == "src/lib.rs"
            && item.module_path == "api"
            && item.name == "Public"
            && item.kind == "struct"
            && item.public
    }));
    assert!(first.items.iter().any(|item| {
        item.file_path == "src/lib.rs"
            && item.module_path == "api"
            && item.name == "helper"
            && item.kind == "function"
            && !item.public
    }));
    assert!(first
        .call_names
        .iter()
        .any(|call| { call.file_path == "src/lib.rs" && call.name == "helper" && call.line == 7 }));
}

#[test]
pub(crate) fn project_model_handles_missing_and_invalid_cargo_metadata() {
    let _guard = analysis_lock();
    let missing_dir = tempdir().expect("tempdir");
    fs::create_dir_all(missing_dir.path().join("src")).expect("src dir");
    fs::write(missing_dir.path().join("README.md"), "# Fixture\n").expect("readme write");
    fs::write(missing_dir.path().join("src/lib.rs"), "pub fn ready() {}\n").expect("lib write");
    let missing = project_context_for_test(missing_dir.path());
    assert!(missing.manifest.is_none());
    assert!(missing.lockfile.is_none());
    assert!(missing.diagnostics.is_empty(), "{:?}", missing.diagnostics);

    let invalid_manifest_dir = tempdir().expect("tempdir");
    fs::write(invalid_manifest_dir.path().join("README.md"), "# Fixture\n").expect("readme write");
    fs::write(invalid_manifest_dir.path().join("Cargo.toml"), "[package\n")
        .expect("manifest write");
    let invalid_manifest = project_context_for_test(invalid_manifest_dir.path());
    assert!(invalid_manifest.manifest.is_none());
    assert_eq!(invalid_manifest.diagnostics.len(), 1);
    assert_eq!(
        invalid_manifest.diagnostics[0].diagnostic_type,
        "manifest-parse-error"
    );
    assert_eq!(
        invalid_manifest.diagnostics[0].file_path.as_deref(),
        Some("Cargo.toml")
    );
    assert!(!invalid_manifest.diagnostics[0]
        .message
        .to_ascii_lowercase()
        .contains("parser"));

    let invalid_lock_dir = tempdir().expect("tempdir");
    fs::write(invalid_lock_dir.path().join("README.md"), "# Fixture\n").expect("readme write");
    fs::write(
        invalid_lock_dir.path().join("Cargo.toml"),
        r#"[package]
name = "invalid-lock-fixture"
version = "0.1.0"
"#,
    )
    .expect("manifest write");
    fs::write(invalid_lock_dir.path().join("Cargo.lock"), "[package\n").expect("lockfile write");
    let invalid_lock = project_context_for_test(invalid_lock_dir.path());
    assert!(invalid_lock.manifest.is_some());
    assert_eq!(invalid_lock.diagnostics.len(), 1);
    assert_eq!(
        invalid_lock.diagnostics[0].diagnostic_type,
        "lockfile-parse-error"
    );
    assert_eq!(
        invalid_lock.diagnostics[0].file_path.as_deref(),
        Some("Cargo.lock")
    );
}

#[test]
pub(crate) fn missing_readme_is_project_scoped() {
    let _guard = analysis_lock();
    let dir = tempdir().expect("tempdir");
    fs::write(dir.path().join("lib.rs"), "pub fn documented() {}\n").expect("fixture write");

    let missing = analyse_project_paths(dir.path(), vec![PathBuf::from("lib.rs")]);
    fs::write(dir.path().join("README.md"), "# Temporary fixture\n").expect("readme write");
    let present = analyse_project_paths(dir.path(), vec![PathBuf::from("lib.rs")]);

    assert_has_rule(&missing, "docs.missing-readme");
    assert_missing_rule(&present, "docs.missing-readme");
}