1use super::Gitlab;
2use crate::api_traits::{ApiOperation, CicdJob, CicdRunner};
3use crate::cmds::cicd::{
4 Job, JobListBodyArgs, LintResponse, Pipeline, PipelineBodyArgs, Runner, RunnerListBodyArgs,
5 RunnerMetadata, RunnerPostDataCliArgs, RunnerRegistrationResponse, RunnerStatus, YamlBytes,
6};
7use crate::http::{self, Body, Headers};
8use crate::remote::{query, URLQueryParamBuilder};
9use crate::{
10 api_traits::Cicd,
11 io::{HttpResponse, HttpRunner},
12};
13use crate::{time, Result};
14
15impl<R: HttpRunner<Response = HttpResponse>> Cicd for Gitlab<R> {
16 fn list(&self, args: PipelineBodyArgs) -> Result<Vec<Pipeline>> {
17 let url = format!("{}/pipelines", self.rest_api_basepath());
18 query::paged(
19 &self.runner,
20 &url,
21 args.from_to_page,
22 self.headers(),
23 None,
24 ApiOperation::Pipeline,
25 |value| GitlabPipelineFields::from(value).into(),
26 )
27 }
28
29 fn get_pipeline(&self, _id: i64) -> Result<Pipeline> {
30 todo!();
31 }
32
33 fn num_pages(&self) -> Result<Option<u32>> {
34 let (url, headers) = self.resource_cicd_metadata_url();
35 query::num_pages(&self.runner, &url, headers, ApiOperation::Pipeline)
36 }
37
38 fn num_resources(&self) -> Result<Option<crate::api_traits::NumberDeltaErr>> {
39 let (url, headers) = self.resource_cicd_metadata_url();
40 query::num_resources(&self.runner, &url, headers, ApiOperation::Pipeline)
41 }
42
43 fn lint(&self, body: YamlBytes) -> Result<LintResponse> {
45 let url = format!("{}/ci/lint", self.rest_api_basepath());
46 let mut payload = Body::new();
47 payload.add("content", body.to_string());
48 query::send(
49 &self.runner,
50 &url,
51 Some(&payload),
52 self.headers(),
53 ApiOperation::Pipeline,
54 |value| GitlabLintResponseFields::from(value).into(),
55 http::Method::POST,
56 )
57 }
58}
59
60impl<R: HttpRunner<Response = HttpResponse>> CicdRunner for Gitlab<R> {
61 fn list(&self, args: RunnerListBodyArgs) -> Result<Vec<crate::cmds::cicd::Runner>> {
62 let url = self.list_runners_url(&args, false);
63 query::paged(
64 &self.runner,
65 &url,
66 args.list_args,
67 self.headers(),
68 None,
69 ApiOperation::Pipeline,
70 |value| GitlabRunnerFields::from(value).into(),
71 )
72 }
73
74 fn get(&self, id: i64) -> Result<RunnerMetadata> {
75 let url = format!("{}/{}", self.base_runner_url, id);
76 query::get::<_, (), _>(
77 &self.runner,
78 &url,
79 None,
80 self.headers(),
81 ApiOperation::Pipeline,
82 |value| GitlabRunnerMetadataFields::from(value).into(),
83 )
84 }
85
86 fn num_pages(&self, args: RunnerListBodyArgs) -> Result<Option<u32>> {
87 let url = self.list_runners_url(&args, true);
88 query::num_pages(&self.runner, &url, self.headers(), ApiOperation::Pipeline)
89 }
90
91 fn num_resources(
92 &self,
93 args: RunnerListBodyArgs,
94 ) -> Result<Option<crate::api_traits::NumberDeltaErr>> {
95 let url = self.list_runners_url(&args, true);
96 query::num_resources(&self.runner, &url, self.headers(), ApiOperation::Pipeline)
97 }
98
99 fn create(&self, args: RunnerPostDataCliArgs) -> Result<RunnerRegistrationResponse> {
105 let url = format!("{}/runners", self.base_current_user_url);
106 let mut body = Body::new();
107 if args.description.is_some() {
108 body.add("description", args.description.unwrap());
109 }
110 if !args.run_untagged {
115 body.add("run_untagged", "false".to_string());
116 }
117 if args.tags.is_some() {
118 body.add("tag_list", args.tags.unwrap());
119 }
120 if args.project_id.is_some() {
121 body.add("project_id", args.project_id.unwrap().to_string());
122 }
123 if args.group_id.is_some() {
124 body.add("group_id", args.group_id.unwrap().to_string());
125 }
126 body.add("runner_type", args.kind.to_string());
127
128 query::send(
129 &self.runner,
130 &url,
131 Some(&body),
132 self.headers(),
133 ApiOperation::Pipeline,
134 |value| GitlabCreateRunnerFields::from(value).into(),
135 http::Method::POST,
136 )
137 }
138}
139
140pub struct GitlabCreateRunnerFields {
141 field: RunnerRegistrationResponse,
142}
143
144impl From<&serde_json::Value> for GitlabCreateRunnerFields {
145 fn from(data: &serde_json::Value) -> Self {
146 GitlabCreateRunnerFields {
147 field: RunnerRegistrationResponse::builder()
148 .id(data["id"].as_i64().unwrap_or_default())
149 .token(data["token"].as_str().unwrap_or_default().to_string())
150 .token_expiration(
151 data["token_expiration"]
152 .as_str()
153 .unwrap_or_default()
154 .to_string(),
155 )
156 .build()
157 .unwrap(),
158 }
159 }
160}
161
162impl From<GitlabCreateRunnerFields> for RunnerRegistrationResponse {
163 fn from(fields: GitlabCreateRunnerFields) -> Self {
164 fields.field
165 }
166}
167
168pub struct GitlabCicdJobFields {
169 job: Job,
170}
171
172impl From<&serde_json::Value> for GitlabCicdJobFields {
173 fn from(data: &serde_json::Value) -> Self {
174 GitlabCicdJobFields {
175 job: Job::builder()
176 .id(data["id"].as_i64().unwrap_or_default())
177 .name(data["name"].as_str().unwrap_or_default().to_string())
178 .branch(data["ref"].as_str().unwrap_or_default().to_string())
179 .url(data["web_url"].as_str().unwrap_or_default().to_string())
180 .author_name(
181 data["user"]["name"]
182 .as_str()
183 .unwrap_or_default()
184 .to_string(),
185 )
186 .commit_sha(
187 data["commit"]["id"]
188 .as_str()
189 .unwrap_or_default()
190 .to_string(),
191 )
192 .pipeline_id(data["pipeline"]["id"].as_i64().unwrap_or_default())
193 .runner_tags(
194 data["tag_list"]
195 .as_array()
196 .unwrap()
197 .iter()
198 .map(|v| v.as_str().unwrap().to_string())
199 .collect(),
200 )
201 .stage(data["stage"].as_str().unwrap_or_default().to_string())
202 .status(data["status"].as_str().unwrap_or_default().to_string())
203 .created_at(data["created_at"].as_str().unwrap_or_default().to_string())
204 .started_at(data["started_at"].as_str().unwrap_or_default().to_string())
205 .finished_at(data["finished_at"].as_str().unwrap_or_default().to_string())
206 .duration(data["duration"].as_f64().unwrap_or_default().to_string())
207 .build()
208 .unwrap(),
209 }
210 }
211}
212
213impl From<GitlabCicdJobFields> for Job {
214 fn from(fields: GitlabCicdJobFields) -> Self {
215 fields.job
216 }
217}
218
219impl<R: HttpRunner<Response = HttpResponse>> CicdJob for Gitlab<R> {
220 fn list(&self, args: JobListBodyArgs) -> Result<Vec<Job>> {
222 let url = format!("{}/jobs", self.rest_api_basepath());
223 query::paged(
224 &self.runner,
225 &url,
226 args.list_args,
227 self.headers(),
228 None,
229 ApiOperation::Pipeline,
230 |value| GitlabCicdJobFields::from(value).into(),
231 )
232 }
233
234 fn num_pages(&self, _args: JobListBodyArgs) -> Result<Option<u32>> {
235 let url = format!("{}/jobs?page=1", self.rest_api_basepath());
236 query::num_pages(&self.runner, &url, self.headers(), ApiOperation::Pipeline)
237 }
238
239 fn num_resources(
240 &self,
241 _args: JobListBodyArgs,
242 ) -> Result<Option<crate::api_traits::NumberDeltaErr>> {
243 let url = format!("{}/jobs?page=1", self.rest_api_basepath());
244 query::num_resources(&self.runner, &url, self.headers(), ApiOperation::Pipeline)
245 }
246}
247
248impl<R> Gitlab<R> {
249 fn list_runners_url(&self, args: &RunnerListBodyArgs, num_pages: bool) -> String {
250 let base_url = if args.all {
251 format!("{}/all", self.base_runner_url)
252 } else {
253 format!("{}/runners", self.rest_api_basepath(),)
254 };
255 let mut url = URLQueryParamBuilder::new(&base_url);
256 match args.status {
257 RunnerStatus::All => {}
258 _ => {
259 url.add_param("status", &args.status.to_string());
260 }
261 }
262 if num_pages {
263 url.add_param("page", "1");
264 }
265 if let Some(tags) = &args.tags {
266 url.add_param("tag_list", tags);
267 }
268 url.build()
269 }
270
271 fn resource_cicd_metadata_url(&self) -> (String, Headers) {
272 let url = format!("{}/pipelines?page=1", self.rest_api_basepath());
273 let mut headers = Headers::new();
274 headers.set("PRIVATE-TOKEN", self.api_token());
275 (url, headers)
276 }
277}
278
279pub struct GitlabRunnerFields {
280 id: i64,
281 description: String,
282 ip_address: String,
283 active: bool,
284 paused: bool,
285 is_shared: bool,
286 runner_type: String,
287 name: String,
288 online: bool,
289 status: String,
290}
291
292impl From<&serde_json::Value> for GitlabRunnerFields {
293 fn from(value: &serde_json::Value) -> Self {
294 Self {
295 id: value["id"].as_i64().unwrap(),
296 description: value["description"]
297 .as_str()
298 .unwrap_or_default()
299 .to_string(),
300 ip_address: value["ip_address"].as_str().unwrap_or_default().to_string(),
301 active: value["active"].as_bool().unwrap_or_default(),
302 paused: value["paused"].as_bool().unwrap_or_default(),
303 is_shared: value["is_shared"].as_bool().unwrap_or_default(),
304 runner_type: value["runner_type"]
305 .as_str()
306 .unwrap_or_default()
307 .to_string(),
308 name: value["name"].as_str().unwrap_or_default().to_string(),
309 online: value["online"].as_bool().unwrap_or_default(),
310 status: value["status"].as_str().unwrap_or_default().to_string(),
311 }
312 }
313}
314
315impl From<GitlabRunnerFields> for Runner {
316 fn from(fields: GitlabRunnerFields) -> Self {
317 Runner::builder()
318 .id(fields.id)
319 .description(fields.description)
320 .ip_address(fields.ip_address)
321 .active(fields.active)
322 .paused(fields.paused)
323 .is_shared(fields.is_shared)
324 .runner_type(fields.runner_type)
325 .name(fields.name)
326 .online(fields.online)
327 .status(fields.status)
328 .build()
329 .unwrap()
330 }
331}
332
333pub struct GitlabRunnerMetadataFields {
334 pub id: i64,
335 pub run_untagged: bool,
336 pub tag_list: Vec<String>,
337 pub version: String,
338 pub architecture: String,
339 pub platform: String,
340 pub contacted_at: String,
341 pub revision: String,
342}
343
344impl From<&serde_json::Value> for GitlabRunnerMetadataFields {
345 fn from(value: &serde_json::Value) -> Self {
346 Self {
347 id: value["id"].as_i64().unwrap(),
348 run_untagged: value["run_untagged"].as_bool().unwrap(),
349 tag_list: value["tag_list"]
350 .as_array()
351 .unwrap()
352 .iter()
353 .map(|v| v.as_str().unwrap().to_string())
354 .collect(),
355 version: value["version"].as_str().unwrap().to_string(),
356 architecture: value["architecture"].as_str().unwrap().to_string(),
357 platform: value["platform"].as_str().unwrap().to_string(),
358 contacted_at: value["contacted_at"].as_str().unwrap().to_string(),
359 revision: value["revision"].as_str().unwrap().to_string(),
360 }
361 }
362}
363
364impl From<GitlabRunnerMetadataFields> for RunnerMetadata {
365 fn from(fields: GitlabRunnerMetadataFields) -> Self {
366 RunnerMetadata::builder()
367 .id(fields.id)
368 .run_untagged(fields.run_untagged)
369 .tag_list(fields.tag_list)
370 .version(fields.version)
371 .architecture(fields.architecture)
372 .platform(fields.platform)
373 .contacted_at(fields.contacted_at)
374 .revision(fields.revision)
375 .build()
376 .unwrap()
377 }
378}
379
380pub struct GitlabPipelineFields {
381 pipeline: Pipeline,
382}
383
384impl From<&serde_json::Value> for GitlabPipelineFields {
385 fn from(data: &serde_json::Value) -> Self {
386 GitlabPipelineFields {
387 pipeline: Pipeline::builder()
388 .id(data["id"].as_i64().unwrap_or_default())
389 .status(data["status"].as_str().unwrap().to_string())
390 .web_url(data["web_url"].as_str().unwrap().to_string())
391 .branch(data["ref"].as_str().unwrap().to_string())
392 .sha(data["sha"].as_str().unwrap().to_string())
393 .created_at(data["created_at"].as_str().unwrap().to_string())
394 .updated_at(data["updated_at"].as_str().unwrap().to_string())
395 .duration(time::compute_duration(
396 data["created_at"].as_str().unwrap(),
397 data["updated_at"].as_str().unwrap(),
398 ))
399 .build()
400 .unwrap(),
401 }
402 }
403}
404
405impl From<GitlabPipelineFields> for Pipeline {
406 fn from(fields: GitlabPipelineFields) -> Self {
407 fields.pipeline
408 }
409}
410
411pub struct GitlabLintResponseFields {
412 lint_response: LintResponse,
413}
414
415impl From<&serde_json::Value> for GitlabLintResponseFields {
416 fn from(data: &serde_json::Value) -> Self {
417 GitlabLintResponseFields {
418 lint_response: LintResponse::builder()
419 .valid(data["valid"].as_bool().unwrap())
420 .errors(
421 data["errors"]
422 .as_array()
423 .unwrap()
424 .iter()
425 .map(|v| v.as_str().unwrap().to_string())
426 .collect(),
427 )
428 .merged_yaml(data["merged_yaml"].as_str().unwrap().to_string())
429 .build()
430 .unwrap(),
431 }
432 }
433}
434
435impl From<GitlabLintResponseFields> for LintResponse {
436 fn from(fields: GitlabLintResponseFields) -> Self {
437 fields.lint_response
438 }
439}
440
441#[cfg(test)]
442mod test {
443
444 use crate::cmds::cicd::{RunnerStatus, RunnerType};
445 use crate::remote::ListBodyArgs;
446 use crate::setup_client;
447 use crate::test::utils::{default_gitlab, ContractType, ResponseContracts};
448
449 use super::*;
450
451 #[test]
452 fn test_list_pipelines_ok() {
453 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
454 200,
455 "list_pipelines.json",
456 None,
457 );
458 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
459 let pipelines = gitlab.list(default_pipeline_body_args()).unwrap();
460
461 assert_eq!(3, pipelines.len());
462 assert_eq!(
463 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/pipelines",
464 *client.url(),
465 );
466 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
467 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
468 }
469
470 #[test]
471 fn test_list_pipelines_with_stream_ok() {
472 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
473 200,
474 "list_pipelines.json",
475 None,
476 );
477 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
478 let pipelines = gitlab
479 .list(
480 PipelineBodyArgs::builder()
481 .from_to_page(Some(ListBodyArgs::builder().flush(true).build().unwrap()))
482 .build()
483 .unwrap(),
484 )
485 .unwrap();
486 assert_eq!(0, pipelines.len());
489 }
490
491 fn default_pipeline_body_args() -> PipelineBodyArgs {
492 let body_args = PipelineBodyArgs::builder()
493 .from_to_page(None)
494 .build()
495 .unwrap();
496 body_args
497 }
498
499 #[test]
500 fn test_list_pipelines_error() {
501 let contracts =
502 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(400, None, None);
503 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
504 assert!(gitlab.list(default_pipeline_body_args()).is_err());
505 }
506
507 #[test]
508 fn test_no_pipelines() {
509 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
510 200,
511 "no_pipelines.json",
512 None,
513 );
514 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
515 let pipelines = gitlab.list(default_pipeline_body_args()).unwrap();
516 assert_eq!(0, pipelines.len());
517 }
518
519 #[test]
520 fn test_pipeline_page_from_set_in_url() {
521 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
522 200,
523 "list_pipelines.json",
524 None,
525 );
526 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
527 let fromtopage_args = ListBodyArgs::builder()
528 .page(2)
529 .max_pages(2)
530 .build()
531 .unwrap();
532 let body_args = PipelineBodyArgs::builder()
533 .from_to_page(Some(fromtopage_args))
534 .build()
535 .unwrap();
536 gitlab.list(body_args).unwrap();
537 assert_eq!(
538 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/pipelines?page=2",
539 *client.url(),
540 );
541 }
542
543 #[test]
544 fn test_gitlab_implements_num_pages_pipeline_operation() {
545 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/pipelines?page=2>; rel=\"next\", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/pipelines?page=2>; rel=\"last\"";
546 let mut headers = Headers::new();
547 headers.set("link", link_header);
548 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
549 200,
550 None,
551 Some(headers),
552 );
553 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
554 assert_eq!(Some(2), gitlab.num_pages().unwrap());
555 assert_eq!(
556 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/pipelines?page=1",
557 *client.url(),
558 );
559 }
560
561 #[test]
562 fn test_gitlab_num_pages_pipeline_no_last_header_in_link() {
563 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/pipelines?page=2>; rel=\"next\"";
564 let mut headers = Headers::new();
565 headers.set("link", link_header);
566 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
567 200,
568 None,
569 Some(headers),
570 );
571 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
572 assert_eq!(None, gitlab.num_pages().unwrap());
573 }
574
575 #[test]
576 fn test_gitlab_num_pages_pipeline_operation_response_error_is_error() {
577 let contracts =
578 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(400, None, None);
579 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
580 assert!(gitlab.num_pages().is_err());
581 }
582
583 #[test]
584 fn test_list_project_runners() {
585 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
586 200,
587 "list_project_runners.json",
588 None,
589 );
590 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
591 let body_args = RunnerListBodyArgs::builder()
592 .status(RunnerStatus::Online)
593 .list_args(None)
594 .build()
595 .unwrap();
596 gitlab.list(body_args).unwrap();
597 assert_eq!(
598 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/runners?status=online",
599 *client.url(),
600 );
601 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
602 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
603 }
604
605 #[test]
606 fn test_project_runner_num_pages() {
607 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/runners?status=online&page=1>; rel=\"first\", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/runners?status=online&page=1>; rel=\"last\"";
608 let mut headers = Headers::new();
609 headers.set("link", link_header);
610 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
611 200,
612 None,
613 Some(headers),
614 );
615 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
616 let body_args = RunnerListBodyArgs::builder()
617 .status(RunnerStatus::Online)
618 .list_args(None)
619 .build()
620 .unwrap();
621 let num_pages = gitlab.num_pages(body_args).unwrap();
622 assert_eq!(
623 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/runners?status=online&page=1",
624 *client.url(),
625 );
626 assert_eq!(Some(1), num_pages);
627 }
628
629 #[test]
630 fn test_get_gitlab_runner_metadata() {
631 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
632 200,
633 "get_runner_details.json",
634 None,
635 );
636 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
637 gitlab.get(11573930).unwrap();
638 assert_eq!("https://gitlab.com/api/v4/runners/11573930", *client.url(),);
639 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
640 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
641 }
642
643 #[test]
644 fn test_list_gitlab_runners_with_a_tag_list() {
645 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
646 200,
647 "list_project_runners.json",
648 None,
649 );
650 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
651 let body_args = RunnerListBodyArgs::builder()
652 .status(RunnerStatus::Online)
653 .list_args(None)
654 .tags(Some("tag1,tag2".to_string()))
655 .build()
656 .unwrap();
657 gitlab.list(body_args).unwrap();
658 assert_eq!(
659 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/runners?status=online&tag_list=tag1,tag2",
660 *client.url(),
661 );
662 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
663 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
664 }
665
666 #[test]
667 fn test_get_all_gitlab_runners() {
668 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
669 200,
670 "list_project_runners.json",
673 None,
674 );
675 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
676 let body_args = RunnerListBodyArgs::builder()
677 .status(RunnerStatus::Online)
678 .list_args(None)
679 .all(true)
680 .build()
681 .unwrap();
682 let runners = gitlab.list(body_args).unwrap();
683 assert_eq!(
684 "https://gitlab.com/api/v4/runners/all?status=online",
685 *client.url(),
686 );
687 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
688 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
689 assert_eq!(2, runners.len());
690 }
691
692 #[test]
693 fn test_get_all_gitlab_runners_stream_ok() {
694 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
695 200,
696 "list_project_runners.json",
699 None,
700 );
701 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
702 let body_args = RunnerListBodyArgs::builder()
703 .status(RunnerStatus::Online)
704 .list_args(Some(ListBodyArgs::builder().flush(true).build().unwrap()))
705 .all(true)
706 .build()
707 .unwrap();
708 let runners = gitlab.list(body_args).unwrap();
709 assert_eq!(
710 "https://gitlab.com/api/v4/runners/all?status=online",
711 *client.url(),
712 );
713 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
714 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
715 assert_eq!(0, runners.len());
718 }
719
720 #[test]
721 fn test_get_project_runners_in_any_status() {
722 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
723 200,
724 "list_project_runners.json",
725 None,
726 );
727 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
728 let body_args = RunnerListBodyArgs::builder()
729 .status(RunnerStatus::All)
730 .list_args(None)
731 .build()
732 .unwrap();
733 gitlab.list(body_args).unwrap();
734 assert_eq!(
735 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/runners",
736 *client.url(),
737 );
738 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
739 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
740 }
741
742 #[test]
743 fn test_all_runners_at_any_status_with_tags() {
744 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
745 200,
746 "list_project_runners.json",
747 None,
748 );
749 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
750 let body_args = RunnerListBodyArgs::builder()
751 .status(RunnerStatus::All)
752 .list_args(None)
753 .tags(Some("tag1,tag2".to_string()))
754 .all(true)
755 .build()
756 .unwrap();
757 gitlab.list(body_args).unwrap();
758 assert_eq!(
759 "https://gitlab.com/api/v4/runners/all?tag_list=tag1,tag2",
760 *client.url(),
761 );
762 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
763 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
764 }
765
766 #[test]
767 fn test_all_runners_at_any_status_with_tags_num_pages() {
768 let link_header = "<https://gitlab.com/api/v4/runners/all?tag_list=tag1,tag2&page=1>; rel=\"first\", <https://gitlab.com/api/v4/runners/all?tag_list=tag1,tag2&page=1>; rel=\"last\"";
769 let mut headers = Headers::new();
770 headers.set("link", link_header);
771 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
772 200,
773 None,
774 Some(headers),
775 );
776 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
777 let body_args = RunnerListBodyArgs::builder()
778 .status(RunnerStatus::All)
779 .list_args(None)
780 .tags(Some("tag1,tag2".to_string()))
781 .all(true)
782 .build()
783 .unwrap();
784 let num_pages = gitlab.num_pages(body_args).unwrap();
785 assert_eq!(
786 "https://gitlab.com/api/v4/runners/all?page=1&tag_list=tag1,tag2",
787 *client.url(),
788 );
789 assert_eq!(Some(1), num_pages);
790 }
791
792 fn gen_gitlab_ci_body<'a>() -> YamlBytes<'a> {
793 YamlBytes::new(
794 b"image: alpine\n\
795 stages:\n\
796 - build\n\
797 build:\n\
798 stage: build\n\
799 script:\n\
800 - echo \"Building\"\n",
801 )
802 }
803
804 #[test]
805 fn test_lint_ci_file_ok() {
806 let contracts =
807 ResponseContracts::new(ContractType::Gitlab).add_contract(201, "ci_lint_ok.json", None);
808 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
809 let response = gitlab.lint(gen_gitlab_ci_body()).unwrap();
810 assert!(response.valid);
811 assert_eq!(
812 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/ci/lint",
813 *client.url()
814 );
815 }
816
817 #[test]
818 fn test_lint_ci_file_error() {
819 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
820 201,
821 "ci_lint_error.json",
822 None,
823 );
824 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn Cicd);
825 let result = gitlab.lint(gen_gitlab_ci_body());
826 assert!(result.is_ok());
827 let response = result.unwrap();
828 assert!(!response.valid);
829 assert!(!response.errors.is_empty());
830 }
831
832 #[test]
833 fn test_gitlab_project_pipeline_jobs() {
834 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
835 200,
836 "list_project_jobs.json",
837 None,
838 );
839 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdJob);
840 let body_args = JobListBodyArgs::builder().list_args(None).build().unwrap();
841 gitlab.list(body_args).unwrap();
842 assert_eq!(
843 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/jobs",
844 *client.url()
845 );
846 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
847 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
848 }
849
850 #[test]
851 fn test_gitlab_project_jobs_num_pages() {
852 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/jobs?page=2>; rel=\"next\", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/jobs?page=2>; rel=\"last\"";
853 let mut headers = Headers::new();
854 headers.set("link", link_header);
855 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
856 200,
857 None,
858 Some(headers),
859 );
860 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdJob);
861 let body_args = JobListBodyArgs::builder().list_args(None).build().unwrap();
862 let num_pages = gitlab.num_pages(body_args).unwrap();
863 assert_eq!(
864 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/jobs?page=1",
865 *client.url(),
866 );
867 assert_eq!(Some(2), num_pages);
868 }
869
870 #[test]
871 fn test_gitlab_project_jobs_num_resources() {
872 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
873 200,
874 None,
875 Some(Headers::new()),
876 );
877 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdJob);
878 let body_args = JobListBodyArgs::builder().list_args(None).build().unwrap();
879 let num_resources = gitlab.num_resources(body_args).unwrap();
880 assert_eq!(
881 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/jobs?page=1",
882 *client.url()
883 );
884 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
885 assert_eq!("(1, 30)", &num_resources.unwrap().to_string());
886 }
887
888 #[test]
889 fn test_gitlab_create_auth_token_based_instance_runner_with_description_and_tags() {
890 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
891 201,
892 "create_auth_runner_response.json",
893 None,
894 );
895 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
896 let args = RunnerPostDataCliArgs::builder()
897 .description(Some("My runner".to_string()))
898 .tags(Some("tag1,tag2".to_string()))
899 .kind(RunnerType::Instance)
900 .build()
901 .unwrap();
902 let response = gitlab.create(args).unwrap();
903 assert_eq!("https://gitlab.com/api/v4/user/runners", *client.url(),);
904 assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
905 assert_eq!(Some(ApiOperation::Pipeline), *client.api_operation.borrow());
906 assert_eq!("newtoken", response.token);
907 let body = client.request_body();
908 assert!(body.contains("description"));
909 assert!(body.contains("tag_list"));
910 assert!(body.contains("instance_type"));
911 }
912
913 #[test]
914 fn test_create_new_runner_tag_list_and_untagged() {
915 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
916 201,
917 "create_auth_runner_response.json",
918 None,
919 );
920 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
921 let args = RunnerPostDataCliArgs::builder()
922 .description(Some("My runner".to_string()))
923 .tags(Some("tag1,tag2".to_string()))
924 .kind(RunnerType::Instance)
925 .run_untagged(true)
926 .build()
927 .unwrap();
928 gitlab.create(args).unwrap();
929 let body = client.request_body();
930 assert!(body.contains("tag_list"));
931 assert!(!body.contains("run_untagged"));
933 }
934
935 #[test]
936 fn test_create_new_runner_untagged_only() {
937 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
938 201,
939 "create_auth_runner_response.json",
940 None,
941 );
942 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
943 let args = RunnerPostDataCliArgs::builder()
944 .description(Some("My runner".to_string()))
945 .kind(RunnerType::Instance)
946 .tags(None)
947 .run_untagged(true)
948 .build()
949 .unwrap();
950 gitlab.create(args).unwrap();
951 let body = client.request_body();
952 assert!(!body.contains("tag_list"));
953 assert!(!body.contains("run_untagged"));
955 }
956
957 #[test]
958 fn test_create_new_runner_tag_list_only() {
959 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
960 201,
961 "create_auth_runner_response.json",
962 None,
963 );
964 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
965 let args = RunnerPostDataCliArgs::builder()
966 .description(Some("My runner".to_string()))
967 .tags(Some("tag1,tag2".to_string()))
968 .kind(RunnerType::Instance)
969 .run_untagged(false)
970 .build()
971 .unwrap();
972 gitlab.create(args).unwrap();
973 let body = client.request_body();
974 assert!(body.contains("tag_list"));
975 assert!(body.contains("\"run_untagged\":\"false\""));
977 }
978
979 #[test]
980 fn create_project_runner_has_project_id_in_payload() {
981 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
982 201,
983 "create_auth_runner_response.json",
984 None,
985 );
986 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
987 let args = RunnerPostDataCliArgs::builder()
988 .description(Some("My runner".to_string()))
989 .tags(Some("tag1,tag2".to_string()))
990 .project_id(Some(1234))
991 .kind(RunnerType::Project)
992 .build()
993 .unwrap();
994 gitlab.create(args).unwrap();
995 let body = client.request_body();
996 assert!(body.contains("project_id"));
997 }
998
999 #[test]
1000 fn create_group_runner_has_group_id_in_payload() {
1001 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
1002 201,
1003 "create_auth_runner_response.json",
1004 None,
1005 );
1006 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CicdRunner);
1007 let args = RunnerPostDataCliArgs::builder()
1008 .description(Some("My group runner".to_string()))
1009 .tags(Some("tag1,tag2".to_string()))
1010 .group_id(Some(1234))
1011 .kind(RunnerType::Group)
1012 .build()
1013 .unwrap();
1014 gitlab.create(args).unwrap();
1015 let body = client.request_body();
1016 assert!(body.contains("group_id"));
1017 }
1018}