Skip to main content

assay_core/coverage_next/
report.rs

1use super::CoverageReport;
2
3impl CoverageReport {
4    /// Format as GitHub Actions annotation
5    pub fn to_github_annotation(&self) -> String {
6        let mut lines = Vec::new();
7
8        if !self.meets_threshold {
9            lines.push(format!(
10                "::error::Coverage {:.1}% is below threshold {:.1}%",
11                self.overall_coverage_pct, self.threshold
12            ));
13        }
14
15        for gap in &self.high_risk_gaps {
16            lines.push(format!(
17                "::warning::High-risk tool '{}' never tested: {}",
18                gap.tool, gap.reason
19            ));
20        }
21
22        for tool in &self.tool_coverage.unseen_tools {
23            lines.push(format!(
24                "::notice::Tool '{}' in policy but not covered by tests",
25                tool
26            ));
27        }
28
29        lines.join("\n")
30    }
31
32    /// Format as markdown summary
33    pub fn to_markdown(&self) -> String {
34        let status = if self.meets_threshold { "✅" } else { "❌" };
35
36        let mut md = format!(
37            "## Coverage Report {}\n\n\
38            | Metric | Value |\n\
39            |--------|-------|\n\
40            | Overall Coverage | {:.1}% |\n\
41            | Tool Coverage | {:.1}% ({}/{}) |\n\
42            | Rule Coverage | {:.1}% ({}/{}) |\n\
43            | Threshold | {:.1}% |\n\n",
44            status,
45            self.overall_coverage_pct,
46            self.tool_coverage.coverage_pct,
47            self.tool_coverage.tools_seen_in_traces,
48            self.tool_coverage.total_tools_in_policy,
49            self.rule_coverage.coverage_pct,
50            self.rule_coverage.rules_triggered,
51            self.rule_coverage.total_rules,
52            self.threshold,
53        );
54
55        if !self.high_risk_gaps.is_empty() {
56            md.push_str("### ⚠️ High-Risk Gaps\n\n");
57            for gap in &self.high_risk_gaps {
58                md.push_str(&format!("- **{}**: {}\n", gap.tool, gap.reason));
59            }
60            md.push('\n');
61        }
62
63        if !self.tool_coverage.unseen_tools.is_empty() {
64            md.push_str("### Uncovered Tools\n\n");
65            for tool in &self.tool_coverage.unseen_tools {
66                md.push_str(&format!("- `{}`\n", tool));
67            }
68            md.push('\n');
69        }
70
71        if !self.rule_coverage.untriggered_rules.is_empty() {
72            md.push_str("### Untriggered Rules\n\n");
73            for rule in &self.rule_coverage.untriggered_rules {
74                md.push_str(&format!("- `{}`\n", rule));
75            }
76            md.push('\n');
77        }
78
79        md
80    }
81}