Skip to main content

git_slop/
report.rs

1mod assembly;
2mod render;
3mod support;
4mod write;
5
6pub use assembly::assemble_report;
7pub use render::{render_compatibility_summary, render_terminal, render_terminal_output};
8pub use write::{load_report, write_report_bundle};
9
10#[cfg(test)]
11mod tests {
12    use std::fs;
13    use std::path::Path;
14
15    use serde_json::{Value, json};
16    use tempfile::tempdir;
17
18    use super::*;
19    use crate::config;
20    use crate::model::{Analysis, HealthRollup, OrganizationAnalysis, RepoMetadata, SkippedCounts};
21
22    fn analysis(root: &Path) -> Analysis {
23        Analysis {
24            repo_root: root.to_path_buf(),
25            repo: RepoMetadata {
26                repo_name: "fixture".to_string(),
27                repo_root: root.display().to_string(),
28                branch: Some("main".to_string()),
29                head_commit: Some("abc123".to_string()),
30                head_commit_timestamp: Some("2026-07-29T08:00:00Z".to_string()),
31                git_remote_url: Some("git@github.com:example/fixture.git".to_string()),
32                is_shallow: false,
33            },
34            config: crate::config::default_config(),
35            generated_at: "2026-07-30T10:11:12Z".to_string(),
36            analyzed_revision_at: Some("2026-07-29T08:00:00Z".to_string()),
37            skipped: SkippedCounts::default(),
38            tracked_file_count: 0,
39            files: vec![],
40            folders: vec![],
41            commits: vec![],
42            organization: OrganizationAnalysis::default(),
43            action_queue: vec![],
44            report: Value::Null,
45        }
46    }
47
48    #[test]
49    fn report_keeps_generation_and_revision_timestamps_distinct() {
50        let root = tempdir().expect("temporary directory");
51        let analysis = analysis(root.path());
52        let report = assemble_report(&analysis, &HealthRollup::default());
53        assert_eq!(report["generated_at"], "2026-07-30T10:11:12Z");
54        assert_eq!(report["analyzed_revision_at"], "2026-07-29T08:00:00Z");
55        assert_eq!(report["repo"]["head_commit"], "abc123");
56        assert_eq!(report["repo"]["head_sha"], "abc123");
57        assert_eq!(
58            report["repo"]["remote_url"],
59            "git@github.com:example/fixture.git"
60        );
61    }
62
63    #[test]
64    fn bundle_writes_compatibility_and_health_surfaces_atomically() {
65        let root = tempdir().expect("temporary directory");
66        let analysis = analysis(root.path());
67        let result =
68            write_report_bundle(&analysis, &HealthRollup::default()).expect("report bundle");
69        assert!(result.report_json.is_file());
70        assert!(result.report_yaml.is_file());
71        assert!(result.summary_md.is_file());
72        assert!(result.health_md.is_file());
73        assert!(
74            fs::read_to_string(&result.summary_md)
75                .expect("summary")
76                .starts_with("# Git Slop Summary")
77        );
78        assert!(
79            fs::read_to_string(&result.health_md)
80                .expect("health")
81                .starts_with("# Repository Health")
82        );
83        assert_eq!(
84            result.report,
85            load_report(&result.report_json).expect("report")
86        );
87        let run_directories = fs::read_dir(config::runs_dir(root.path()))
88            .expect("runs")
89            .count();
90        assert_eq!(run_directories, 1);
91    }
92
93    #[test]
94    fn compatibility_summary_is_bounded_and_actionable() {
95        let mut analysis = analysis(Path::new("/tmp/fixture"));
96        analysis.action_queue = (0..40)
97            .map(|index| {
98                json!({
99                    "path": format!("src/file_{index}.rs"),
100                    "slop_band": "moderate",
101                    "context_band": "healthy",
102                    "slop_score": 50.0,
103                    "tokens": 4_000,
104                    "age_days": 1,
105                    "revisions_window": 2,
106                    "churn_pressure": 0.5,
107                    "reason_codes": ["high_revision_frequency"],
108                    "is_pure_context_hotspot": false
109                })
110            })
111            .collect();
112        let report = assemble_report(&analysis, &HealthRollup::default());
113        let summary = render_compatibility_summary(&report);
114        assert_eq!(
115            summary.matches("| [src/file_").count(),
116            super::render::DEFAULT_SUMMARY_LIMIT
117        );
118        assert!(summary.contains("git-slop explain --path"));
119        assert!(summary.contains("changes frequently"));
120    }
121}