Skip to main content

cuenv_ci/report/
json.rs

1use super::PipelineReport;
2use cuenv_core::Result;
3
4/// Writes the pipeline report to a JSON file
5///
6/// # Errors
7/// Returns error if file creation or JSON serialization fails
8pub fn write_report(report: &PipelineReport, path: &std::path::Path) -> Result<()> {
9    let file = std::fs::File::create(path)?;
10    serde_json::to_writer_pretty(file, report).map_err(|e| cuenv_core::Error::Io {
11        source: e.into(),
12        path: Some(path.into()),
13        operation: "write_report".to_string(),
14    })?;
15    Ok(())
16}
17
18#[cfg(test)]
19mod tests {
20    use super::*;
21    use crate::report::{ContextReport, PipelineStatus, TaskReport, TaskStatus};
22    use chrono::Utc;
23    use tempfile::TempDir;
24
25    fn create_test_report() -> PipelineReport {
26        PipelineReport {
27            version: "0.21.4".to_string(),
28            project: "test-project".to_string(),
29            pipeline: "default".to_string(),
30            context: ContextReport {
31                provider: "github".to_string(),
32                event: "push".to_string(),
33                ref_name: "refs/heads/main".to_string(),
34                base_ref: None,
35                sha: "abc123".to_string(),
36                changed_files: vec!["src/main.rs".to_string()],
37            },
38            started_at: Utc::now(),
39            completed_at: Some(Utc::now()),
40            duration_ms: Some(1234),
41            status: PipelineStatus::Success,
42            tasks: vec![TaskReport {
43                name: "build".to_string(),
44                status: TaskStatus::Success,
45                duration_ms: 500,
46                exit_code: Some(0),
47                inputs_matched: vec!["src/**/*.rs".to_string()],
48                cache_key: Some("abc123".to_string()),
49                outputs: vec!["target/release/binary".to_string()],
50            }],
51        }
52    }
53
54    #[test]
55    fn test_write_report_creates_valid_json() {
56        let temp_dir = TempDir::new().unwrap();
57        let report_path = temp_dir.path().join("report.json");
58        let report = create_test_report();
59
60        let result = write_report(&report, &report_path);
61        assert!(result.is_ok());
62
63        // Verify file was created
64        assert!(report_path.exists());
65
66        // Read and parse the file
67        let content = std::fs::read_to_string(&report_path).unwrap();
68        let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
69
70        // Verify expected fields
71        assert_eq!(parsed["version"], "0.21.4");
72        assert_eq!(parsed["project"], "test-project");
73        assert_eq!(parsed["pipeline"], "default");
74        assert_eq!(parsed["status"], "success");
75    }
76
77    #[test]
78    fn test_write_report_pretty_prints() {
79        let temp_dir = TempDir::new().unwrap();
80        let report_path = temp_dir.path().join("report.json");
81        let report = create_test_report();
82
83        write_report(&report, &report_path).unwrap();
84
85        // Pretty-printed JSON should contain indentation
86        let content = std::fs::read_to_string(&report_path).unwrap();
87        assert!(content.contains('\n'));
88        assert!(content.contains("  "));
89    }
90
91    #[test]
92    fn test_write_report_includes_context() {
93        let temp_dir = TempDir::new().unwrap();
94        let report_path = temp_dir.path().join("report.json");
95        let report = create_test_report();
96
97        write_report(&report, &report_path).unwrap();
98
99        let file_content = std::fs::read_to_string(&report_path).unwrap();
100        let parsed: serde_json::Value = serde_json::from_str(&file_content).unwrap();
101
102        let ctx = &parsed["context"];
103        assert_eq!(ctx["provider"], "github");
104        assert_eq!(ctx["event"], "push");
105        assert_eq!(ctx["ref_name"], "refs/heads/main");
106        assert_eq!(ctx["sha"], "abc123");
107    }
108
109    #[test]
110    fn test_write_report_includes_tasks() {
111        let temp_dir = TempDir::new().unwrap();
112        let report_path = temp_dir.path().join("report.json");
113        let report = create_test_report();
114
115        write_report(&report, &report_path).unwrap();
116
117        let content = std::fs::read_to_string(&report_path).unwrap();
118        let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
119
120        let tasks = parsed["tasks"].as_array().unwrap();
121        assert_eq!(tasks.len(), 1);
122        assert_eq!(tasks[0]["name"], "build");
123        assert_eq!(tasks[0]["status"], "success");
124        assert_eq!(tasks[0]["duration_ms"], 500);
125    }
126
127    #[test]
128    fn test_write_report_failed_pipeline() {
129        let temp_dir = TempDir::new().unwrap();
130        let report_path = temp_dir.path().join("report.json");
131        let mut report = create_test_report();
132        report.status = PipelineStatus::Failed;
133        report.tasks[0].status = TaskStatus::Failed;
134        report.tasks[0].exit_code = Some(1);
135
136        write_report(&report, &report_path).unwrap();
137
138        let content = std::fs::read_to_string(&report_path).unwrap();
139        let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
140
141        assert_eq!(parsed["status"], "failed");
142        assert_eq!(parsed["tasks"][0]["status"], "failed");
143        assert_eq!(parsed["tasks"][0]["exit_code"], 1);
144    }
145
146    #[test]
147    fn test_write_report_cached_task() {
148        let temp_dir = TempDir::new().unwrap();
149        let report_path = temp_dir.path().join("report.json");
150        let mut report = create_test_report();
151        report.tasks[0].status = TaskStatus::Cached;
152
153        write_report(&report, &report_path).unwrap();
154
155        let content = std::fs::read_to_string(&report_path).unwrap();
156        let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
157
158        assert_eq!(parsed["tasks"][0]["status"], "cached");
159    }
160
161    #[test]
162    fn test_write_report_to_nested_directory() {
163        let temp_dir = TempDir::new().unwrap();
164        let nested_path = temp_dir.path().join("nested").join("dir");
165        std::fs::create_dir_all(&nested_path).unwrap();
166        let report_path = nested_path.join("report.json");
167        let report = create_test_report();
168
169        let result = write_report(&report, &report_path);
170        assert!(result.is_ok());
171        assert!(report_path.exists());
172    }
173
174    #[test]
175    fn test_write_report_invalid_path_fails() {
176        let report = create_test_report();
177        // Path to a non-existent directory
178        let invalid_path = std::path::Path::new("/nonexistent/dir/report.json");
179
180        let result = write_report(&report, invalid_path);
181        assert!(result.is_err());
182    }
183
184    #[test]
185    fn test_write_report_multiple_tasks() {
186        let temp_dir = TempDir::new().unwrap();
187        let report_path = temp_dir.path().join("report.json");
188        let mut report = create_test_report();
189        report.tasks.push(TaskReport {
190            name: "test".to_string(),
191            status: TaskStatus::Success,
192            duration_ms: 300,
193            exit_code: Some(0),
194            inputs_matched: vec!["tests/**/*.rs".to_string()],
195            cache_key: None,
196            outputs: vec![],
197        });
198        report.tasks.push(TaskReport {
199            name: "lint".to_string(),
200            status: TaskStatus::Skipped,
201            duration_ms: 0,
202            exit_code: None,
203            inputs_matched: vec![],
204            cache_key: None,
205            outputs: vec![],
206        });
207
208        write_report(&report, &report_path).unwrap();
209
210        let content = std::fs::read_to_string(&report_path).unwrap();
211        let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
212
213        let tasks = parsed["tasks"].as_array().unwrap();
214        assert_eq!(tasks.len(), 3);
215        assert_eq!(tasks[1]["name"], "test");
216        assert_eq!(tasks[2]["name"], "lint");
217        assert_eq!(tasks[2]["status"], "skipped");
218    }
219}