libverify_core/controls/
code_scanning_alerts_resolved.rs1use crate::control::{Control, ControlFinding, ControlId, builtin};
2use crate::evidence::EvidenceBundle;
3
4pub struct CodeScanningAlertsResolvedControl;
15
16impl Control for CodeScanningAlertsResolvedControl {
17 fn id(&self) -> ControlId {
18 builtin::id(builtin::CODE_SCANNING_ALERTS_RESOLVED)
19 }
20
21 fn description(&self) -> &'static str {
22 "All high-or-above severity code scanning alerts must be resolved"
23 }
24
25 fn evaluate(&self, evidence: &EvidenceBundle) -> Vec<ControlFinding> {
26 let posture = match ControlFinding::extract_posture(self.id(), evidence) {
27 Ok(p) => p,
28 Err(findings) => return findings,
29 };
30
31 if !posture.code_scanning_enabled {
32 return vec![ControlFinding::not_applicable(
33 self.id(),
34 "Code scanning is not enabled — alert resolution check is not applicable",
35 )];
36 }
37
38 if posture.open_high_severity_alerts == 0 {
39 vec![ControlFinding::satisfied(
40 self.id(),
41 "No open high-or-above severity code scanning alerts",
42 vec!["repository:code-scanning:alerts:clear".to_string()],
43 )]
44 } else {
45 vec![ControlFinding::violated(
46 self.id(),
47 format!(
48 "{} open high-or-above severity code scanning alert(s) — \
49 resolve before deploying to production",
50 posture.open_high_severity_alerts
51 ),
52 vec![format!(
53 "repository:code-scanning:alerts:open:{}",
54 posture.open_high_severity_alerts
55 )],
56 )]
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::control::ControlStatus;
65 use crate::evidence::{EvidenceGap, EvidenceState, RepositoryPosture};
66
67 fn posture(code_scanning: bool, open_alerts: u32) -> RepositoryPosture {
68 RepositoryPosture {
69 code_scanning_enabled: code_scanning,
70 open_high_severity_alerts: open_alerts,
71 ..Default::default()
72 }
73 }
74
75 fn bundle(state: EvidenceState<RepositoryPosture>) -> EvidenceBundle {
76 EvidenceBundle {
77 repository_posture: state,
78 ..Default::default()
79 }
80 }
81
82 #[test]
83 fn not_applicable_when_posture_not_applicable() {
84 let findings =
85 CodeScanningAlertsResolvedControl.evaluate(&bundle(EvidenceState::not_applicable()));
86 assert_eq!(findings[0].status, ControlStatus::NotApplicable);
87 }
88
89 #[test]
90 fn indeterminate_when_posture_missing() {
91 let findings =
92 CodeScanningAlertsResolvedControl.evaluate(&bundle(EvidenceState::missing(vec![
93 EvidenceGap::CollectionFailed {
94 source: "github".to_string(),
95 subject: "posture".to_string(),
96 detail: "API error".to_string(),
97 },
98 ])));
99 assert_eq!(findings[0].status, ControlStatus::Indeterminate);
100 }
101
102 #[test]
103 fn not_applicable_when_code_scanning_disabled() {
104 let findings = CodeScanningAlertsResolvedControl
105 .evaluate(&bundle(EvidenceState::complete(posture(false, 0))));
106 assert_eq!(findings[0].status, ControlStatus::NotApplicable);
107 assert!(findings[0].rationale.contains("not enabled"));
108 }
109
110 #[test]
111 fn satisfied_when_no_open_alerts() {
112 let findings = CodeScanningAlertsResolvedControl
113 .evaluate(&bundle(EvidenceState::complete(posture(true, 0))));
114 assert_eq!(findings[0].status, ControlStatus::Satisfied);
115 assert!(findings[0].rationale.contains("No open"));
116 }
117
118 #[test]
119 fn violated_when_open_alerts_exist() {
120 let findings = CodeScanningAlertsResolvedControl
121 .evaluate(&bundle(EvidenceState::complete(posture(true, 3))));
122 assert_eq!(findings[0].status, ControlStatus::Violated);
123 assert!(findings[0].rationale.contains("3 open"));
124 assert!(findings[0].subjects[0].contains("open:3"));
125 }
126}