use super::*;
#[test]
fn test_taint_path_exposes_sink_callee_and_sanitizers() {
let code = r#"
const cmd = req.body.x;
child_process.exec(cmd);
"#;
let analyzer = TaintAnalyzer::new();
let paths = analyzer.analyze_intra_function(
code,
"handler",
"routes.js",
1,
crate::parsers::lightweight::Language::JavaScript,
TaintCategory::CommandInjection,
);
assert!(
!paths.is_empty(),
"Should detect taint flow from req.body.x to child_process.exec"
);
let path = &paths[0];
assert!(
!path.sink_callee_text.is_empty(),
"sink_callee_text must be non-empty; got empty string"
);
assert!(
path.sink_callee_text.contains("exec") || path.sink_callee_text.contains("child_process"),
"sink_callee_text should identify the exec sink, got: {:?}",
path.sink_callee_text
);
assert!(
path.sanitizers_on_path.is_empty(),
"No sanitizer applied — sanitizers_on_path should be empty, got: {:?}",
path.sanitizers_on_path
);
assert!(!path.is_sanitized, "Path should not be marked as sanitized");
}
#[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() {
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
);
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)); assert!(analyzer.is_sanitizer("sanitize_data", TaintCategory::Xss)); }
#[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,
sink_callee_text: "cursor.execute".to_string(),
sanitizers_on_path: vec![],
};
let safe_path = TaintPath {
is_sanitized: true,
sanitizer: Some("escape".to_string()),
sanitizers_on_path: vec!["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,
sink_callee_text: "cursor.execute".to_string(),
sanitizers_on_path: vec![],
};
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,
sink_callee_text: "b".to_string(),
sanitizers_on_path: vec![],
},
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,
sink_callee_text: "d".to_string(),
sanitizers_on_path: vec!["escape".to_string()],
},
];
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);
}