repotoire 0.8.0

Graph-powered code analysis CLI. 110 detectors for security, architecture, bus factor, and code quality.
Documentation
use super::*;

#[test]
fn test_taint_category_cwe() {
    assert_eq!(TaintCategory::SqlInjection.cwe_id(), "CWE-89");
    assert_eq!(TaintCategory::CommandInjection.cwe_id(), "CWE-78");
    assert_eq!(TaintCategory::Xss.cwe_id(), "CWE-79");
    assert_eq!(TaintCategory::Xxe.cwe_id(), "CWE-611");
}

#[test]
fn test_xxe_sinks_registered() {
    // The XXE category must register XML-specific sinks (etree.parse,
    // lxml.etree.parse, ...) and NOT borrow path-traversal sinks
    // (os.path.join, fs.readFile). This is the architectural invariant
    // that fixes the Click utils.py:490 false positive.
    let analyzer = TaintAnalyzer::new();
    let xxe_sinks = analyzer
        .get_sinks(TaintCategory::Xxe)
        .expect("Xxe category should have sinks registered");
    assert!(!xxe_sinks.is_empty(), "Xxe should have at least one sink");
    assert!(
        xxe_sinks.contains("lxml.etree.parse"),
        "Xxe sinks must include lxml.etree.parse, got: {:?}",
        xxe_sinks
    );
    assert!(
        xxe_sinks.contains("DocumentBuilder"),
        "Xxe sinks must include DocumentBuilder (Java), got: {:?}",
        xxe_sinks
    );
    // Negative: must NOT borrow path-traversal sinks.
    assert!(
        !xxe_sinks.contains("os.path.join"),
        "Xxe sinks must NOT include os.path.join (that's path traversal)"
    );
    assert!(
        !xxe_sinks.contains("path.join"),
        "Xxe sinks must NOT include path.join (that's path traversal)"
    );
}

#[test]
fn test_is_source() {
    let analyzer = TaintAnalyzer::new();

    assert!(analyzer.is_source("req.body", TaintCategory::SqlInjection));
    assert!(analyzer.is_source("request.form", TaintCategory::SqlInjection));
    assert!(analyzer.is_source("c.Param", TaintCategory::SqlInjection));
    assert!(!analyzer.is_source("random_function", TaintCategory::SqlInjection));
}

#[test]
fn test_is_sink() {
    let analyzer = TaintAnalyzer::new();

    assert!(analyzer.is_sink("cursor.execute", TaintCategory::SqlInjection));
    assert!(analyzer.is_sink("db.query", TaintCategory::SqlInjection));
    assert!(analyzer.is_sink("os.system", TaintCategory::CommandInjection));
    assert!(analyzer.is_sink("innerHTML", TaintCategory::Xss));
    assert!(!analyzer.is_sink("print", TaintCategory::SqlInjection));
}

#[test]
fn test_is_sanitizer() {
    let analyzer = TaintAnalyzer::new();

    assert!(analyzer.is_sanitizer("escapeHtml", TaintCategory::Xss));
    assert!(analyzer.is_sanitizer("shlex.quote", TaintCategory::CommandInjection));
    assert!(analyzer.is_sanitizer("validate_input", TaintCategory::SqlInjection)); // generic
    assert!(analyzer.is_sanitizer("sanitize_data", TaintCategory::Xss)); // generic
}

#[test]
fn test_taint_path_is_vulnerable() {
    let vulnerable_path = TaintPath {
        source_function: "handler".to_string(),
        source_file: "app.py".to_string(),
        source_line: 10,
        sink_function: "execute".to_string(),
        sink_file: "db.py".to_string(),
        sink_line: 20,
        category: TaintCategory::SqlInjection,
        call_chain: vec![],
        is_sanitized: false,
        sanitizer: None,
        confidence: 0.8,
    };

    let safe_path = TaintPath {
        is_sanitized: true,
        sanitizer: Some("escape".to_string()),
        ..vulnerable_path.clone()
    };

    assert!(vulnerable_path.is_vulnerable());
    assert!(!safe_path.is_vulnerable());
}

#[test]
fn test_taint_path_string() {
    let path = TaintPath {
        source_function: "handler".to_string(),
        source_file: "app.py".to_string(),
        source_line: 10,
        sink_function: "execute".to_string(),
        sink_file: "db.py".to_string(),
        sink_line: 20,
        category: TaintCategory::SqlInjection,
        call_chain: vec!["process".to_string(), "query".to_string()],
        is_sanitized: false,
        sanitizer: None,
        confidence: 0.8,
    };

    assert_eq!(path.path_string(), "handler → process → query → execute");
}

#[test]
fn test_analysis_result() {
    let paths = vec![
        TaintPath {
            source_function: "a".to_string(),
            source_file: "a.py".to_string(),
            source_line: 1,
            sink_function: "b".to_string(),
            sink_file: "b.py".to_string(),
            sink_line: 2,
            category: TaintCategory::SqlInjection,
            call_chain: vec![],
            is_sanitized: false,
            sanitizer: None,
            confidence: 0.8,
        },
        TaintPath {
            source_function: "c".to_string(),
            source_file: "c.py".to_string(),
            source_line: 3,
            sink_function: "d".to_string(),
            sink_file: "d.py".to_string(),
            sink_line: 4,
            category: TaintCategory::SqlInjection,
            call_chain: vec![],
            is_sanitized: true,
            sanitizer: Some("escape".to_string()),
            confidence: 0.8,
        },
    ];

    let result = TaintAnalysisResult::from_paths(paths);

    assert_eq!(result.vulnerable_count, 1);
    assert_eq!(result.sanitized_count, 1);
    assert!(result.has_vulnerabilities());
    assert_eq!(result.vulnerable_paths().len(), 1);
}