1use super::Github;
2use crate::api_traits::{ApiOperation, CicdJob, CicdRunner, NumberDeltaErr};
3use crate::cmds::cicd::{
4 Job, JobListBodyArgs, LintResponse, Pipeline, PipelineBodyArgs, RunnerListBodyArgs,
5 RunnerMetadata, RunnerPostDataCliArgs, RunnerRegistrationResponse, YamlBytes,
6};
7use crate::remote::query;
8use crate::{
9 api_traits::Cicd,
10 io::{HttpResponse, HttpRunner},
11};
12use crate::{http, time, Result};
13
14impl<R: HttpRunner<Response = HttpResponse>> Cicd for Github<R> {
15 fn list(&self, args: PipelineBodyArgs) -> Result<Vec<Pipeline>> {
16 let url = format!(
19 "{}/repos/{}/actions/runs",
20 self.rest_api_basepath, self.path
21 );
22 query::paged(
23 &self.runner,
24 &url,
25 args.from_to_page,
26 self.request_headers(),
27 Some("workflow_runs"),
28 ApiOperation::Pipeline,
29 |value| GithubPipelineFields::from(value).into(),
30 )
31 }
32
33 fn get_pipeline(&self, _id: i64) -> Result<Pipeline> {
34 todo!()
35 }
36
37 fn num_pages(&self) -> Result<Option<u32>> {
38 let (url, headers) = self.resource_cicd_metadata_url();
39 query::num_pages(&self.runner, &url, headers, ApiOperation::Pipeline)
40 }
41
42 fn num_resources(&self) -> Result<Option<NumberDeltaErr>> {
43 let (url, headers) = self.resource_cicd_metadata_url();
44 query::num_resources(&self.runner, &url, headers, ApiOperation::Pipeline)
45 }
46
47 fn lint(&self, _body: YamlBytes) -> Result<LintResponse> {
48 todo!()
49 }
50}
51
52impl<R> Github<R> {
53 fn resource_cicd_metadata_url(&self) -> (String, http::Headers) {
54 let url = format!(
55 "{}/repos/{}/actions/runs?page=1",
56 self.rest_api_basepath, self.path
57 );
58 let headers = self.request_headers();
59 (url, headers)
60 }
61}
62
63impl<R: HttpRunner<Response = HttpResponse>> CicdRunner for Github<R> {
64 fn list(&self, _args: RunnerListBodyArgs) -> Result<Vec<crate::cmds::cicd::Runner>> {
65 todo!();
66 }
67
68 fn get(&self, _id: i64) -> Result<RunnerMetadata> {
69 todo!();
70 }
71
72 fn num_pages(&self, _args: RunnerListBodyArgs) -> Result<Option<u32>> {
73 todo!();
74 }
75
76 fn num_resources(&self, _args: RunnerListBodyArgs) -> Result<Option<NumberDeltaErr>> {
77 todo!()
78 }
79
80 fn create(&self, _args: RunnerPostDataCliArgs) -> Result<RunnerRegistrationResponse> {
81 todo!()
82 }
83}
84
85impl<R: HttpRunner<Response = HttpResponse>> CicdJob for Github<R> {
86 fn list(&self, _args: JobListBodyArgs) -> Result<Vec<Job>> {
87 todo!();
88 }
89
90 fn num_pages(&self, _args: JobListBodyArgs) -> Result<Option<u32>> {
91 todo!();
92 }
93
94 fn num_resources(
95 &self,
96 _args: JobListBodyArgs,
97 ) -> Result<Option<crate::api_traits::NumberDeltaErr>> {
98 todo!();
99 }
100}
101
102pub struct GithubPipelineFields {
103 pipeline: Pipeline,
104}
105
106impl From<&serde_json::Value> for GithubPipelineFields {
107 fn from(pipeline_data: &serde_json::Value) -> Self {
108 GithubPipelineFields {
109 pipeline: Pipeline::builder()
110 .id(pipeline_data["id"].as_i64().unwrap_or_default())
111 .status(
118 pipeline_data["conclusion"]
119 .as_str()
120 .unwrap_or_else(||
123 pipeline_data["status"].as_str().unwrap_or("unknown"))
126 .to_string(),
127 )
128 .web_url(pipeline_data["html_url"].as_str().unwrap().to_string())
129 .branch(pipeline_data["head_branch"].as_str().unwrap().to_string())
130 .sha(pipeline_data["head_sha"].as_str().unwrap().to_string())
131 .created_at(pipeline_data["created_at"].as_str().unwrap().to_string())
132 .updated_at(pipeline_data["updated_at"].as_str().unwrap().to_string())
133 .duration(time::compute_duration(
134 pipeline_data["created_at"].as_str().unwrap(),
135 pipeline_data["updated_at"].as_str().unwrap(),
136 ))
137 .build()
138 .unwrap(),
139 }
140 }
141}
142
143impl From<GithubPipelineFields> for Pipeline {
144 fn from(fields: GithubPipelineFields) -> Self {
145 fields.pipeline
146 }
147}
148
149#[cfg(test)]
150mod test {
151
152 use crate::{
153 error,
154 http::Headers,
155 remote::ListBodyArgs,
156 setup_client,
157 test::utils::{default_github, get_contract, ContractType, ResponseContracts},
158 };
159
160 use super::*;
161
162 #[test]
163 fn test_list_actions() {
164 let contracts = ResponseContracts::new(ContractType::Github).add_contract(
165 200,
166 "list_pipelines.json",
167 None,
168 );
169 let (client, github) = setup_client!(contracts, default_github(), dyn Cicd);
170 let args = PipelineBodyArgs::builder()
171 .from_to_page(None)
172 .build()
173 .unwrap();
174 let runs = github.list(args).unwrap();
175 assert_eq!(
176 "https://api.github.com/repos/jordilin/githapi/actions/runs",
177 *client.url(),
178 );
179 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
180 assert_eq!(1, runs.len());
181 }
182
183 #[test]
184 fn test_list_actions_error_status_code() {
185 let contracts =
186 ResponseContracts::new(ContractType::Github).add_body::<String>(401, None, None);
187 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
188 let args = PipelineBodyArgs::builder()
189 .from_to_page(None)
190 .build()
191 .unwrap();
192 assert!(github.list(args).is_err());
193 }
194
195 #[test]
196 fn test_list_actions_unexpected_ok_status_code() {
197 let contracts =
198 ResponseContracts::new(ContractType::Github).add_body::<String>(302, None, None);
199 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
200 let args = PipelineBodyArgs::builder()
201 .from_to_page(None)
202 .build()
203 .unwrap();
204 match github.list(args) {
205 Ok(_) => panic!("Expected error"),
206 Err(err) => match err.downcast_ref::<error::GRError>() {
207 Some(error::GRError::RemoteServerError(_)) => (),
208 _ => panic!("Expected error::GRError::RemoteServerError"),
209 },
210 }
211 }
212
213 #[test]
214 fn test_list_actions_empty_workflow_runs() {
215 let contracts = ResponseContracts::new(ContractType::Github).add_body::<String>(
216 200,
217 Some(r#"{"workflow_runs":[]}"#.to_string()),
218 None,
219 );
220 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
221 let args = PipelineBodyArgs::builder()
222 .from_to_page(None)
223 .build()
224 .unwrap();
225 assert_eq!(0, github.list(args).unwrap().len());
226 }
227
228 #[test]
229 fn test_workflow_runs_not_an_array_is_error() {
230 let contracts = ResponseContracts::new(ContractType::Github).add_body::<String>(
231 200,
232 Some(r#"{"workflow_runs":{}}"#.to_string()),
233 None,
234 );
235 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
236 let args = PipelineBodyArgs::builder()
237 .from_to_page(None)
238 .build()
239 .unwrap();
240 match github.list(args) {
241 Ok(_) => panic!("Expected error"),
242 Err(err) => match err.downcast_ref::<error::GRError>() {
243 Some(error::GRError::RemoteUnexpectedResponseContract(_)) => (),
244 _ => panic!("Expected error::GRError::RemoteUnexpectedResponseContract"),
245 },
246 }
247 }
248
249 #[test]
250 fn test_num_pages_for_list_actions() {
251 let link_header = r#"<https://api.github.com/repos/jordilin/githapi/actions/runs?page=1>; rel="next", <https://api.github.com/repos/jordilin/githapi/actions/runs?page=1>; rel="last""#;
252 let mut headers = Headers::new();
253 headers.set("link".to_string(), link_header.to_string());
254 let contracts = ResponseContracts::new(ContractType::Github).add_body::<String>(
255 200,
256 None,
257 Some(headers),
258 );
259 let (client, github) = setup_client!(contracts, default_github(), dyn Cicd);
260 assert_eq!(Some(1), github.num_pages().unwrap());
261 assert_eq!(
262 "https://api.github.com/repos/jordilin/githapi/actions/runs?page=1",
263 *client.url(),
264 );
265 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
266 }
267
268 #[test]
269 fn test_num_pages_error_retrieving_last_page() {
270 let contracts =
271 ResponseContracts::new(ContractType::Github).add_body::<String>(200, None, None);
272 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
273 assert_eq!(Some(1), github.num_pages().unwrap());
274 }
275
276 #[test]
277 fn test_list_actions_from_page_set_in_url() {
278 let contracts = ResponseContracts::new(ContractType::Github).add_contract(
279 200,
280 "list_pipelines.json",
281 None,
282 );
283 let (client, github) = setup_client!(contracts, default_github(), dyn Cicd);
284 let args = PipelineBodyArgs::builder()
285 .from_to_page(Some(
286 ListBodyArgs::builder()
287 .page(2)
288 .max_pages(3)
289 .build()
290 .unwrap(),
291 ))
292 .build()
293 .unwrap();
294 github.list(args).unwrap();
295 assert_eq!(
296 "https://api.github.com/repos/jordilin/githapi/actions/runs?page=2",
297 *client.url(),
298 );
299 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
300 }
301
302 #[test]
303 fn test_list_actions_conclusion_field_not_available_use_status() {
304 let contract_json = get_contract(ContractType::Github, "list_pipelines.json");
305 let contract_json = contract_json
306 .lines()
307 .filter(|line| !line.contains("conclusion"))
308 .collect::<Vec<&str>>()
309 .join("\n");
310 let contracts = ResponseContracts::new(ContractType::Github).add_body::<String>(
311 200,
312 Some(contract_json),
313 None,
314 );
315 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
316 let args = PipelineBodyArgs::builder()
317 .from_to_page(None)
318 .build()
319 .unwrap();
320 let runs = github.list(args).unwrap();
321 assert_eq!("completed", runs[0].status);
322 }
323
324 #[test]
325 fn test_list_actions_neither_conclusion_nor_status_use_unknown() {
326 let contract_json = get_contract(ContractType::Github, "list_pipelines.json");
327 let contract_json = contract_json
328 .lines()
329 .filter(|line| !line.contains("conclusion") && !line.contains("status"))
330 .collect::<Vec<&str>>()
331 .join("\n");
332 let contracts = ResponseContracts::new(ContractType::Github).add_body::<String>(
333 200,
334 Some(contract_json),
335 None,
336 );
337 let (_, github) = setup_client!(contracts, default_github(), dyn Cicd);
338 let args = PipelineBodyArgs::builder()
339 .from_to_page(None)
340 .build()
341 .unwrap();
342 let runs = github.list(args).unwrap();
343 assert_eq!("unknown", runs[0].status);
344 }
345}