#[test]
fn test_gather_security_evidence_no_vulnerabilities() {
let gatherer = EvidenceGatherer::new();
let claim = Claim {
category: ClaimCategory::Security,
text: "zero vulnerabilities".to_string(),
is_absolute: true,
numeric_value: None,
issue_number: None,
has_scope_qualifier: false,
scope: None,
};
let context = RepositoryContext::new_mock().with_vulnerabilities(0);
let evidence = gatherer.gather_evidence(&claim, &context);
let audit_evidence = evidence
.iter()
.find(|e| e.source == EvidenceSource::CargoAudit)
.expect("Should have cargo audit evidence");
assert!(audit_evidence.supports_claim);
assert!(audit_evidence.details.contains("No vulnerabilities found"));
}
#[test]
fn test_gather_security_evidence_with_vulnerabilities() {
let gatherer = EvidenceGatherer::new();
let claim = Claim {
category: ClaimCategory::Security,
text: "zero vulnerabilities".to_string(),
is_absolute: true,
numeric_value: None,
issue_number: None,
has_scope_qualifier: false,
scope: None,
};
let context = RepositoryContext::new_mock().with_vulnerabilities(3);
let evidence = gatherer.gather_evidence(&claim, &context);
let audit_evidence = evidence
.iter()
.find(|e| e.source == EvidenceSource::CargoAudit)
.expect("Should have cargo audit evidence");
assert!(!audit_evidence.supports_claim);
assert!(audit_evidence.details.contains("3 vulnerabilities found"));
}
#[test]
fn test_gather_security_evidence_with_security_fixes() {
let gatherer = EvidenceGatherer::new();
let claim = Claim {
category: ClaimCategory::Security,
text: "zero vulnerabilities".to_string(),
is_absolute: true,
numeric_value: None,
issue_number: None,
has_scope_qualifier: false,
scope: None,
};
let context = RepositoryContext::new_mock()
.with_subsequent_commits(vec!["security: patch CVE-2024-1234".to_string()]);
let evidence = gatherer.gather_evidence(&claim, &context);
let git_evidence = evidence
.iter()
.find(|e| e.source == EvidenceSource::GitHistory)
.expect("Should have git history evidence");
assert!(!git_evidence.supports_claim);
}
#[test]
fn test_repository_context_with_commit_timestamps() {
let timestamps = vec![1000, 2000, 3000];
let context = RepositoryContext::new_mock().with_commit_timestamps(timestamps.clone());
assert_eq!(context.commit_timestamps, Some(timestamps));
assert_eq!(context.latest_commit_timestamp, Some(3000));
}
#[test]
fn test_repository_context_with_empty_commit_timestamps() {
let context = RepositoryContext::new_mock().with_commit_timestamps(vec![]);
assert_eq!(context.commit_timestamps, Some(vec![]));
assert_eq!(context.latest_commit_timestamp, None);
}
#[test]
fn test_repository_context_new_mock_defaults() {
let context = RepositoryContext::new_mock();
assert_eq!(context.subsequent_commits, Some(vec![]));
assert_eq!(context.test_results, Some((true, 0)));
assert_eq!(context.actual_coverage, None);
assert_eq!(context.coverage_error, None);
assert_eq!(context.broken_links_count, None);
assert_eq!(context.vulnerabilities_count, None);
assert_eq!(context.benchmark_results, None);
assert_eq!(context.issue_status, None);
assert_eq!(context.code_grep_results, None);
}
#[test]
fn test_repository_context_has_git_history() {
let context = RepositoryContext::new_mock();
assert!(!context.has_git_history());
}
#[test]
fn test_repository_context_has_coverage_report() {
let context = RepositoryContext::new_mock();
assert!(!context.has_coverage_report());
}
#[test]
fn test_repository_context_get_test_files() {
let context = RepositoryContext::new_mock();
assert!(context.get_test_files().is_empty());
}
#[test]
fn test_repository_context_get_coverage_percentage_no_report() {
let context = RepositoryContext::new_mock();
assert!((context.get_coverage_percentage() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_repository_context_get_test_execution_info_no_results() {
let context = RepositoryContext::new_mock();
let info = context.get_test_execution_info();
assert!(!info.has_results);
assert_eq!(info.passed_count, 0);
assert_eq!(info.failed_count, 0);
assert_eq!(info.ignored_count, 0);
}
#[test]
fn test_repository_context_grep_codebase() {
let context = RepositoryContext::new_mock();
let needle = ["pmat", "absent", "needle", "7c1f"].join("_");
let results = context.grep_codebase(&needle);
assert!(
results.is_empty(),
"a pattern absent from the tree must match no files, got: {results:?}"
);
}
#[test]
fn test_repository_context_get_recent_commits_no_repo() {
let context = RepositoryContext::new_mock();
let commits = context.get_recent_commits(10);
assert!(commits.is_empty());
}
#[test]
fn test_evidence_source_serialization() {
let sources = vec![
EvidenceSource::GitHistory,
EvidenceSource::TestExecution,
EvidenceSource::CoverageReport,
EvidenceSource::LinkValidation,
EvidenceSource::CargoAudit,
EvidenceSource::BenchmarkResults,
EvidenceSource::IssueTracker,
EvidenceSource::CodeGrep,
];
for source in sources {
let serialized = serde_json::to_string(&source).expect("Should serialize");
let deserialized: EvidenceSource =
serde_json::from_str(&serialized).expect("Should deserialize");
assert_eq!(source, deserialized);
}
}
#[test]
fn test_evidence_result_serialization() {
let result = EvidenceResult {
source: EvidenceSource::GitHistory,
supports_claim: true,
confidence: 0.85,
details: "No issues found".to_string(),
timestamp: Some(1234567890),
};
let serialized = serde_json::to_string(&result).expect("Should serialize");
let deserialized: EvidenceResult =
serde_json::from_str(&serialized).expect("Should deserialize");
assert_eq!(result.source, deserialized.source);
assert_eq!(result.supports_claim, deserialized.supports_claim);
assert!((result.confidence - deserialized.confidence).abs() < f64::EPSILON);
assert_eq!(result.details, deserialized.details);
assert_eq!(result.timestamp, deserialized.timestamp);
}
#[test]
fn test_commit_info_fields() {
let info = CommitInfo {
message: "Fix bug".to_string(),
timestamp: 1234567890,
author: "Test Author".to_string(),
};
assert_eq!(info.message, "Fix bug");
assert_eq!(info.timestamp, 1234567890);
assert_eq!(info.author, "Test Author");
}
#[test]
fn test_test_execution_info_default() {
let info = TestExecutionInfo::default();
assert!(!info.has_results);
assert_eq!(info.passed_count, 0);
assert_eq!(info.failed_count, 0);
assert_eq!(info.ignored_count, 0);
}
fn coverage_claim(numeric: Option<f64>) -> Claim {
Claim {
category: ClaimCategory::Coverage,
text: "coverage at 95%".to_string(),
is_absolute: false,
numeric_value: numeric,
issue_number: None,
has_scope_qualifier: false,
scope: None,
}
}
fn repo_with_lcov(lines_hit: Option<&str>) -> (tempfile::TempDir, RepositoryContext) {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("Cargo.toml"), "[package]\nname=\"rt\"\n")
.expect("write Cargo.toml");
if let Some(lh) = lines_hit {
std::fs::write(
dir.path().join("lcov.info"),
format!("TN:\nSF:src/main.rs\nDA:1,1\nLF:10\nLH:{lh}\nend_of_record\n"),
)
.expect("write lcov");
}
let ctx = RepositoryContext::from_path(dir.path()).expect("context");
(dir, ctx)
}
#[test]
fn a_located_coverage_report_is_actually_read() {
for (lh, expected) in [("1", 10.0), ("10", 100.0), ("0", 0.0)] {
let (_dir, ctx) = repo_with_lcov(Some(lh));
assert_eq!(
ctx.actual_coverage,
Some(expected),
"lcov with LH:{lh}/LF:10 must read as {expected}%"
);
}
let (_dir, ctx) = repo_with_lcov(None);
assert_eq!(ctx.actual_coverage, None);
}
#[test]
fn a_ten_percent_report_contradicts_a_ninety_five_percent_claim() {
let (_dir, ctx) = repo_with_lcov(Some("1"));
let evidence = EvidenceGatherer::new().gather_evidence(&coverage_claim(Some(95.0)), &ctx);
let coverage = evidence
.iter()
.find(|e| e.source == EvidenceSource::CoverageReport)
.expect("the coverage report must adjudicate a numeric coverage claim");
assert!(coverage.contradicts(), "{}", coverage.details);
assert!(coverage.details.contains("Actual: 10.0%"));
let evidence = EvidenceGatherer::new().gather_evidence(&coverage_claim(Some(10.0)), &ctx);
let coverage = evidence
.iter()
.find(|e| e.source == EvidenceSource::CoverageReport)
.expect("coverage evidence");
assert!(coverage.supports_claim, "{}", coverage.details);
}
#[test]
fn a_missing_coverage_report_is_recorded_as_not_measured() {
let (_dir, ctx) = repo_with_lcov(None);
let evidence = EvidenceGatherer::new().gather_evidence(&coverage_claim(Some(95.0)), &ctx);
let note = evidence
.iter()
.find(|e| e.source == EvidenceSource::NotMeasured)
.expect("absence of a coverage report must be stated");
assert!(note.details.contains("no coverage report found"));
assert!(!note.contradicts(), "absence is not a contradiction");
assert!(evidence.iter().all(|e| e.source != EvidenceSource::CoverageReport));
}
#[test]
fn an_empty_lcov_is_unreadable_not_zero_percent() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("lcov.info"), "TN:\nend_of_record\n").expect("write");
let ctx = RepositoryContext::from_path(dir.path()).expect("context");
assert_eq!(ctx.actual_coverage, None);
assert!(ctx.has_coverage_report(), "the path still exists");
assert!(ctx
.coverage_error
.as_deref()
.is_some_and(|e| e.contains("nothing was measured")));
}
#[test]
fn a_missing_benchmark_contradicts_nothing() {
let gatherer = EvidenceGatherer::new();
for (text, numeric) in [
("30% faster after SIMD", Some(30.0)),
("0% faster", Some(0.0)),
("performance improved", None),
] {
let claim = Claim {
category: ClaimCategory::Performance,
text: text.to_string(),
is_absolute: false,
numeric_value: numeric,
issue_number: None,
has_scope_qualifier: false,
scope: None,
};
let evidence = gatherer.gather_evidence(&claim, &RepositoryContext::new_mock());
assert!(
!evidence.iter().any(EvidenceResult::contradicts),
"absent benchmark data contradicted {text:?}: {evidence:?}"
);
let note = evidence
.iter()
.find(|e| e.source == EvidenceSource::NotMeasured)
.expect("the absence must be stated");
assert!(note.details.contains("no benchmark data"));
}
}
#[test]
fn test_results_are_read_when_present() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::create_dir_all(dir.path().join("test-results")).expect("mkdir");
std::fs::write(
dir.path().join("test-results/output.txt"),
"test result: ok. 10 passed; 0 failed; 3 ignored; 0 measured\n",
)
.expect("write");
let ctx = RepositoryContext::from_path(dir.path()).expect("context");
assert_eq!(ctx.test_results, Some((true, 3)));
}