refactor: modularize codebase and add 404 unit tests (#13)
All checks were successful
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Detect Changes (push) Successful in 5s
CI / Tests (push) Successful in 5m15s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2s
All checks were successful
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Detect Changes (push) Successful in 5s
CI / Tests (push) Successful in 5m15s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2s
This commit was merged in pull request #13.
This commit is contained in:
@@ -278,3 +278,220 @@ struct TriageResult {
|
||||
fn default_action() -> String {
|
||||
"confirm".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use compliance_core::models::Severity;
|
||||
|
||||
// ── classify_file_path ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn classify_none_path() {
|
||||
assert_eq!(classify_file_path(None), "unknown");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_production_path() {
|
||||
assert_eq!(classify_file_path(Some("src/main.rs")), "production");
|
||||
assert_eq!(classify_file_path(Some("lib/core/engine.py")), "production");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_test_paths() {
|
||||
assert_eq!(classify_file_path(Some("src/test/helper.rs")), "test");
|
||||
assert_eq!(classify_file_path(Some("src/tests/unit.rs")), "test");
|
||||
assert_eq!(classify_file_path(Some("foo_test.go")), "test");
|
||||
assert_eq!(classify_file_path(Some("bar.test.js")), "test");
|
||||
assert_eq!(classify_file_path(Some("baz.spec.ts")), "test");
|
||||
assert_eq!(
|
||||
classify_file_path(Some("data/fixtures/sample.json")),
|
||||
"test"
|
||||
);
|
||||
assert_eq!(classify_file_path(Some("src/testdata/input.txt")), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_example_paths() {
|
||||
assert_eq!(
|
||||
classify_file_path(Some("docs/examples/basic.rs")),
|
||||
"example"
|
||||
);
|
||||
// /example matches because contains("/example")
|
||||
assert_eq!(classify_file_path(Some("src/example/main.py")), "example");
|
||||
assert_eq!(classify_file_path(Some("src/demo/run.sh")), "example");
|
||||
assert_eq!(classify_file_path(Some("src/sample/lib.rs")), "example");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_generated_paths() {
|
||||
assert_eq!(
|
||||
classify_file_path(Some("src/generated/api.rs")),
|
||||
"generated"
|
||||
);
|
||||
assert_eq!(
|
||||
classify_file_path(Some("proto/gen/service.go")),
|
||||
"generated"
|
||||
);
|
||||
assert_eq!(classify_file_path(Some("api.generated.ts")), "generated");
|
||||
assert_eq!(classify_file_path(Some("service.pb.go")), "generated");
|
||||
assert_eq!(classify_file_path(Some("model_generated.rs")), "generated");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_vendored_paths() {
|
||||
// Implementation checks for /vendor/, /node_modules/, /third_party/ (with slashes)
|
||||
assert_eq!(
|
||||
classify_file_path(Some("src/vendor/lib/foo.go")),
|
||||
"vendored"
|
||||
);
|
||||
assert_eq!(
|
||||
classify_file_path(Some("src/node_modules/pkg/index.js")),
|
||||
"vendored"
|
||||
);
|
||||
assert_eq!(
|
||||
classify_file_path(Some("src/third_party/lib.c")),
|
||||
"vendored"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_is_case_insensitive() {
|
||||
assert_eq!(classify_file_path(Some("src/TEST/Helper.rs")), "test");
|
||||
assert_eq!(classify_file_path(Some("src/VENDOR/lib.go")), "vendored");
|
||||
assert_eq!(
|
||||
classify_file_path(Some("src/GENERATED/foo.ts")),
|
||||
"generated"
|
||||
);
|
||||
}
|
||||
|
||||
// ── adjust_confidence ────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_production() {
|
||||
assert_eq!(adjust_confidence(8.0, "production"), 8.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_test() {
|
||||
assert_eq!(adjust_confidence(10.0, "test"), 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_example() {
|
||||
assert_eq!(adjust_confidence(10.0, "example"), 6.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_generated() {
|
||||
assert_eq!(adjust_confidence(10.0, "generated"), 3.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_vendored() {
|
||||
assert_eq!(adjust_confidence(10.0, "vendored"), 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_unknown_classification() {
|
||||
assert_eq!(adjust_confidence(7.0, "unknown"), 7.0);
|
||||
assert_eq!(adjust_confidence(7.0, "something_else"), 7.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjust_confidence_zero() {
|
||||
assert_eq!(adjust_confidence(0.0, "test"), 0.0);
|
||||
assert_eq!(adjust_confidence(0.0, "production"), 0.0);
|
||||
}
|
||||
|
||||
// ── downgrade_severity ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn downgrade_severity_all_levels() {
|
||||
assert_eq!(downgrade_severity(&Severity::Critical), Severity::High);
|
||||
assert_eq!(downgrade_severity(&Severity::High), Severity::Medium);
|
||||
assert_eq!(downgrade_severity(&Severity::Medium), Severity::Low);
|
||||
assert_eq!(downgrade_severity(&Severity::Low), Severity::Info);
|
||||
assert_eq!(downgrade_severity(&Severity::Info), Severity::Info);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn downgrade_severity_info_is_floor() {
|
||||
// Downgrading Info twice should still be Info
|
||||
let s = downgrade_severity(&Severity::Info);
|
||||
assert_eq!(downgrade_severity(&s), Severity::Info);
|
||||
}
|
||||
|
||||
// ── upgrade_severity ─────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn upgrade_severity_all_levels() {
|
||||
assert_eq!(upgrade_severity(&Severity::Info), Severity::Low);
|
||||
assert_eq!(upgrade_severity(&Severity::Low), Severity::Medium);
|
||||
assert_eq!(upgrade_severity(&Severity::Medium), Severity::High);
|
||||
assert_eq!(upgrade_severity(&Severity::High), Severity::Critical);
|
||||
assert_eq!(upgrade_severity(&Severity::Critical), Severity::Critical);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_severity_critical_is_ceiling() {
|
||||
let s = upgrade_severity(&Severity::Critical);
|
||||
assert_eq!(upgrade_severity(&s), Severity::Critical);
|
||||
}
|
||||
|
||||
// ── upgrade/downgrade roundtrip ──────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn upgrade_then_downgrade_is_identity_for_middle_values() {
|
||||
for sev in [Severity::Low, Severity::Medium, Severity::High] {
|
||||
assert_eq!(downgrade_severity(&upgrade_severity(&sev)), sev);
|
||||
}
|
||||
}
|
||||
|
||||
// ── TriageResult deserialization ─────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn triage_result_full() {
|
||||
let json = r#"{"action":"dismiss","confidence":8.5,"rationale":"false positive","remediation":"remove code"}"#;
|
||||
let r: TriageResult = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(r.action, "dismiss");
|
||||
assert_eq!(r.confidence, 8.5);
|
||||
assert_eq!(r.rationale, "false positive");
|
||||
assert_eq!(r.remediation.as_deref(), Some("remove code"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triage_result_defaults() {
|
||||
let json = r#"{}"#;
|
||||
let r: TriageResult = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(r.action, "confirm");
|
||||
assert_eq!(r.confidence, 0.0);
|
||||
assert_eq!(r.rationale, "");
|
||||
assert!(r.remediation.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triage_result_partial() {
|
||||
let json = r#"{"action":"downgrade","confidence":6.0}"#;
|
||||
let r: TriageResult = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(r.action, "downgrade");
|
||||
assert_eq!(r.confidence, 6.0);
|
||||
assert_eq!(r.rationale, "");
|
||||
assert!(r.remediation.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triage_result_with_markdown_fences() {
|
||||
// Simulate LLM wrapping response in markdown code fences
|
||||
let raw = "```json\n{\"action\":\"upgrade\",\"confidence\":9,\"rationale\":\"critical\",\"remediation\":null}\n```";
|
||||
let cleaned = raw
|
||||
.trim()
|
||||
.trim_start_matches("```json")
|
||||
.trim_start_matches("```")
|
||||
.trim_end_matches("```")
|
||||
.trim();
|
||||
let r: TriageResult = serde_json::from_str(cleaned).unwrap();
|
||||
assert_eq!(r.action, "upgrade");
|
||||
assert_eq!(r.confidence, 9.0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user