gitlab/api/issues/
projects.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use std::borrow::Cow;
8use std::collections::BTreeSet;
9use std::iter;
10
11use chrono::{DateTime, Utc};
12use derive_builder::Builder;
13use http::Method;
14
15use crate::api::common::{CommaSeparatedList, NameOrId, SortOrder};
16use crate::api::helpers::{Labels, ReactionEmoji};
17use crate::api::{Endpoint, Pageable, QueryParams};
18
19use super::Assignee;
20use super::IssueDueDateFilter;
21use super::IssueEpic;
22use super::IssueHealthStatus;
23use super::IssueIteration;
24use super::IssueMilestone;
25use super::IssueOrderBy;
26use super::IssueScope;
27use super::IssueSearchScope;
28use super::IssueState;
29use super::IssueType;
30use super::IssueWeight;
31
32/// Query for issues within a project.
33///
34/// TODO: Negation (not) filters are not yet supported.
35#[derive(Debug, Builder, Clone)]
36#[builder(setter(strip_option))]
37pub struct ProjectIssues<'a> {
38    /// The project to query for issues.
39    #[builder(setter(into))]
40    project: NameOrId<'a>,
41
42    /// Filter issues with specific IDs.
43    #[builder(setter(name = "_iids"), default, private)]
44    iids: BTreeSet<u64>,
45    /// Filter issues based on state.
46    #[builder(default)]
47    state: Option<IssueState>,
48    /// Filter issues based on labels.
49    #[builder(setter(name = "_labels"), default, private)]
50    labels: Option<Labels<'a>>,
51    /// Include label details in the result.
52    #[builder(default)]
53    with_labels_details: Option<bool>,
54    /// Filter by the iteration.
55    #[builder(default)]
56    iteration: Option<IssueIteration<'a>>,
57    /// Filter issues with a milestone.
58    #[builder(default)]
59    milestone_id: Option<IssueMilestone<'a>>,
60    /// Filter issues within a scope.
61    #[builder(default)]
62    scope: Option<IssueScope>,
63    /// Filter issues by author.
64    #[builder(setter(into), default)]
65    author: Option<NameOrId<'a>>,
66    /// Filter issues by assignees.
67    #[builder(setter(name = "_assignee"), default, private)]
68    assignee: Option<Assignee<'a>>,
69    /// Filter issues by the API caller's reactions.
70    #[builder(setter(name = "_my_reaction_emoji"), default, private)]
71    my_reaction_emoji: Option<ReactionEmoji<'a>>,
72    /// Filter issues by weight.
73    #[builder(default)]
74    weight: Option<IssueWeight>,
75    /// Filter issues with a search query.
76    #[builder(setter(into), default)]
77    search: Option<Cow<'a, str>>,
78    /// The scopes to search within.
79    #[builder(setter(name = "_search_in"), default, private)]
80    search_in: Option<CommaSeparatedList<IssueSearchScope>>,
81    /// Filter issues created after a point in time.
82    #[builder(default)]
83    created_after: Option<DateTime<Utc>>,
84    /// Filter issues created before a point in time.
85    #[builder(default)]
86    created_before: Option<DateTime<Utc>>,
87    /// Filter issues last updated after a point in time.
88    #[builder(default)]
89    updated_after: Option<DateTime<Utc>>,
90    /// Filter issues last updated before a point in time.
91    #[builder(default)]
92    updated_before: Option<DateTime<Utc>>,
93    /// Filter issues by confidentiality.
94    #[builder(default)]
95    confidential: Option<bool>,
96    /// Filter issues by due date.
97    #[builder(default)]
98    due_date: Option<IssueDueDateFilter>,
99    /// Filter by epic ID.
100    #[builder(setter(into), default)]
101    epic_id: Option<IssueEpic>,
102    /// Filter by issue type.
103    #[builder(default)]
104    issue_type: Option<IssueType>,
105    /// Filter by issue health status.
106    #[builder(default)]
107    health_status: Option<IssueHealthStatus>,
108
109    // TODO: How best to support this parameter?
110    // not
111    /// Order results by a given key.
112    #[builder(default)]
113    order_by: Option<IssueOrderBy>,
114    /// The sort order for returned results.
115    #[builder(default)]
116    sort: Option<SortOrder>,
117}
118
119impl<'a> ProjectIssues<'a> {
120    /// Create a builder for the endpoint.
121    pub fn builder() -> ProjectIssuesBuilder<'a> {
122        ProjectIssuesBuilder::default()
123    }
124}
125
126impl<'a> ProjectIssuesBuilder<'a> {
127    /// Return an issue with an ID.
128    pub fn iid(&mut self, iid: u64) -> &mut Self {
129        self.iids.get_or_insert_with(BTreeSet::new).insert(iid);
130        self
131    }
132
133    /// Return issues with one of a set of IDs.
134    pub fn iids<I>(&mut self, iter: I) -> &mut Self
135    where
136        I: Iterator<Item = u64>,
137    {
138        self.iids.get_or_insert_with(BTreeSet::new).extend(iter);
139        self
140    }
141
142    /// Filter unlabeled issues.
143    pub fn unlabeled(&mut self) -> &mut Self {
144        self.labels = Some(Some(Labels::None));
145        self
146    }
147
148    /// Filter issues with any label.
149    pub fn with_any_label(&mut self) -> &mut Self {
150        self.labels = Some(Some(Labels::Any));
151        self
152    }
153
154    /// Filter issues with a given label.
155    pub fn label<L>(&mut self, label: L) -> &mut Self
156    where
157        L: Into<Cow<'a, str>>,
158    {
159        let label = label.into();
160        let labels = if let Some(Some(Labels::AllOf(mut set))) = self.labels.take() {
161            set.push(label);
162            set
163        } else {
164            iter::once(label).collect()
165        };
166        self.labels = Some(Some(Labels::AllOf(labels)));
167        self
168    }
169
170    /// Filter issues with all of the given labels.
171    pub fn labels<I, L>(&mut self, iter: I) -> &mut Self
172    where
173        I: IntoIterator<Item = L>,
174        L: Into<Cow<'a, str>>,
175    {
176        let iter = iter.into_iter().map(Into::into);
177        let labels = if let Some(Some(Labels::AllOf(mut set))) = self.labels.take() {
178            set.extend(iter);
179            set
180        } else {
181            iter.collect()
182        };
183        self.labels = Some(Some(Labels::AllOf(labels)));
184        self
185    }
186
187    /// Filter unassigned issues.
188    pub fn unassigned(&mut self) -> &mut Self {
189        self.assignee = Some(Some(Assignee::Unassigned));
190        self
191    }
192
193    /// Filter assigned issues.
194    pub fn assigned(&mut self) -> &mut Self {
195        self.assignee = Some(Some(Assignee::Assigned));
196        self
197    }
198
199    /// Filter issues assigned to a user (by ID).
200    pub fn assignee_id(&mut self, assignee: u64) -> &mut Self {
201        self.assignee = Some(Some(Assignee::Id(assignee)));
202        self
203    }
204
205    /// Filter issues assigned to a users (by username).
206    pub fn assignee<A>(&mut self, assignee: A) -> &mut Self
207    where
208        A: Into<Cow<'a, str>>,
209    {
210        let assignee = assignee.into();
211        let assignees = if let Some(Some(Assignee::Usernames(mut set))) = self.assignee.take() {
212            set.insert(assignee);
213            set
214        } else {
215            let mut set = BTreeSet::new();
216            set.insert(assignee);
217            set
218        };
219        self.assignee = Some(Some(Assignee::Usernames(assignees)));
220        self
221    }
222
223    /// Filter issues assigned to a set of users.
224    pub fn assignees<I, A>(&mut self, iter: I) -> &mut Self
225    where
226        I: IntoIterator<Item = A>,
227        A: Into<Cow<'a, str>>,
228    {
229        let iter = iter.into_iter().map(Into::into);
230        let assignees = if let Some(Some(Assignee::Usernames(mut set))) = self.assignee.take() {
231            set.extend(iter);
232            set
233        } else {
234            iter.collect()
235        };
236        self.assignee = Some(Some(Assignee::Usernames(assignees)));
237        self
238    }
239
240    /// Filter issues without a reaction by the API caller.
241    pub fn no_reaction(&mut self) -> &mut Self {
242        self.my_reaction_emoji = Some(Some(ReactionEmoji::None));
243        self
244    }
245
246    /// Filter issues with any reaction by the API caller.
247    pub fn any_reaction(&mut self) -> &mut Self {
248        self.my_reaction_emoji = Some(Some(ReactionEmoji::Any));
249        self
250    }
251
252    /// Filter issues with a specific reaction by the API caller.
253    pub fn my_reaction<E>(&mut self, emoji: E) -> &mut Self
254    where
255        E: Into<Cow<'a, str>>,
256    {
257        self.my_reaction_emoji = Some(Some(ReactionEmoji::Emoji(emoji.into())));
258        self
259    }
260
261    /// The scopes to look for search query within.
262    pub fn search_in(&mut self, scope: IssueSearchScope) -> &mut Self {
263        self.search_in
264            .get_or_insert(None)
265            .get_or_insert_with(CommaSeparatedList::new)
266            .push(scope);
267        self
268    }
269}
270
271impl Endpoint for ProjectIssues<'_> {
272    fn method(&self) -> Method {
273        Method::GET
274    }
275
276    fn endpoint(&self) -> Cow<'static, str> {
277        format!("projects/{}/issues", self.project).into()
278    }
279
280    fn parameters(&self) -> QueryParams<'_> {
281        let mut params = QueryParams::default();
282
283        params
284            .extend(self.iids.iter().map(|&value| ("iids[]", value)))
285            .push_opt("state", self.state)
286            .push_opt("labels", self.labels.as_ref())
287            .push_opt("with_labels_details", self.with_labels_details)
288            .push_opt("scope", self.scope)
289            .push_opt("my_reaction_emoji", self.my_reaction_emoji.as_ref())
290            .push_opt("weight", self.weight)
291            .push_opt("search", self.search.as_ref())
292            .push_opt("in", self.search_in.as_ref())
293            .push_opt("created_after", self.created_after)
294            .push_opt("created_before", self.created_before)
295            .push_opt("updated_after", self.updated_after)
296            .push_opt("updated_before", self.updated_before)
297            .push_opt("confidential", self.confidential)
298            .push_opt("due_date", self.due_date)
299            .push_opt("epic_id", self.epic_id)
300            .push_opt("issue_type", self.issue_type)
301            .push_opt("health_status", self.health_status)
302            .push_opt("order_by", self.order_by)
303            .push_opt("sort", self.sort);
304
305        if let Some(milestone_id) = self.milestone_id.as_ref() {
306            match milestone_id {
307                IssueMilestone::Named(name) => {
308                    params.push("milestone", name);
309                },
310                milestone => {
311                    params.push("milestone_id", milestone);
312                },
313            }
314        }
315        if let Some(author) = self.author.as_ref() {
316            match author {
317                NameOrId::Name(name) => {
318                    params.push("author_username", name);
319                },
320                NameOrId::Id(id) => {
321                    params.push("author_id", *id);
322                },
323            }
324        }
325        if let Some(iteration) = self.iteration.as_ref() {
326            iteration.add_params(&mut params);
327        }
328        if let Some(assignee) = self.assignee.as_ref() {
329            assignee.add_params(&mut params);
330        }
331
332        params
333    }
334}
335
336impl Pageable for ProjectIssues<'_> {}
337
338#[cfg(test)]
339mod tests {
340    use chrono::{TimeZone, Utc};
341
342    use crate::api::common::SortOrder;
343    use crate::api::issues::{
344        projects::ProjectIssues, projects::ProjectIssuesBuilderError, IssueDueDateFilter,
345        IssueEpic, IssueHealthStatus, IssueIteration, IssueMilestone, IssueOrderBy, IssueScope,
346        IssueSearchScope, IssueState, IssueType, IssueWeight,
347    };
348    use crate::api::{self, Query};
349    use crate::test::client::{ExpectedUrl, SingleTestClient};
350
351    #[test]
352    fn project_is_needed() {
353        let err = ProjectIssues::builder().build().unwrap_err();
354        crate::test::assert_missing_field!(err, ProjectIssuesBuilderError, "project");
355    }
356
357    #[test]
358    fn project_is_sufficient() {
359        ProjectIssues::builder().project(1).build().unwrap();
360    }
361
362    #[test]
363    fn endpoint() {
364        let endpoint = ExpectedUrl::builder()
365            .endpoint("projects/simple%2Fproject/issues")
366            .build()
367            .unwrap();
368        let client = SingleTestClient::new_raw(endpoint, "");
369
370        let endpoint = ProjectIssues::builder()
371            .project("simple/project")
372            .build()
373            .unwrap();
374        api::ignore(endpoint).query(&client).unwrap();
375    }
376
377    #[test]
378    fn endpoint_iids() {
379        let endpoint = ExpectedUrl::builder()
380            .endpoint("projects/simple%2Fproject/issues")
381            .add_query_params(&[("iids[]", "1"), ("iids[]", "2")])
382            .build()
383            .unwrap();
384        let client = SingleTestClient::new_raw(endpoint, "");
385
386        let endpoint = ProjectIssues::builder()
387            .project("simple/project")
388            .iid(1)
389            .iids([1, 2].iter().copied())
390            .build()
391            .unwrap();
392        api::ignore(endpoint).query(&client).unwrap();
393    }
394
395    #[test]
396    fn endpoint_state() {
397        let endpoint = ExpectedUrl::builder()
398            .endpoint("projects/simple%2Fproject/issues")
399            .add_query_params(&[("state", "closed")])
400            .build()
401            .unwrap();
402        let client = SingleTestClient::new_raw(endpoint, "");
403
404        let endpoint = ProjectIssues::builder()
405            .project("simple/project")
406            .state(IssueState::Closed)
407            .build()
408            .unwrap();
409        api::ignore(endpoint).query(&client).unwrap();
410    }
411
412    #[test]
413    fn endpoint_labels() {
414        let endpoint = ExpectedUrl::builder()
415            .endpoint("projects/simple%2Fproject/issues")
416            .add_query_params(&[("labels", "label,label1,label2")])
417            .build()
418            .unwrap();
419        let client = SingleTestClient::new_raw(endpoint, "");
420
421        let endpoint = ProjectIssues::builder()
422            .project("simple/project")
423            .label("label")
424            .labels(["label1", "label2"].iter().cloned())
425            .build()
426            .unwrap();
427        api::ignore(endpoint).query(&client).unwrap();
428    }
429
430    #[test]
431    fn endpoint_labels_unlabeled() {
432        let endpoint = ExpectedUrl::builder()
433            .endpoint("projects/simple%2Fproject/issues")
434            .add_query_params(&[("labels", "None")])
435            .build()
436            .unwrap();
437        let client = SingleTestClient::new_raw(endpoint, "");
438
439        let endpoint = ProjectIssues::builder()
440            .project("simple/project")
441            .unlabeled()
442            .build()
443            .unwrap();
444        api::ignore(endpoint).query(&client).unwrap();
445    }
446
447    #[test]
448    fn endpoint_labels_any() {
449        let endpoint = ExpectedUrl::builder()
450            .endpoint("projects/simple%2Fproject/issues")
451            .add_query_params(&[("labels", "Any")])
452            .build()
453            .unwrap();
454        let client = SingleTestClient::new_raw(endpoint, "");
455
456        let endpoint = ProjectIssues::builder()
457            .project("simple/project")
458            .with_any_label()
459            .build()
460            .unwrap();
461        api::ignore(endpoint).query(&client).unwrap();
462    }
463
464    #[test]
465    fn endpoint_with_labels_details() {
466        let endpoint = ExpectedUrl::builder()
467            .endpoint("projects/simple%2Fproject/issues")
468            .add_query_params(&[("with_labels_details", "true")])
469            .build()
470            .unwrap();
471        let client = SingleTestClient::new_raw(endpoint, "");
472
473        let endpoint = ProjectIssues::builder()
474            .project("simple/project")
475            .with_labels_details(true)
476            .build()
477            .unwrap();
478        api::ignore(endpoint).query(&client).unwrap();
479    }
480
481    #[test]
482    fn endpoint_epic_id() {
483        let endpoint = ExpectedUrl::builder()
484            .endpoint("projects/simple%2Fproject/issues")
485            .add_query_params(&[("epic_id", "1")])
486            .build()
487            .unwrap();
488        let client = SingleTestClient::new_raw(endpoint, "");
489
490        let endpoint = ProjectIssues::builder()
491            .project("simple/project")
492            .epic_id(1)
493            .build()
494            .unwrap();
495        api::ignore(endpoint).query(&client).unwrap();
496    }
497
498    #[test]
499    fn endpoint_epic_id_any() {
500        let endpoint = ExpectedUrl::builder()
501            .endpoint("projects/simple%2Fproject/issues")
502            .add_query_params(&[("epic_id", "Any")])
503            .build()
504            .unwrap();
505        let client = SingleTestClient::new_raw(endpoint, "");
506
507        let endpoint = ProjectIssues::builder()
508            .project("simple/project")
509            .epic_id(IssueEpic::Any)
510            .build()
511            .unwrap();
512        api::ignore(endpoint).query(&client).unwrap();
513    }
514
515    #[test]
516    fn endpoint_epic_id_none() {
517        let endpoint = ExpectedUrl::builder()
518            .endpoint("projects/simple%2Fproject/issues")
519            .add_query_params(&[("epic_id", "None")])
520            .build()
521            .unwrap();
522        let client = SingleTestClient::new_raw(endpoint, "");
523
524        let endpoint = ProjectIssues::builder()
525            .project("simple/project")
526            .epic_id(IssueEpic::None)
527            .build()
528            .unwrap();
529        api::ignore(endpoint).query(&client).unwrap();
530    }
531
532    #[test]
533    fn endpoint_issue_type() {
534        let endpoint = ExpectedUrl::builder()
535            .endpoint("projects/simple%2Fproject/issues")
536            .add_query_params(&[("issue_type", "incident")])
537            .build()
538            .unwrap();
539        let client = SingleTestClient::new_raw(endpoint, "");
540
541        let endpoint = ProjectIssues::builder()
542            .project("simple/project")
543            .issue_type(IssueType::Incident)
544            .build()
545            .unwrap();
546        api::ignore(endpoint).query(&client).unwrap();
547    }
548
549    #[test]
550    fn endpoint_health_status() {
551        let endpoint = ExpectedUrl::builder()
552            .endpoint("projects/simple%2Fproject/issues")
553            .add_query_params(&[("health_status", "at_risk")])
554            .build()
555            .unwrap();
556        let client = SingleTestClient::new_raw(endpoint, "");
557
558        let endpoint = ProjectIssues::builder()
559            .project("simple/project")
560            .health_status(IssueHealthStatus::AtRisk)
561            .build()
562            .unwrap();
563        api::ignore(endpoint).query(&client).unwrap();
564    }
565
566    #[test]
567    fn endpoint_iteration_none() {
568        let endpoint = ExpectedUrl::builder()
569            .endpoint("projects/simple%2Fproject/issues")
570            .add_query_params(&[("iteration_id", "None")])
571            .build()
572            .unwrap();
573        let client = SingleTestClient::new_raw(endpoint, "");
574
575        let endpoint = ProjectIssues::builder()
576            .project("simple/project")
577            .iteration(IssueIteration::None)
578            .build()
579            .unwrap();
580        api::ignore(endpoint).query(&client).unwrap();
581    }
582
583    #[test]
584    fn endpoint_iteration_any() {
585        let endpoint = ExpectedUrl::builder()
586            .endpoint("projects/simple%2Fproject/issues")
587            .add_query_params(&[("iteration_id", "Any")])
588            .build()
589            .unwrap();
590        let client = SingleTestClient::new_raw(endpoint, "");
591
592        let endpoint = ProjectIssues::builder()
593            .project("simple/project")
594            .iteration(IssueIteration::Any)
595            .build()
596            .unwrap();
597        api::ignore(endpoint).query(&client).unwrap();
598    }
599
600    #[test]
601    fn endpoint_iteration_id() {
602        let endpoint = ExpectedUrl::builder()
603            .endpoint("projects/simple%2Fproject/issues")
604            .add_query_params(&[("iteration_id", "1")])
605            .build()
606            .unwrap();
607        let client = SingleTestClient::new_raw(endpoint, "");
608
609        let endpoint = ProjectIssues::builder()
610            .project("simple/project")
611            .iteration(IssueIteration::Id(1))
612            .build()
613            .unwrap();
614        api::ignore(endpoint).query(&client).unwrap();
615    }
616
617    #[test]
618    fn endpoint_iteration_title() {
619        let endpoint = ExpectedUrl::builder()
620            .endpoint("projects/simple%2Fproject/issues")
621            .add_query_params(&[("iteration_title", "title")])
622            .build()
623            .unwrap();
624        let client = SingleTestClient::new_raw(endpoint, "");
625
626        let endpoint = ProjectIssues::builder()
627            .project("simple/project")
628            .iteration(IssueIteration::Title("title".into()))
629            .build()
630            .unwrap();
631        api::ignore(endpoint).query(&client).unwrap();
632    }
633
634    #[test]
635    fn endpoint_milestone_id() {
636        let endpoint = ExpectedUrl::builder()
637            .endpoint("projects/simple%2Fproject/issues")
638            .add_query_params(&[("milestone_id", "Upcoming")])
639            .build()
640            .unwrap();
641        let client = SingleTestClient::new_raw(endpoint, "");
642
643        let endpoint = ProjectIssues::builder()
644            .project("simple/project")
645            .milestone_id(IssueMilestone::Upcoming)
646            .build()
647            .unwrap();
648        api::ignore(endpoint).query(&client).unwrap();
649    }
650
651    #[test]
652    fn endpoint_milestone_id_named() {
653        let endpoint = ExpectedUrl::builder()
654            .endpoint("projects/simple%2Fproject/issues")
655            .add_query_params(&[("milestone", "1.0")])
656            .build()
657            .unwrap();
658        let client = SingleTestClient::new_raw(endpoint, "");
659
660        let endpoint = ProjectIssues::builder()
661            .project("simple/project")
662            .milestone_id(IssueMilestone::named("1.0"))
663            .build()
664            .unwrap();
665        api::ignore(endpoint).query(&client).unwrap();
666    }
667
668    #[test]
669    fn endpoint_scope() {
670        let endpoint = ExpectedUrl::builder()
671            .endpoint("projects/simple%2Fproject/issues")
672            .add_query_params(&[("scope", "all")])
673            .build()
674            .unwrap();
675        let client = SingleTestClient::new_raw(endpoint, "");
676
677        let endpoint = ProjectIssues::builder()
678            .project("simple/project")
679            .scope(IssueScope::All)
680            .build()
681            .unwrap();
682        api::ignore(endpoint).query(&client).unwrap();
683    }
684
685    #[test]
686    fn endpoint_author_id() {
687        let endpoint = ExpectedUrl::builder()
688            .endpoint("projects/simple%2Fproject/issues")
689            .add_query_params(&[("author_id", "1")])
690            .build()
691            .unwrap();
692        let client = SingleTestClient::new_raw(endpoint, "");
693
694        let endpoint = ProjectIssues::builder()
695            .project("simple/project")
696            .author(1)
697            .build()
698            .unwrap();
699        api::ignore(endpoint).query(&client).unwrap();
700    }
701
702    #[test]
703    fn endpoint_author_name() {
704        let endpoint = ExpectedUrl::builder()
705            .endpoint("projects/simple%2Fproject/issues")
706            .add_query_params(&[("author_username", "name")])
707            .build()
708            .unwrap();
709        let client = SingleTestClient::new_raw(endpoint, "");
710
711        let endpoint = ProjectIssues::builder()
712            .project("simple/project")
713            .author("name")
714            .build()
715            .unwrap();
716        api::ignore(endpoint).query(&client).unwrap();
717    }
718
719    #[test]
720    fn endpoint_assignee_unassigned() {
721        let endpoint = ExpectedUrl::builder()
722            .endpoint("projects/simple%2Fproject/issues")
723            .add_query_params(&[("assignee_id", "None")])
724            .build()
725            .unwrap();
726        let client = SingleTestClient::new_raw(endpoint, "");
727
728        let endpoint = ProjectIssues::builder()
729            .project("simple/project")
730            .unassigned()
731            .build()
732            .unwrap();
733        api::ignore(endpoint).query(&client).unwrap();
734    }
735
736    #[test]
737    fn endpoint_assignee_assigned() {
738        let endpoint = ExpectedUrl::builder()
739            .endpoint("projects/simple%2Fproject/issues")
740            .add_query_params(&[("assignee_id", "Any")])
741            .build()
742            .unwrap();
743        let client = SingleTestClient::new_raw(endpoint, "");
744
745        let endpoint = ProjectIssues::builder()
746            .project("simple/project")
747            .assigned()
748            .build()
749            .unwrap();
750        api::ignore(endpoint).query(&client).unwrap();
751    }
752
753    #[test]
754    fn endpoint_assignee_id() {
755        let endpoint = ExpectedUrl::builder()
756            .endpoint("projects/simple%2Fproject/issues")
757            .add_query_params(&[("assignee_id", "1")])
758            .build()
759            .unwrap();
760        let client = SingleTestClient::new_raw(endpoint, "");
761
762        let endpoint = ProjectIssues::builder()
763            .project("simple/project")
764            .assignee_id(1)
765            .build()
766            .unwrap();
767        api::ignore(endpoint).query(&client).unwrap();
768    }
769
770    #[test]
771    fn endpoint_assignee_user() {
772        let endpoint = ExpectedUrl::builder()
773            .endpoint("projects/simple%2Fproject/issues")
774            .add_query_params(&[
775                ("assignee_username[]", "name1"),
776                ("assignee_username[]", "name2"),
777            ])
778            .build()
779            .unwrap();
780        let client = SingleTestClient::new_raw(endpoint, "");
781
782        let endpoint = ProjectIssues::builder()
783            .project("simple/project")
784            .assignee("name1")
785            .assignees(["name1", "name2"].iter().copied())
786            .build()
787            .unwrap();
788        api::ignore(endpoint).query(&client).unwrap();
789    }
790
791    #[test]
792    fn endpoint_my_reaction_emoji() {
793        let endpoint = ExpectedUrl::builder()
794            .endpoint("projects/simple%2Fproject/issues")
795            .add_query_params(&[("my_reaction_emoji", "tada")])
796            .build()
797            .unwrap();
798        let client = SingleTestClient::new_raw(endpoint, "");
799
800        let endpoint = ProjectIssues::builder()
801            .project("simple/project")
802            .my_reaction("tada")
803            .build()
804            .unwrap();
805        api::ignore(endpoint).query(&client).unwrap();
806    }
807
808    #[test]
809    fn endpoint_my_reaction_emoji_no_reaction() {
810        let endpoint = ExpectedUrl::builder()
811            .endpoint("projects/simple%2Fproject/issues")
812            .add_query_params(&[("my_reaction_emoji", "None")])
813            .build()
814            .unwrap();
815        let client = SingleTestClient::new_raw(endpoint, "");
816
817        let endpoint = ProjectIssues::builder()
818            .project("simple/project")
819            .no_reaction()
820            .build()
821            .unwrap();
822        api::ignore(endpoint).query(&client).unwrap();
823    }
824
825    #[test]
826    fn endpoint_my_reaction_emoji_any_reaction() {
827        let endpoint = ExpectedUrl::builder()
828            .endpoint("projects/simple%2Fproject/issues")
829            .add_query_params(&[("my_reaction_emoji", "Any")])
830            .build()
831            .unwrap();
832        let client = SingleTestClient::new_raw(endpoint, "");
833
834        let endpoint = ProjectIssues::builder()
835            .project("simple/project")
836            .any_reaction()
837            .build()
838            .unwrap();
839        api::ignore(endpoint).query(&client).unwrap();
840    }
841
842    #[test]
843    fn endpoint_weight() {
844        let endpoint = ExpectedUrl::builder()
845            .endpoint("projects/simple%2Fproject/issues")
846            .add_query_params(&[("weight", "Any")])
847            .build()
848            .unwrap();
849        let client = SingleTestClient::new_raw(endpoint, "");
850
851        let endpoint = ProjectIssues::builder()
852            .project("simple/project")
853            .weight(IssueWeight::Any)
854            .build()
855            .unwrap();
856        api::ignore(endpoint).query(&client).unwrap();
857    }
858
859    #[test]
860    fn endpoint_search() {
861        let endpoint = ExpectedUrl::builder()
862            .endpoint("projects/simple%2Fproject/issues")
863            .add_query_params(&[("search", "query")])
864            .build()
865            .unwrap();
866        let client = SingleTestClient::new_raw(endpoint, "");
867
868        let endpoint = ProjectIssues::builder()
869            .project("simple/project")
870            .search("query")
871            .build()
872            .unwrap();
873        api::ignore(endpoint).query(&client).unwrap();
874    }
875
876    #[test]
877    fn endpoint_search_in() {
878        let endpoint = ExpectedUrl::builder()
879            .endpoint("projects/simple%2Fproject/issues")
880            .add_query_params(&[("in", "title,description")])
881            .build()
882            .unwrap();
883        let client = SingleTestClient::new_raw(endpoint, "");
884
885        let endpoint = ProjectIssues::builder()
886            .project("simple/project")
887            .search_in(IssueSearchScope::Title)
888            .search_in(IssueSearchScope::Description)
889            .build()
890            .unwrap();
891        api::ignore(endpoint).query(&client).unwrap();
892    }
893
894    #[test]
895    fn endpoint_created_after() {
896        let endpoint = ExpectedUrl::builder()
897            .endpoint("projects/simple%2Fproject/issues")
898            .add_query_params(&[("created_after", "2020-01-01T00:00:00Z")])
899            .build()
900            .unwrap();
901        let client = SingleTestClient::new_raw(endpoint, "");
902
903        let endpoint = ProjectIssues::builder()
904            .project("simple/project")
905            .created_after(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
906            .build()
907            .unwrap();
908        api::ignore(endpoint).query(&client).unwrap();
909    }
910
911    #[test]
912    fn endpoint_created_before() {
913        let endpoint = ExpectedUrl::builder()
914            .endpoint("projects/simple%2Fproject/issues")
915            .add_query_params(&[("created_before", "2020-01-01T00:00:00Z")])
916            .build()
917            .unwrap();
918        let client = SingleTestClient::new_raw(endpoint, "");
919
920        let endpoint = ProjectIssues::builder()
921            .project("simple/project")
922            .created_before(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
923            .build()
924            .unwrap();
925        api::ignore(endpoint).query(&client).unwrap();
926    }
927
928    #[test]
929    fn endpoint_updated_after() {
930        let endpoint = ExpectedUrl::builder()
931            .endpoint("projects/simple%2Fproject/issues")
932            .add_query_params(&[("updated_after", "2020-01-01T00:00:00Z")])
933            .build()
934            .unwrap();
935        let client = SingleTestClient::new_raw(endpoint, "");
936
937        let endpoint = ProjectIssues::builder()
938            .project("simple/project")
939            .updated_after(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
940            .build()
941            .unwrap();
942        api::ignore(endpoint).query(&client).unwrap();
943    }
944
945    #[test]
946    fn endpoint_updated_before() {
947        let endpoint = ExpectedUrl::builder()
948            .endpoint("projects/simple%2Fproject/issues")
949            .add_query_params(&[("updated_before", "2020-01-01T00:00:00Z")])
950            .build()
951            .unwrap();
952        let client = SingleTestClient::new_raw(endpoint, "");
953
954        let endpoint = ProjectIssues::builder()
955            .project("simple/project")
956            .updated_before(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
957            .build()
958            .unwrap();
959        api::ignore(endpoint).query(&client).unwrap();
960    }
961
962    #[test]
963    fn endpoint_confidential() {
964        let endpoint = ExpectedUrl::builder()
965            .endpoint("projects/simple%2Fproject/issues")
966            .add_query_params(&[("confidential", "true")])
967            .build()
968            .unwrap();
969        let client = SingleTestClient::new_raw(endpoint, "");
970
971        let endpoint = ProjectIssues::builder()
972            .project("simple/project")
973            .confidential(true)
974            .build()
975            .unwrap();
976        api::ignore(endpoint).query(&client).unwrap();
977    }
978
979    #[test]
980    fn endpoint_due_date() {
981        let endpoint = ExpectedUrl::builder()
982            .endpoint("projects/simple%2Fproject/issues")
983            .add_query_params(&[("due_date", "week")])
984            .build()
985            .unwrap();
986        let client = SingleTestClient::new_raw(endpoint, "");
987
988        let endpoint = ProjectIssues::builder()
989            .project("simple/project")
990            .due_date(IssueDueDateFilter::ThisWeek)
991            .build()
992            .unwrap();
993        api::ignore(endpoint).query(&client).unwrap();
994    }
995
996    #[test]
997    fn endpoint_order_by() {
998        let endpoint = ExpectedUrl::builder()
999            .endpoint("projects/simple%2Fproject/issues")
1000            .add_query_params(&[("order_by", "due_date")])
1001            .build()
1002            .unwrap();
1003        let client = SingleTestClient::new_raw(endpoint, "");
1004
1005        let endpoint = ProjectIssues::builder()
1006            .project("simple/project")
1007            .order_by(IssueOrderBy::DueDate)
1008            .build()
1009            .unwrap();
1010        api::ignore(endpoint).query(&client).unwrap();
1011    }
1012
1013    #[test]
1014    fn endpoint_sort() {
1015        let endpoint = ExpectedUrl::builder()
1016            .endpoint("projects/simple%2Fproject/issues")
1017            .add_query_params(&[("sort", "desc")])
1018            .build()
1019            .unwrap();
1020        let client = SingleTestClient::new_raw(endpoint, "");
1021
1022        let endpoint = ProjectIssues::builder()
1023            .project("simple/project")
1024            .sort(SortOrder::Descending)
1025            .build()
1026            .unwrap();
1027        api::ignore(endpoint).query(&client).unwrap();
1028    }
1029}