infigraph-core 1.5.5

AST-powered code analysis framework — parser, graph, diff, and analysis engine
Documentation
use infigraph_core::check::{load_config, run_checks, CheckConfig, CheckSelection, CheckStatus};
use infigraph_core::graph::GraphStore;

fn empty_store() -> (tempfile::TempDir, GraphStore) {
    let dir = tempfile::TempDir::new().unwrap();
    let store = GraphStore::open(&dir.path().join("graph")).unwrap();
    (dir, store)
}

#[test]
fn test_check_config_parsing() {
    let dir = tempfile::TempDir::new().unwrap();
    let config_path = dir.path().join("check.toml");
    std::fs::write(
        &config_path,
        r#"
[security]
enabled = true
max_critical = 0
max_high = 5

[complexity]
enabled = true
threshold = 20
max_violations = 3

[dead_code]
enabled = false
max_dead = 100

[vulnerabilities]
enabled = true
max_critical = 0
max_high = 10
"#,
    )
    .unwrap();

    let cfg = load_config(&config_path).unwrap();
    assert!(cfg.security.enabled);
    assert_eq!(cfg.security.max_high, 5);
    assert_eq!(cfg.complexity.threshold, 20);
    assert_eq!(cfg.complexity.max_violations, 3);
    assert!(!cfg.dead_code.enabled);
    assert_eq!(cfg.dead_code.max_dead, 100);
    assert!(cfg.vulnerabilities.enabled);
    assert_eq!(cfg.vulnerabilities.max_high, 10);
}

#[test]
fn test_check_config_defaults() {
    let dir = tempfile::TempDir::new().unwrap();
    let missing = dir.path().join("nonexistent.toml");
    let cfg = load_config(&missing).unwrap();

    assert!(cfg.security.enabled);
    assert_eq!(cfg.security.max_critical, 0);
    assert_eq!(cfg.security.max_high, 0);
    assert!(cfg.complexity.enabled);
    assert_eq!(cfg.complexity.threshold, 15);
    assert!(cfg.dead_code.enabled);
}

#[test]
fn test_check_security_pass() {
    let dir = tempfile::TempDir::new().unwrap();
    std::fs::write(dir.path().join("safe.py"), "print('hello world')\n").unwrap();

    let (_tmp, store) = empty_store();
    let cfg = CheckConfig::default();
    let sel = CheckSelection::all();
    let results = run_checks(dir.path(), &cfg, &store, &sel);

    let sec = results.iter().find(|r| r.name == "security");
    if let Some(s) = sec {
        assert_eq!(
            s.status,
            CheckStatus::Pass,
            "safe code should pass: {}",
            s.summary
        );
    }
}

#[test]
fn test_check_security_fail() {
    let dir = tempfile::TempDir::new().unwrap();
    std::fs::write(
        dir.path().join("vuln.py"),
        "import os\npassword = 'hardcoded_secret'\nos.system(user_input)\neval(data)\n",
    )
    .unwrap();

    let (_tmp, store) = empty_store();
    let mut cfg = CheckConfig::default();
    cfg.security.max_critical = 0;
    cfg.security.max_high = 0;
    let sel = CheckSelection {
        security: true,
        complexity: false,
        dead_code: false,
        vulnerabilities: false,
    };
    let results = run_checks(dir.path(), &cfg, &store, &sel);

    let sec = results.iter().find(|r| r.name == "security").unwrap();
    assert_eq!(
        sec.status,
        CheckStatus::Fail,
        "vuln code should fail: {}",
        sec.summary
    );
}

#[test]
fn test_check_complexity_pass() {
    let (_tmp, store) = empty_store();
    let cfg = CheckConfig::default();
    let sel = CheckSelection {
        security: false,
        complexity: true,
        dead_code: false,
        vulnerabilities: false,
    };
    let results = run_checks(std::path::Path::new("."), &cfg, &store, &sel);

    let cx = results.iter().find(|r| r.name == "complexity");
    if let Some(c) = cx {
        assert_eq!(
            c.status,
            CheckStatus::Pass,
            "empty graph should pass complexity"
        );
    }
}

#[test]
fn test_check_selection_from_csv() {
    let sel = CheckSelection::from_csv("security,complexity");
    assert!(sel.security);
    assert!(sel.complexity);
    assert!(!sel.dead_code);
    assert!(!sel.vulnerabilities);

    let sel2 = CheckSelection::from_csv("sec,vulns,dead-code");
    assert!(sel2.security);
    assert!(!sel2.complexity);
    assert!(sel2.dead_code);
    assert!(sel2.vulnerabilities);
}

#[test]
fn test_check_all_categories_have_rules() {
    use infigraph_core::security::Category;

    let categories = [
        Category::SqlInjection,
        Category::CommandInjection,
        Category::PathTraversal,
        Category::XssRisk,
        Category::InsecureDeserialization,
        Category::WeakCrypto,
        Category::HardcodedSecret,
        Category::InsecureRandom,
    ];

    let dir = tempfile::TempDir::new().unwrap();
    std::fs::write(dir.path().join("empty.py"), "").unwrap();

    for cat in &categories {
        assert!(
            !format!("{:?}", cat).is_empty(),
            "category {:?} should be representable",
            cat
        );
    }
}