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