1use crate::api_traits::{ApiOperation, CommentMergeRequest, NumberDeltaErr, RemoteProject};
2use crate::cli::browse::BrowseOptions;
3use crate::cmds::merge_request::{
4 Comment, CommentMergeRequestBodyArgs, CommentMergeRequestListBodyArgs, MergeRequestBodyArgs,
5 MergeRequestListBodyArgs, MergeRequestResponse,
6};
7use crate::cmds::project::MrMemberType;
8use crate::error::{self, GRError};
9use crate::http::{self, Body, Headers};
10use crate::io::CmdInfo;
11use crate::remote::query;
12use crate::Result;
13use crate::{
14 api_traits::MergeRequest,
15 io::{HttpResponse, HttpRunner},
16};
17
18use crate::json_loads;
19
20use super::Gitlab;
21
22impl<R: HttpRunner<Response = HttpResponse>> MergeRequest for Gitlab<R> {
23 fn open(&self, args: MergeRequestBodyArgs) -> Result<MergeRequestResponse> {
24 let mut body = Body::new();
25 body.add("source_branch", args.source_branch);
26 body.add("target_branch", args.target_branch);
27 body.add("title", args.title);
28 match args.assignee.mr_member_type {
29 MrMemberType::Filled => {
30 body.add("assignee_id", args.assignee.id.to_string());
31 }
32 MrMemberType::Empty => {}
33 }
34 match args.reviewer.mr_member_type {
35 MrMemberType::Filled => {
36 body.add("reviewer_ids", args.reviewer.id.to_string());
39 }
40 MrMemberType::Empty => {}
41 }
42 body.add("description", args.description);
43 body.add("remove_source_branch", args.remove_source_branch);
44 if !args.target_repo.is_empty() {
46 match self.get_project_data(None, Some(&args.target_repo)) {
47 Ok(CmdInfo::Project(project)) => {
48 body.add("target_project_id", project.id.to_string());
49 }
50 Ok(_) => {
51 return Err(GRError::ApplicationError(
53 "Failed to get target project data".to_string(),
54 )
55 .into());
56 }
57 Err(e) => {
58 return Err(error::gen(format!(
59 "Could not get target project data for {} with error {}",
60 args.target_repo, e
61 )))
62 }
63 }
64 }
65 let url = format!("{}/merge_requests", self.rest_api_basepath());
66 let response = query::send_raw(
67 &self.runner,
68 &url,
69 Some(&body),
70 self.headers(),
71 ApiOperation::MergeRequest,
72 http::Method::POST,
73 )?;
74 if response.status == 409 {
78 let merge_request_json: serde_json::Value = serde_json::from_str(&response.body)?;
81 let merge_request_iid = merge_request_json["message"][0]
82 .as_str()
83 .unwrap()
84 .split_whitespace()
85 .last()
86 .unwrap()
87 .trim_matches('!');
88 if args.amend {
89 let url = format!(
90 "{}/merge_requests/{}",
91 self.rest_api_basepath(),
92 merge_request_iid
93 );
94 query::send_raw(
95 &self.runner,
96 &url,
97 Some(&body),
98 self.headers(),
99 ApiOperation::MergeRequest,
100 http::Method::PUT,
101 )?;
102 }
103 let merge_request_url = format!(
104 "https://{}/{}/-/merge_requests/{}",
105 self.domain, self.path, merge_request_iid
106 );
107 return Ok(MergeRequestResponse::builder()
108 .id(merge_request_iid.parse().unwrap())
109 .web_url(merge_request_url)
110 .build()
111 .unwrap());
112 }
113 if response.status != 201 {
114 return Err(error::gen(format!(
115 "Failed to open merge request: {}",
116 response.body
117 )));
118 }
119 let merge_request_json = json_loads(&response.body)?;
120
121 Ok(MergeRequestResponse::builder()
122 .id(merge_request_json["iid"].as_i64().unwrap())
123 .web_url(merge_request_json["web_url"].as_str().unwrap().to_string())
124 .build()
125 .unwrap())
126 }
127
128 fn list(&self, args: MergeRequestListBodyArgs) -> Result<Vec<MergeRequestResponse>> {
129 let url = self.list_merge_request_url(&args, false);
130 query::paged(
131 &self.runner,
132 &url,
133 args.list_args,
134 self.headers(),
135 None,
136 ApiOperation::MergeRequest,
137 |value| GitlabMergeRequestFields::from(value).into(),
138 )
139 }
140
141 fn merge(&self, id: i64) -> Result<MergeRequestResponse> {
142 let url = format!("{}/merge_requests/{}/merge", self.rest_api_basepath(), id);
144 query::send::<_, (), _>(
145 &self.runner,
146 &url,
147 None,
148 self.headers(),
149 ApiOperation::MergeRequest,
150 |value| GitlabMergeRequestFields::from(value).into(),
151 http::Method::PUT,
152 )
153 }
154
155 fn get(&self, id: i64) -> Result<MergeRequestResponse> {
156 let url = format!("{}/merge_requests/{}", self.rest_api_basepath(), id);
158 query::get::<_, (), _>(
159 &self.runner,
160 &url,
161 None,
162 self.headers(),
163 ApiOperation::MergeRequest,
164 |value| GitlabMergeRequestFields::from(value).into(),
165 )
166 }
167
168 fn close(&self, id: i64) -> Result<MergeRequestResponse> {
169 let url = format!("{}/merge_requests/{}", self.rest_api_basepath(), id);
170 let mut body = Body::new();
171 body.add("state_event", "close");
172 query::send::<_, &str, _>(
173 &self.runner,
174 &url,
175 Some(&body),
176 self.headers(),
177 ApiOperation::MergeRequest,
178 |value| GitlabMergeRequestFields::from(value).into(),
179 http::Method::PUT,
180 )
181 }
182
183 fn num_pages(&self, args: MergeRequestListBodyArgs) -> Result<Option<u32>> {
184 let url = self.list_merge_request_url(&args, true);
185 let mut headers = Headers::new();
186 headers.set("PRIVATE-TOKEN", self.api_token());
187 query::num_pages(&self.runner, &url, headers, ApiOperation::MergeRequest)
188 }
189
190 fn num_resources(&self, args: MergeRequestListBodyArgs) -> Result<Option<NumberDeltaErr>> {
191 let url = self.list_merge_request_url(&args, true);
192 let mut headers = Headers::new();
193 headers.set("PRIVATE-TOKEN", self.api_token());
194 query::num_resources(&self.runner, &url, headers, ApiOperation::MergeRequest)
195 }
196
197 fn approve(&self, id: i64) -> Result<MergeRequestResponse> {
198 let url = format!("{}/merge_requests/{}/approve", self.rest_api_basepath(), id);
199 let result = query::send::<_, (), MergeRequestResponse>(
200 &self.runner,
201 &url,
202 None,
203 self.headers(),
204 ApiOperation::MergeRequest,
205 |value| GitlabMergeRequestFields::from(value).into(),
206 http::Method::POST,
207 );
208 if let Ok(mut response) = result {
211 response.web_url = self.get_url(BrowseOptions::MergeRequestId(id));
212 return Ok(response);
213 }
214 result
215 }
216}
217
218impl<R> Gitlab<R> {
219 fn list_merge_request_url(&self, args: &MergeRequestListBodyArgs, num_pages: bool) -> String {
220 let mut url = if let Some(assignee) = &args.assignee {
221 format!(
222 "{}?state={}&assignee_id={}",
223 self.merge_requests_url, args.state, assignee.id
224 )
225 } else if let Some(reviewer) = &args.reviewer {
226 format!(
227 "{}?state={}&reviewer_id={}",
228 self.merge_requests_url, args.state, reviewer.id
229 )
230 } else if let Some(author) = &args.author {
231 format!(
232 "{}?state={}&author_id={}",
233 self.merge_requests_url, args.state, author.id
234 )
235 } else {
236 format!(
237 "{}/merge_requests?state={}",
238 self.rest_api_basepath(),
239 args.state
240 )
241 };
242 if num_pages {
243 url.push_str("&page=1");
244 }
245 url
246 }
247
248 fn resource_comments_metadata_url(&self, args: CommentMergeRequestListBodyArgs) -> String {
249 let url = format!(
250 "{}/merge_requests/{}/notes?page=1",
251 self.rest_api_basepath(),
252 args.id
253 );
254 url
255 }
256}
257
258impl<R: HttpRunner<Response = HttpResponse>> CommentMergeRequest for Gitlab<R> {
259 fn create(&self, args: CommentMergeRequestBodyArgs) -> Result<()> {
260 let url = format!(
261 "{}/merge_requests/{}/notes",
262 self.rest_api_basepath(),
263 args.id
264 );
265 let mut body = Body::new();
266 body.add("body", args.comment);
267 query::send_raw(
268 &self.runner,
269 &url,
270 Some(&body),
271 self.headers(),
272 ApiOperation::MergeRequest,
273 http::Method::POST,
274 )?;
275 Ok(())
276 }
277
278 fn list(&self, args: CommentMergeRequestListBodyArgs) -> Result<Vec<Comment>> {
279 let url = format!(
280 "{}/merge_requests/{}/notes",
281 self.rest_api_basepath(),
282 args.id
283 );
284
285 query::paged(
286 &self.runner,
287 &url,
288 args.list_args,
289 self.headers(),
290 None,
291 ApiOperation::MergeRequest,
292 |value| GitlabMergeRequestCommentFields::from(value).into(),
293 )
294 }
295
296 fn num_pages(&self, args: CommentMergeRequestListBodyArgs) -> Result<Option<u32>> {
297 let url = self.resource_comments_metadata_url(args);
298 query::num_pages(
299 &self.runner,
300 &url,
301 self.headers(),
302 ApiOperation::MergeRequest,
303 )
304 }
305
306 fn num_resources(
307 &self,
308 args: CommentMergeRequestListBodyArgs,
309 ) -> Result<Option<NumberDeltaErr>> {
310 let url = self.resource_comments_metadata_url(args);
311 query::num_resources(
312 &self.runner,
313 &url,
314 self.headers(),
315 ApiOperation::MergeRequest,
316 )
317 }
318}
319
320pub struct GitlabMergeRequestFields {
321 fields: MergeRequestResponse,
322}
323
324impl From<&serde_json::Value> for GitlabMergeRequestFields {
325 fn from(data: &serde_json::Value) -> Self {
326 GitlabMergeRequestFields {
327 fields: MergeRequestResponse::builder()
328 .id(data["iid"].as_i64().unwrap_or_default())
329 .web_url(data["web_url"].as_str().unwrap_or_default().to_string())
330 .source_branch(
331 data["source_branch"]
332 .as_str()
333 .unwrap_or_default()
334 .to_string(),
335 )
336 .sha(
337 data["merge_commit_sha"]
338 .as_str()
339 .unwrap_or_default()
340 .to_string(),
341 )
342 .author(
343 data["author"]["username"]
344 .as_str()
345 .unwrap_or_default()
346 .to_string(),
347 )
348 .updated_at(data["updated_at"].as_str().unwrap_or_default().to_string())
349 .created_at(data["created_at"].as_str().unwrap_or_default().to_string())
350 .title(data["title"].as_str().unwrap_or_default().to_string())
351 .description(data["description"].as_str().unwrap_or_default().to_string())
352 .merged_at(data["merged_at"].as_str().unwrap_or_default().to_string())
354 .pipeline_id(data["head_pipeline"]["id"].as_i64())
357 .pipeline_url(
358 data["head_pipeline"]["web_url"]
359 .as_str()
360 .map(|s| s.to_string()),
361 )
362 .build()
363 .unwrap(),
364 }
365 }
366}
367
368impl From<GitlabMergeRequestFields> for MergeRequestResponse {
369 fn from(fields: GitlabMergeRequestFields) -> Self {
370 fields.fields
371 }
372}
373
374pub struct GitlabMergeRequestCommentFields {
375 comment: Comment,
376}
377
378impl From<&serde_json::Value> for GitlabMergeRequestCommentFields {
379 fn from(data: &serde_json::Value) -> Self {
380 GitlabMergeRequestCommentFields {
381 comment: Comment::builder()
382 .id(data["id"].as_i64().unwrap_or_default())
383 .body(data["body"].as_str().unwrap_or_default().to_string())
384 .author(
385 data["author"]["username"]
386 .as_str()
387 .unwrap_or_default()
388 .to_string(),
389 )
390 .created_at(data["created_at"].as_str().unwrap_or_default().to_string())
391 .build()
392 .unwrap(),
393 }
394 }
395}
396
397impl From<GitlabMergeRequestCommentFields> for Comment {
398 fn from(fields: GitlabMergeRequestCommentFields) -> Self {
399 fields.comment
400 }
401}
402
403#[cfg(test)]
404mod test {
405
406 use crate::cmds::merge_request::MergeRequestState;
407 use crate::cmds::project::Member;
408 use crate::remote::ListBodyArgs;
409 use crate::setup_client;
410 use crate::test::utils::{
411 default_gitlab, get_contract, BasePath, ClientType, ContractType, Domain, ResponseContracts,
412 };
413
414 use super::*;
415
416 #[test]
417 fn test_list_merge_request_with_from_page() {
418 let contracts =
419 ResponseContracts::new(ContractType::Gitlab).add_body(200, Some("[]"), None);
420 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
421 let args = MergeRequestListBodyArgs::builder()
422 .state(MergeRequestState::Opened)
423 .list_args(Some(
424 ListBodyArgs::builder()
425 .page(2)
426 .max_pages(2)
427 .build()
428 .unwrap(),
429 ))
430 .assignee(None)
431 .build()
432 .unwrap();
433 gitlab.list(args).unwrap();
434 assert_eq!(
435 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests?state=opened&page=2",
436 *client.url(),
437 );
438 }
439
440 #[test]
441 fn test_list_all_merge_requests_assigned_for_current_user() {
442 let contract = ResponseContracts::new(ContractType::Gitlab).add_body(200, Some("[]"), None);
443 let (client, gitlab) = setup_client!(contract, default_gitlab(), dyn MergeRequest);
444 let args = MergeRequestListBodyArgs::builder()
445 .state(MergeRequestState::Opened)
446 .list_args(None)
447 .assignee(Some(
448 Member::builder()
449 .name("tom".to_string())
450 .username("tsawyer".to_string())
451 .id(1234)
452 .build()
453 .unwrap(),
454 ))
455 .build()
456 .unwrap();
457 gitlab.list(args).unwrap();
458 assert_eq!(
459 "https://gitlab.com/api/v4/merge_requests?state=opened&assignee_id=1234",
460 *client.url(),
461 );
462 }
463
464 #[test]
465 fn test_list_all_merge_requests_auth_user_is_reviewer() {
466 let contract = ResponseContracts::new(ContractType::Gitlab).add_body(200, Some("[]"), None);
467 let (client, gitlab) = setup_client!(contract, default_gitlab(), dyn MergeRequest);
468 let args = MergeRequestListBodyArgs::builder()
469 .state(MergeRequestState::Opened)
470 .list_args(None)
471 .reviewer(Some(
472 Member::builder()
473 .name("tom".to_string())
474 .username("tsawyer".to_string())
475 .id(123)
476 .build()
477 .unwrap(),
478 ))
479 .build()
480 .unwrap();
481 gitlab.list(args).unwrap();
482 assert_eq!(
483 "https://gitlab.com/api/v4/merge_requests?state=opened&reviewer_id=123",
484 *client.url(),
485 );
486 }
487
488 #[test]
489 fn test_list_all_merge_requests_auth_user_is_the_author() {
490 let contract = ResponseContracts::new(ContractType::Gitlab).add_body(200, Some("[]"), None);
491 let (client, gitlab) = setup_client!(contract, default_gitlab(), dyn MergeRequest);
492 let args = MergeRequestListBodyArgs::builder()
493 .state(MergeRequestState::Opened)
494 .list_args(None)
495 .author(Some(
496 Member::builder()
497 .name("tom".to_string())
498 .username("tsawyer".to_string())
499 .id(192)
500 .build()
501 .unwrap(),
502 ))
503 .build()
504 .unwrap();
505 gitlab.list(args).unwrap();
506 assert_eq!(
507 "https://gitlab.com/api/v4/merge_requests?state=opened&author_id=192",
508 *client.url(),
509 );
510 }
511
512 #[test]
513 fn test_open_merge_request() {
514 let assignee = Member::builder()
515 .name("tom".to_string())
516 .username("tsawyer".to_string())
517 .mr_member_type(MrMemberType::Filled)
518 .id(1234)
519 .build()
520 .unwrap();
521 let reviewer = Member::builder()
522 .name("huck".to_string())
523 .username("hfinn".to_string())
524 .mr_member_type(MrMemberType::Filled)
525 .id(5678)
526 .build()
527 .unwrap();
528 let mr_args = MergeRequestBodyArgs::builder()
529 .assignee(assignee)
530 .reviewer(reviewer)
531 .build()
532 .unwrap();
533 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
534 201,
535 "merge_request.json",
536 None,
537 );
538 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
539 assert!(gitlab.open(mr_args).is_ok());
540 assert_eq!(
541 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests",
542 *client.url(),
543 );
544 let mut actual_method = client.http_method.borrow_mut();
545 assert_eq!(http::Method::POST, actual_method.pop().unwrap());
546 assert_eq!(
547 Some(ApiOperation::MergeRequest),
548 *client.api_operation.borrow()
549 );
550 let actual_body = client.request_body.borrow();
551 assert!(actual_body.contains("assignee_id"));
552 assert!(actual_body.contains("reviewer_ids"));
553 }
554
555 #[test]
556 fn test_open_merge_request_with_no_assignee() {
557 let assignee = Member::default();
558 let mr_args = MergeRequestBodyArgs::builder()
559 .assignee(assignee)
560 .build()
561 .unwrap();
562 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
563 201,
564 "merge_request.json",
565 None,
566 );
567 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
568 assert!(gitlab.open(mr_args).is_ok());
569 assert_eq!(
570 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests",
571 *client.url(),
572 );
573 let mut actual_method = client.http_method.borrow_mut();
574 assert_eq!(http::Method::POST, actual_method.pop().unwrap());
575 assert_eq!(
576 Some(ApiOperation::MergeRequest),
577 *client.api_operation.borrow()
578 );
579 let actual_body = client.request_body.borrow();
580 assert!(!actual_body.contains("assignee_id"));
581 }
582
583 #[test]
584 fn test_open_merge_request_target_repo() {
585 let client_type = ClientType::Gitlab(
587 Domain("gitlab.com".to_string()),
588 BasePath("jdoe/gitar".to_string()),
589 );
590 let responses = ResponseContracts::new(ContractType::Gitlab)
591 .add_contract(201, "merge_request.json", None)
592 .add_contract(200, "project.json", None);
593 let (client, gitlab) = setup_client!(responses, client_type, dyn MergeRequest);
594 let mr_args = MergeRequestBodyArgs::builder()
595 .target_repo("jordilin/gitar".to_string())
596 .build()
597 .unwrap();
598 assert!(gitlab.open(mr_args).is_ok());
599 assert_eq!(
600 "https://gitlab.com/api/v4/projects/jdoe%2Fgitar/merge_requests",
601 *client.url(),
602 );
603 assert_eq!(
604 Some(ApiOperation::MergeRequest),
605 *client.api_operation.borrow()
606 );
607 }
608
609 #[test]
610 fn test_open_merge_request_error() {
611 let contracts =
612 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(400, None, None);
613 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
614 let mr_args = MergeRequestBodyArgs::builder().build().unwrap();
615 assert!(gitlab.open(mr_args).is_err());
616 }
617
618 #[test]
619 fn test_merge_request_already_exists_status_code_409_conflict() {
620 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
621 409,
622 "merge_request_conflict.json",
623 None,
624 );
625 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
626 let mr_args = MergeRequestBodyArgs::builder().build().unwrap();
627 assert!(gitlab.open(mr_args).is_ok());
628 }
629
630 #[test]
631 fn test_amend_existing_merge_request() {
632 let contracts = ResponseContracts::new(ContractType::Gitlab)
633 .add_contract(200, "merge_request.json", None)
634 .add_contract(409, "merge_request_conflict.json", None);
635 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
636 let mr_args = MergeRequestBodyArgs::builder().amend(true).build().unwrap();
637 assert!(gitlab.open(mr_args).is_ok());
638 assert_eq!(
639 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/33",
640 *client.url()
641 );
642 let actual_method = client.http_method.borrow();
643 assert_eq!(http::Method::PUT, actual_method[1]);
644 }
645
646 #[test]
647 fn test_gitlab_merge_request_num_pages() {
648 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests?state=opened&page=1>; rel=\"next\", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests?state=opened&page=2>; rel=\"last\"";
649 let mut headers = Headers::new();
650 headers.set("link", link_header);
651 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
652 200,
653 None,
654 Some(headers),
655 );
656 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
657 let body_args = MergeRequestListBodyArgs::builder()
658 .state(MergeRequestState::Opened)
659 .list_args(None)
660 .assignee(None)
661 .build()
662 .unwrap();
663 assert_eq!(Some(2), gitlab.num_pages(body_args).unwrap());
664 assert_eq!(
665 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests?state=opened&page=1",
666 *client.url(),
667 );
668 }
669
670 #[test]
671 fn test_gitlab_merge_request_num_pages_current_auth_user() {
672 let link_header = "<https://gitlab.com/api/v4/merge_requests?state=opened&assignee_id=1234&page=1>; rel=\"next\", <https://gitlab.com/api/v4/merge_requests?state=opened&assignee_id=1234&page=2>; rel=\"last\"";
673 let mut headers = Headers::new();
674 headers.set("link", link_header);
675 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
676 200,
677 None,
678 Some(headers),
679 );
680 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
681 let body_args = MergeRequestListBodyArgs::builder()
682 .state(MergeRequestState::Opened)
683 .list_args(None)
684 .assignee(Some(
685 Member::builder()
686 .name("tom".to_string())
687 .username("tsawyer".to_string())
688 .id(1234)
689 .build()
690 .unwrap(),
691 ))
692 .build()
693 .unwrap();
694 assert_eq!(Some(2), gitlab.num_pages(body_args).unwrap());
695 assert_eq!(
696 "https://gitlab.com/api/v4/merge_requests?state=opened&assignee_id=1234&page=1",
697 *client.url(),
698 );
699 }
700
701 #[test]
702 fn test_gitlab_merge_request_num_pages_no_link_header_error() {
703 let contracts =
704 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(200, None, None);
705 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
706 let body_args = MergeRequestListBodyArgs::builder()
707 .state(MergeRequestState::Opened)
708 .list_args(None)
709 .assignee(None)
710 .build()
711 .unwrap();
712 assert_eq!(Some(1), gitlab.num_pages(body_args).unwrap());
713 }
714
715 #[test]
716 fn test_gitlab_merge_request_num_pages_response_error_is_error() {
717 let contracts =
718 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(400, None, None);
719 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
720 let body_args = MergeRequestListBodyArgs::builder()
721 .state(MergeRequestState::Opened)
722 .list_args(None)
723 .assignee(None)
724 .build()
725 .unwrap();
726 assert!(gitlab.num_pages(body_args).is_err());
727 }
728
729 #[test]
730 fn test_gitlab_merge_request_num_pages_no_last_header_in_link() {
731 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests?state=opened&page=1>; rel=\"next\"";
732 let mut headers = Headers::new();
733 headers.set("link", link_header);
734 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
735 200,
736 None,
737 Some(headers),
738 );
739 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
740 let body_args = MergeRequestListBodyArgs::builder()
741 .state(MergeRequestState::Opened)
742 .list_args(None)
743 .assignee(None)
744 .build()
745 .unwrap();
746 assert_eq!(None, gitlab.num_pages(body_args).unwrap());
747 }
748
749 #[test]
750 fn test_gitlab_create_merge_request_comment_ok() {
751 let contracts =
752 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(201, None, None);
753 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CommentMergeRequest);
754 let comment_args = CommentMergeRequestBodyArgs::builder()
755 .id(1456)
756 .comment("LGTM, ship it".to_string())
757 .build()
758 .unwrap();
759 gitlab.create(comment_args).unwrap();
760 assert_eq!(
761 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/1456/notes",
762 *client.url()
763 );
764 assert_eq!(
765 Some(ApiOperation::MergeRequest),
766 *client.api_operation.borrow()
767 );
768 }
769
770 #[test]
771 fn test_gitlab_create_merge_request_comment_error() {
772 let contracts =
773 ResponseContracts::new(ContractType::Gitlab).add_body::<String>(400, None, None);
774 let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn CommentMergeRequest);
775 let comment_args = CommentMergeRequestBodyArgs::builder()
776 .id(1456)
777 .comment("LGTM, ship it".to_string())
778 .build()
779 .unwrap();
780 assert!(gitlab.create(comment_args).is_err());
781 }
782
783 #[test]
784 fn test_get_gitlab_merge_request_details() {
785 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
786 200,
787 "merge_request.json",
788 None,
789 );
790 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
791 let merge_request_id = 123456;
792 gitlab.get(merge_request_id).unwrap();
793 assert_eq!(
794 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/123456",
795 *client.url()
796 );
797 assert_eq!(
798 Some(ApiOperation::MergeRequest),
799 *client.api_operation.borrow()
800 );
801 }
802
803 #[test]
804 fn test_merge_merge_request() {
805 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
806 200,
807 "merge_request.json",
808 None,
809 );
810 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
811 let merge_request_id = 33;
812 gitlab.merge(merge_request_id).unwrap();
813 assert_eq!(
814 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/33/merge",
815 *client.url()
816 );
817 assert_eq!(
818 Some(ApiOperation::MergeRequest),
819 *client.api_operation.borrow()
820 );
821 }
822
823 #[test]
824 fn test_close_merge_request() {
825 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
826 200,
827 "merge_request.json",
828 None,
829 );
830 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
831 let merge_request_id = 33;
832 gitlab.close(merge_request_id).unwrap();
833 assert_eq!(
834 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/33",
835 *client.url()
836 );
837 let mut actual_method = client.http_method.borrow_mut();
838 assert_eq!(http::Method::PUT, actual_method.pop().unwrap());
839 assert_eq!(
840 Some(ApiOperation::MergeRequest),
841 *client.api_operation.borrow()
842 );
843 }
844
845 #[test]
846 fn test_approve_merge_request_ok() {
847 let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
848 200,
849 "approve_merge_request.json",
850 None,
851 );
852 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn MergeRequest);
853 let merge_request_id = 33;
854 let result = gitlab.approve(merge_request_id);
855 match result {
856 Ok(response) => {
857 assert_eq!(
858 "https://gitlab.com/jordilin/gitlapi/-/merge_requests/33",
859 response.web_url
860 );
861 }
862 Err(e) => {
863 panic!(
864 "Expected Ok merge request approval but got: {:?} instead",
865 e
866 );
867 }
868 }
869 assert_eq!(
870 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/33/approve",
871 *client.url()
872 );
873 let mut actual_method = client.http_method.borrow_mut();
874 assert_eq!(http::Method::POST, actual_method.pop().unwrap());
875 assert_eq!(
876 Some(ApiOperation::MergeRequest),
877 *client.api_operation.borrow()
878 );
879 }
880
881 #[test]
882 fn test_list_merge_request_comments() {
883 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body(
884 200,
885 Some(format!(
886 "[{}]",
887 get_contract(ContractType::Gitlab, "comment.json")
888 )),
889 None,
890 );
891 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CommentMergeRequest);
892 let args = CommentMergeRequestListBodyArgs::builder()
893 .id(123)
894 .list_args(None)
895 .build()
896 .unwrap();
897 let comments = gitlab.list(args).unwrap();
898 assert_eq!(1, comments.len());
899 assert_eq!(
900 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/123/notes",
901 *client.url()
902 );
903 assert_eq!(
904 Some(ApiOperation::MergeRequest),
905 *client.api_operation.borrow()
906 );
907 }
908
909 #[test]
910 fn test_merge_request_comments_num_pages() {
911 let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/123/notes?page=1>; rel=\"next\", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/123/notes?page=2>; rel=\"last\"";
912 let mut headers = Headers::new();
913 headers.set("link", link_header);
914 let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
915 200,
916 None,
917 Some(headers),
918 );
919 let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn CommentMergeRequest);
920 let args = CommentMergeRequestListBodyArgs::builder()
921 .id(123)
922 .list_args(None)
923 .build()
924 .unwrap();
925 assert_eq!(Some(2), gitlab.num_pages(args).unwrap());
926 assert_eq!(
927 "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/merge_requests/123/notes?page=1",
928 *client.url(),
929 );
930 assert_eq!(
931 Some(ApiOperation::MergeRequest),
932 *client.api_operation.borrow()
933 );
934 }
935}