1
0
Fork 0
mirror of https://github.com/imjasonh/rustvulncheck synced 2026-07-13 08:29:18 +00:00
rustvulncheck/tests/integration_test.rs
Claude a2db0d4d15
Add syn-based type tracking and golden codebase integration tests
Type tracking (type_tracker.rs):
- Uses syn AST parsing to resolve variable types from function parameters,
  let bindings with annotations, constructor calls (Type::new()), struct
  literals, builder patterns, and closure parameters
- Integrated into scanner to promote method-call matches from Medium to
  High confidence when the receiver's type is known

Golden integration tests:
- vulnerable_project: 4 vulnerable deps, 3 with reachable calls across
  multiple files (qualified calls, typed method calls, constructor-inferred
  types). Verifies regex dep is listed but Compiler::compile is NOT reachable.
- safe_project: patched deps (hyper, smallvec) excluded, vulnerable deps
  (tokio, regex) listed but no symbols reachable. Reports POSSIBLY SAFE.
- clean_project: no vulnerable deps at all. Reports CLEAN.

37 tests total (33 unit + 4 integration), all passing.

https://claude.ai/code/session_011sdj18ZvQYbDVwEKqrWDj1
2026-03-25 12:44:13 +00:00

230 lines
7.6 KiB
Rust

use std::path::PathBuf;
use std::process::Command;
fn fixtures() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
fn run_analyze(project: &str, db: &str) -> std::process::Output {
let f = fixtures();
Command::new(env!("CARGO_BIN_EXE_cargo-deep-audit"))
.args([
"analyze",
"--project",
f.join(project).to_str().unwrap(),
"--db",
f.join(db).to_str().unwrap(),
])
.output()
.expect("failed to run binary")
}
// =============================================================================
// Phase 1: Enrichment pipeline tests
// =============================================================================
#[test]
fn test_parse_fixture_advisories() {
let f = fixtures();
let output = Command::new(env!("CARGO_BIN_EXE_cargo-deep-audit"))
.args([
"enrich",
"--advisory-db",
f.to_str().unwrap(),
"--limit",
"10",
"--include-all",
"--output",
"/tmp/test_vuln_db.json",
])
.output()
.expect("failed to run binary");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("STDOUT:\n{stdout}");
println!("STDERR:\n{stderr}");
assert!(
stdout.contains("Found 3 total advisories"),
"Expected 3 advisories, got: {stdout}"
);
assert!(stdout.contains("RUSTSEC-2024-0001"));
assert!(stdout.contains("RUSTSEC-2024-0002"));
assert!(stdout.contains("RUSTSEC-2024-0003"));
assert!(stdout.contains("Wrote enriched database to /tmp/test_vuln_db.json"));
let json_content = std::fs::read_to_string("/tmp/test_vuln_db.json").unwrap();
let db: serde_json::Value = serde_json::from_str(&json_content).unwrap();
assert!(!db["entries"].as_array().unwrap().is_empty());
}
// =============================================================================
// Phase 2: Golden codebase integration tests
// =============================================================================
/// Golden test: vulnerable project with multiple call patterns.
///
/// The vulnerable_project fixture has:
/// - hyper 0.14.27 (vulnerable, patched >= 1.0.0) with calls to Client::encode and Decoder::decode
/// - smallvec 1.10.0 (vulnerable, patched >= 1.11.0) with calls to SmallVec::insert_many
/// - tokio 1.37.0 (vulnerable, patched >= 1.38.0) with calls to JoinHandle::abort
/// - regex 1.9.6 (vulnerable, patched >= 1.10.0) — present but Compiler::compile is NOT called
#[test]
fn golden_vulnerable_project() {
let output = run_analyze("vulnerable_project", "golden_vuln_db.json");
let stdout = String::from_utf8_lossy(&output.stdout);
println!("STDOUT:\n{stdout}");
// --- Should exit non-zero (vulnerable) ---
assert!(
!output.status.success(),
"Should exit with non-zero when vulnerabilities found"
);
assert!(
stdout.contains("VULNERABLE"),
"Should report VULNERABLE status:\n{stdout}"
);
// --- Should detect all four vulnerable dependencies ---
assert!(stdout.contains("RUSTSEC-2024-0001"), "hyper advisory missing:\n{stdout}");
assert!(stdout.contains("RUSTSEC-2024-0010"), "smallvec advisory missing:\n{stdout}");
assert!(stdout.contains("RUSTSEC-2024-0020"), "tokio advisory missing:\n{stdout}");
assert!(stdout.contains("RUSTSEC-2024-0030"), "regex advisory missing:\n{stdout}");
// --- Should find reachable calls for hyper, smallvec, and tokio ---
assert!(
stdout.contains("Client::encode"),
"Should find Client::encode call:\n{stdout}"
);
assert!(
stdout.contains("insert_many"),
"Should find SmallVec::insert_many call:\n{stdout}"
);
assert!(
stdout.contains("abort"),
"Should find JoinHandle::abort call:\n{stdout}"
);
// --- regex Compiler::compile is NOT called in user code ---
// It's an internal symbol; user code only calls Regex::new.
// It should appear in "Vulnerable Dependencies" but NOT in "Reachable" findings.
let reachable_section = stdout
.split("Reachable Vulnerable Symbols")
.nth(1)
.unwrap_or("");
assert!(
!reachable_section.contains("Compiler::compile"),
"Compiler::compile should NOT appear as reachable:\n{stdout}"
);
// --- Should scan multiple source files ---
assert!(
stdout.contains("3 source files"),
"Should scan all 3 .rs files:\n{stdout}"
);
}
/// Golden test: safe project where vulnerabilities exist in deps but aren't reachable.
///
/// The safe_project fixture has:
/// - hyper 1.2.0 (PATCHED — version >= 1.0.0) → should not appear at all
/// - smallvec 1.11.0 (PATCHED — version >= 1.11.0) → should not appear at all
/// - tokio 1.37.0 (vulnerable) but abort() is NEVER called → dep listed, no findings
/// - regex 1.9.6 (vulnerable) but Compiler::compile is NEVER called → dep listed, no findings
#[test]
fn golden_safe_project() {
let output = run_analyze("safe_project", "golden_vuln_db.json");
let stdout = String::from_utf8_lossy(&output.stdout);
println!("STDOUT:\n{stdout}");
// --- Should exit zero (safe / not reachable) ---
assert!(
output.status.success(),
"Should exit successfully when no reachable vulns:\n{stdout}"
);
// --- Patched deps should NOT appear ---
assert!(
!stdout.contains("RUSTSEC-2024-0001"),
"hyper is patched (1.2.0), should not appear:\n{stdout}"
);
assert!(
!stdout.contains("RUSTSEC-2024-0010"),
"smallvec is patched (1.11.0), should not appear:\n{stdout}"
);
// --- Vulnerable but unreachable deps SHOULD appear in dep list ---
assert!(
stdout.contains("RUSTSEC-2024-0020"),
"tokio advisory should be listed (vulnerable version):\n{stdout}"
);
assert!(
stdout.contains("RUSTSEC-2024-0030"),
"regex advisory should be listed (vulnerable version):\n{stdout}"
);
// --- But no reachable findings ---
assert!(
stdout.contains("POSSIBLY SAFE"),
"Should report POSSIBLY SAFE (deps present but not reachable):\n{stdout}"
);
assert!(
!stdout.contains("Reachable Vulnerable Symbols"),
"Should NOT have reachable symbols section:\n{stdout}"
);
}
/// Minimal test: project with zero vulnerable dependencies.
#[test]
fn golden_clean_project() {
let tmp = tempfile::tempdir().unwrap();
let src_dir = tmp.path().join("src");
std::fs::create_dir_all(&src_dir).unwrap();
std::fs::write(
tmp.path().join("Cargo.lock"),
r#"version = 3
[[package]]
name = "clean-app"
version = "0.1.0"
[[package]]
name = "serde"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#,
)
.unwrap();
std::fs::write(
src_dir.join("main.rs"),
"fn main() { println!(\"Hello\"); }\n",
)
.unwrap();
let f = fixtures();
let output = Command::new(env!("CARGO_BIN_EXE_cargo-deep-audit"))
.args([
"analyze",
"--project",
tmp.path().to_str().unwrap(),
"--db",
f.join("golden_vuln_db.json").to_str().unwrap(),
])
.output()
.expect("failed to run binary");
let stdout = String::from_utf8_lossy(&output.stdout);
println!("STDOUT:\n{stdout}");
assert!(
stdout.contains("CLEAN"),
"Should report CLEAN:\n{stdout}"
);
assert!(
output.status.success(),
"Should exit successfully when clean"
);
}