cuenv_ci/provider/
local.rs1use super::{CIContext, CIProvider};
2use crate::report::{CheckHandle, PipelineReport};
3use async_trait::async_trait;
4use cuenv_core::Result;
5use std::collections::HashSet;
6use std::path::PathBuf;
7
8pub struct LocalProvider {
9 context: CIContext,
10 base_ref: Option<String>,
11}
12
13impl LocalProvider {
14 #[must_use]
17 pub fn with_base_ref(base_ref: String) -> Self {
18 Self {
19 context: CIContext {
20 provider: "local".to_string(),
21 event: "manual".to_string(),
22 ref_name: "current".to_string(),
23 base_ref: Some(base_ref.clone()),
24 sha: "current".to_string(),
25 },
26 base_ref: Some(base_ref),
27 }
28 }
29}
30
31#[async_trait]
32impl CIProvider for LocalProvider {
33 fn detect() -> Option<Self> {
34 Some(Self {
36 context: CIContext {
37 provider: "local".to_string(),
38 event: "manual".to_string(),
39 ref_name: "current".to_string(),
40 base_ref: None,
41 sha: "current".to_string(),
42 },
43 base_ref: None,
44 })
45 }
46
47 fn context(&self) -> &CIContext {
48 &self.context
49 }
50
51 async fn changed_files(&self) -> Result<Vec<PathBuf>> {
52 let mut changed: HashSet<PathBuf> = HashSet::new();
53
54 if let Some(ref base_ref) = self.base_ref {
56 let output = std::process::Command::new("git")
59 .args(["diff", "--name-only", &format!("{base_ref}...HEAD")])
60 .output()
61 .ok();
62
63 if let Some(output) = output {
64 let stdout = String::from_utf8_lossy(&output.stdout);
65 for line in stdout.lines() {
66 changed.insert(PathBuf::from(line));
67 }
68 }
69 }
70
71 let output = std::process::Command::new("git")
73 .args(["diff", "--name-only", "HEAD"])
74 .output()
75 .ok();
76
77 if let Some(output) = output {
78 let stdout = String::from_utf8_lossy(&output.stdout);
79 for line in stdout.lines() {
80 changed.insert(PathBuf::from(line));
81 }
82 }
83
84 Ok(changed.into_iter().collect())
85 }
86
87 async fn create_check(&self, _name: &str) -> Result<CheckHandle> {
88 Ok(CheckHandle {
89 id: "local".to_string(),
90 })
91 }
92
93 async fn update_check(&self, _handle: &CheckHandle, _summary: &str) -> Result<()> {
94 Ok(())
95 }
96
97 async fn complete_check(&self, _handle: &CheckHandle, _report: &PipelineReport) -> Result<()> {
98 Ok(())
99 }
100
101 async fn upload_report(&self, _report: &PipelineReport) -> Result<Option<String>> {
102 Ok(None)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::report::{ContextReport, PipelineStatus};
110
111 fn make_test_report() -> PipelineReport {
112 PipelineReport {
113 version: "1.0".to_string(),
114 project: "test".to_string(),
115 pipeline: "ci".to_string(),
116 context: ContextReport {
117 provider: "local".to_string(),
118 event: "manual".to_string(),
119 ref_name: "current".to_string(),
120 base_ref: None,
121 sha: "abc123".to_string(),
122 changed_files: vec![],
123 },
124 started_at: chrono::Utc::now(),
125 completed_at: Some(chrono::Utc::now()),
126 duration_ms: Some(100),
127 status: PipelineStatus::Success,
128 tasks: vec![],
129 }
130 }
131
132 #[test]
133 fn test_local_provider_detect() {
134 let provider = LocalProvider::detect();
135 assert!(provider.is_some());
136
137 let provider = provider.unwrap();
138 let ctx = provider.context();
139 assert_eq!(ctx.provider, "local");
140 assert_eq!(ctx.event, "manual");
141 assert_eq!(ctx.ref_name, "current");
142 assert!(ctx.base_ref.is_none());
143 }
144
145 #[test]
146 fn test_local_provider_with_base_ref() {
147 let provider = LocalProvider::with_base_ref("main".to_string());
148 let ctx = provider.context();
149
150 assert_eq!(ctx.provider, "local");
151 assert_eq!(ctx.event, "manual");
152 assert_eq!(ctx.base_ref, Some("main".to_string()));
153 }
154
155 #[test]
156 fn test_local_provider_context_sha() {
157 let provider = LocalProvider::detect().unwrap();
158 let ctx = provider.context();
159 assert_eq!(ctx.sha, "current");
160 }
161
162 #[tokio::test]
163 async fn test_local_provider_create_check() {
164 let provider = LocalProvider::detect().unwrap();
165 let handle = provider.create_check("test-check").await.unwrap();
166 assert_eq!(handle.id, "local");
167 }
168
169 #[tokio::test]
170 async fn test_local_provider_update_check() {
171 let provider = LocalProvider::detect().unwrap();
172 let handle = CheckHandle {
173 id: "local".to_string(),
174 };
175 let result = provider.update_check(&handle, "running").await;
176 assert!(result.is_ok());
177 }
178
179 #[tokio::test]
180 async fn test_local_provider_complete_check() {
181 let provider = LocalProvider::detect().unwrap();
182 let handle = CheckHandle {
183 id: "local".to_string(),
184 };
185 let report = make_test_report();
186 let result = provider.complete_check(&handle, &report).await;
187 assert!(result.is_ok());
188 }
189
190 #[tokio::test]
191 async fn test_local_provider_upload_report_returns_none() {
192 let provider = LocalProvider::detect().unwrap();
193 let report = make_test_report();
194 let result = provider.upload_report(&report).await.unwrap();
195 assert!(result.is_none());
196 }
197
198 #[tokio::test]
199 async fn test_local_provider_changed_files() {
200 let provider = LocalProvider::detect().unwrap();
202 let result = provider.changed_files().await;
203 assert!(result.is_ok());
205 }
206
207 #[tokio::test]
208 async fn test_local_provider_with_base_ref_changed_files() {
209 let provider = LocalProvider::with_base_ref("HEAD~1".to_string());
211 let result = provider.changed_files().await;
212 assert!(result.is_ok());
214 }
215}