gitlab/api/groups/
edit.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::collections::BTreeSet;
8use std::time::Duration;
9
10use derive_builder::Builder;
11
12use crate::api::common::{CommaSeparatedList, NameOrId, VisibilityLevel};
13use crate::api::endpoint_prelude::*;
14use crate::api::groups::{
15    BranchProtectionDefaults, DuoAvailability, GitAccessProtocol, GroupProjectCreationAccessLevel,
16    SharedRunnersMinutesLimit, SubgroupCreationAccessLevel,
17};
18use crate::api::projects::FeatureAccessLevel;
19use crate::api::ParamValue;
20
21/// Access levels for creating a project within a group.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum SharedRunnersSetting {
25    /// All projects and subgroups can use shared runners.
26    Enabled,
27    /// Shared runners are not allowed, but subgroups can enable.
28    DisabledWithOverride,
29    /// Shared runners are not allowed for this group and all subgroups.
30    DisableAndUnoverridable,
31}
32
33impl SharedRunnersSetting {
34    fn as_str(self) -> &'static str {
35        match self {
36            SharedRunnersSetting::Enabled => "enabled",
37            SharedRunnersSetting::DisabledWithOverride => "disabled_with_override",
38            SharedRunnersSetting::DisableAndUnoverridable => "disabled_and_unoverridable",
39        }
40    }
41}
42
43impl ParamValue<'static> for SharedRunnersSetting {
44    fn as_value(&self) -> Cow<'static, str> {
45        self.as_str().into()
46    }
47}
48
49/// Edit an existing group.
50#[derive(Debug, Builder, Clone)]
51#[builder(setter(strip_option))]
52pub struct EditGroup<'a> {
53    /// The group to edit.
54    #[builder(setter(into))]
55    group: NameOrId<'a>,
56
57    /// The name of the group.
58    #[builder(setter(into), default)]
59    name: Option<Cow<'a, str>>,
60    /// The path of the group.
61    #[builder(setter(into), default)]
62    path: Option<Cow<'a, str>>,
63    /// A short description for the group.
64    #[builder(setter(into), default)]
65    description: Option<Cow<'a, str>>,
66    /// Prevent adding members directly to projects within the group.
67    #[builder(default)]
68    membership_lock: Option<bool>,
69    /// The visibility of the group.
70    #[builder(default)]
71    visibility: Option<VisibilityLevel>,
72    /// Prevent sharing a project in this group with another group.
73    #[builder(default)]
74    share_with_group_lock: Option<bool>,
75    /// Require two-factor authentication to be a member of this group.
76    #[builder(default)]
77    require_two_factor_authentication: Option<bool>,
78    /// Time (in hours) for users to enable two-factor before enforcing it.
79    #[builder(default)]
80    two_factor_grace_period: Option<u64>,
81    /// The access level to the group that is required to create new projects.
82    #[builder(default)]
83    project_creation_level: Option<GroupProjectCreationAccessLevel>,
84    /// Default to Auto DevOps for new projects in the group.
85    #[builder(default)]
86    auto_devops_enabled: Option<bool>,
87    /// The access level to the group that is required to create subgroups.
88    #[builder(default)]
89    subgroup_creation_level: Option<SubgroupCreationAccessLevel>,
90    /// Enable email notifications from the group.
91    #[builder(default)]
92    emails_enabled: Option<bool>,
93    // TODO: Figure out how to actually use this.
94    // avatar   mixed   no  Image file for avatar of the group
95    // avatar: ???,
96    /// Disable group-wide mentions.
97    #[builder(default)]
98    mentions_disabled: Option<bool>,
99    /// Disable sharing outside of the group hierarchy.
100    ///
101    /// Only available on top-level groups.
102    #[builder(default)]
103    prevent_sharing_groups_outside_hierarchy: Option<bool>,
104    /// Whether `git-lfs` is enabled by default for projects within the group.
105    #[builder(default)]
106    lfs_enabled: Option<bool>,
107    /// Whether access to the group may be requested.
108    #[builder(default)]
109    request_access_enabled: Option<bool>,
110    /// The parent group ID (for subgroups).
111    #[builder(default)]
112    parent_id: Option<u64>,
113    /// The default branch name for projects within the group.
114    #[builder(setter(into), default)]
115    default_branch: Option<Cow<'a, str>>,
116    /// The allowed Git access protocols for projects within the group.
117    #[builder(default)]
118    enabled_git_access_protocol: Option<GitAccessProtocol>,
119    /// The default branch protection defaults for projects within the group.
120    #[builder(default)]
121    default_branch_protection_defaults: Option<BranchProtectionDefaults>,
122    /// Shared runner settings for the group.
123    #[builder(default)]
124    shared_runners_setting: Option<SharedRunnersSetting>,
125    /// Pipeline quota (in minutes) for the group on shared runners.
126    #[builder(setter(into), default)]
127    shared_runners_minutes_limit: Option<SharedRunnersMinutesLimit>,
128    /// Pipeline quota excess (in minutes) for the group on shared runners.
129    #[builder(default)]
130    extra_shared_runners_minutes_limit: Option<u64>,
131    /// The project id to load custom file templates from.
132    #[builder(default)]
133    file_template_project_id: Option<u64>,
134    /// When enabled, users cannot fork projects from this group to other namespaces.
135    #[builder(default)]
136    prevent_forking_outside_group: Option<bool>,
137    /// A set of IP addresses or IP ranges that are allowed to access the group.
138    #[builder(setter(name = "_ip_restriction_ranges"), default, private)]
139    ip_restriction_ranges: Option<CommaSeparatedList<Cow<'a, str>>>,
140    /// Comma-separated list of email domains allowed to sign up for the group.
141    #[builder(setter(name = "_allowed_email_domains_list"), default, private)]
142    allowed_email_domains_list: Option<CommaSeparatedList<Cow<'a, str>>>,
143    /// The wiki access level.
144    #[builder(default)]
145    wiki_access_level: Option<FeatureAccessLevel>,
146    /// GitLab Duo availability setting for the group.
147    #[builder(default)]
148    duo_availability: Option<DuoAvailability>,
149    /// Enable experiment features for the group.
150    #[builder(default)]
151    experiment_features_enabled: Option<bool>,
152    /// Enable math rendering limits for the group.
153    #[builder(default)]
154    math_rendering_limits_enabled: Option<bool>,
155    /// Lock math rendering limits for the group.
156    #[builder(default)]
157    lock_math_rendering_limits_enabled: Option<bool>,
158    /// Enable GitLab Duo features for the group.
159    #[builder(default)]
160    duo_features_enabled: Option<bool>,
161    /// Maximum size of artifacts in megabytes.
162    #[builder(default)]
163    max_artifacts_size: Option<u64>,
164
165    /// Maximum number of unique projects a user can download before being banned.
166    ///
167    /// Only supported on top-level groups.
168    #[builder(default)]
169    unique_project_download_limit: Option<u64>,
170    /// The window (in seconds) where downloads will be counted.
171    ///
172    /// Only supported on top-level groups.
173    #[builder(default)]
174    unique_project_download_limit_interval: Option<Duration>,
175    /// List of usernames excluded from the download limit.
176    ///
177    /// Only supported on top-level groups.
178    #[builder(
179        setter(name = "_unique_project_download_limit_allowlist"),
180        default,
181        private
182    )]
183    unique_project_download_limit_allowlist: BTreeSet<Cow<'a, str>>,
184    /// List of user IDs that are emailed when a download limit is exceeded.
185    ///
186    /// Only supported on top-level groups.
187    #[builder(
188        setter(name = "_unique_project_download_limit_alertlist"),
189        default,
190        private
191    )]
192    unique_project_download_limit_alertlist: BTreeSet<u64>,
193    /// Ban users from the group when they exceed the download limit.
194    ///
195    /// Only supported on top-level groups.
196    #[builder(default)]
197    auto_ban_user_on_excessive_projects_download: Option<bool>,
198}
199
200impl<'a> EditGroup<'a> {
201    /// Create a builder for the endpoint.
202    pub fn builder() -> EditGroupBuilder<'a> {
203        EditGroupBuilder::default()
204    }
205}
206
207impl<'a> EditGroupBuilder<'a> {
208    /// An IP address or IP range that is allowed to access the group.
209    pub fn ip_restriction_range<R>(&mut self, range: R) -> &mut Self
210    where
211        R: Into<Cow<'a, str>>,
212    {
213        self.ip_restriction_ranges
214            .get_or_insert(None)
215            .get_or_insert_with(CommaSeparatedList::new)
216            .push(range.into());
217        self
218    }
219
220    /// A set of IP addresses or IP ranges that are allowed to access the group.
221    pub fn ip_restriction_ranges<I, R>(&mut self, iter: I) -> &mut Self
222    where
223        I: Iterator<Item = R>,
224        R: Into<Cow<'a, str>>,
225    {
226        self.ip_restriction_ranges
227            .get_or_insert(None)
228            .get_or_insert_with(CommaSeparatedList::new)
229            .extend(iter.map(Into::into));
230        self
231    }
232
233    /// An email domain that is allowed to sign up for the group.
234    pub fn allowed_email_domain<D>(&mut self, domain: D) -> &mut Self
235    where
236        D: Into<Cow<'a, str>>,
237    {
238        self.allowed_email_domains_list
239            .get_or_insert(None)
240            .get_or_insert_with(CommaSeparatedList::new)
241            .push(domain.into());
242        self
243    }
244
245    /// A set of email domains that are allowed to sign up for the group.
246    pub fn allowed_email_domains<I, D>(&mut self, iter: I) -> &mut Self
247    where
248        I: Iterator<Item = D>,
249        D: Into<Cow<'a, str>>,
250    {
251        self.allowed_email_domains_list
252            .get_or_insert(None)
253            .get_or_insert_with(CommaSeparatedList::new)
254            .extend(iter.map(Into::into));
255        self
256    }
257
258    /// A username excluded from the download limit.
259    pub fn unique_project_download_limit_allow<A>(&mut self, allow: A) -> &mut Self
260    where
261        A: Into<Cow<'a, str>>,
262    {
263        self.unique_project_download_limit_allowlist
264            .get_or_insert_with(BTreeSet::new)
265            .insert(allow.into());
266        self
267    }
268
269    /// List of usernames excluded from the download limit.
270    pub fn unique_project_download_limit_allow_users<I, A>(&mut self, iter: I) -> &mut Self
271    where
272        I: Iterator<Item = A>,
273        A: Into<Cow<'a, str>>,
274    {
275        self.unique_project_download_limit_allowlist
276            .get_or_insert_with(BTreeSet::new)
277            .extend(iter.map(Into::into));
278        self
279    }
280
281    /// A user ID that is emailed when a download limit is exceeded.
282    pub fn unique_project_download_limit_alert(&mut self, alert: u64) -> &mut Self {
283        self.unique_project_download_limit_alertlist
284            .get_or_insert_with(BTreeSet::new)
285            .insert(alert);
286        self
287    }
288
289    /// List of user IDs that are emailed when a download limit is exceeded.
290    pub fn unique_project_download_limit_alert_users<I>(&mut self, iter: I) -> &mut Self
291    where
292        I: Iterator<Item = u64>,
293    {
294        self.unique_project_download_limit_alertlist
295            .get_or_insert_with(BTreeSet::new)
296            .extend(iter);
297        self
298    }
299}
300
301impl Endpoint for EditGroup<'_> {
302    fn method(&self) -> Method {
303        Method::PUT
304    }
305
306    fn endpoint(&self) -> Cow<'static, str> {
307        format!("groups/{}", self.group).into()
308    }
309
310    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
311        let mut params = FormParams::default();
312
313        params
314            .push_opt("name", self.name.as_ref())
315            .push_opt("path", self.path.as_ref())
316            .push_opt("description", self.description.as_ref())
317            .push_opt("membership_lock", self.membership_lock)
318            .push_opt("visibility", self.visibility)
319            .push_opt("share_with_group_lock", self.share_with_group_lock)
320            .push_opt(
321                "require_two_factor_authentication",
322                self.require_two_factor_authentication,
323            )
324            .push_opt("two_factor_grace_period", self.two_factor_grace_period)
325            .push_opt("project_creation_level", self.project_creation_level)
326            .push_opt("auto_devops_enabled", self.auto_devops_enabled)
327            .push_opt("subgroup_creation_level", self.subgroup_creation_level)
328            .push_opt("emails_enabled", self.emails_enabled)
329            .push_opt("mentions_disabled", self.mentions_disabled)
330            .push_opt(
331                "prevent_sharing_groups_outside_hierarchy",
332                self.prevent_sharing_groups_outside_hierarchy,
333            )
334            .push_opt("lfs_enabled", self.lfs_enabled)
335            .push_opt("request_access_enabled", self.request_access_enabled)
336            .push_opt("parent_id", self.parent_id)
337            .push_opt("default_branch", self.default_branch.as_ref())
338            .push_opt(
339                "enabled_git_access_protocol",
340                self.enabled_git_access_protocol,
341            )
342            .push_opt("shared_runners_setting", self.shared_runners_setting)
343            .push_opt(
344                "shared_runners_minutes_limit",
345                self.shared_runners_minutes_limit,
346            )
347            .push_opt(
348                "extra_shared_runners_minutes_limit",
349                self.extra_shared_runners_minutes_limit,
350            )
351            .push_opt("file_template_project_id", self.file_template_project_id)
352            .push_opt(
353                "prevent_forking_outside_group",
354                self.prevent_forking_outside_group,
355            )
356            .push_opt("ip_restriction_ranges", self.ip_restriction_ranges.as_ref())
357            .push_opt(
358                "allowed_email_domains",
359                self.allowed_email_domains_list.as_ref(),
360            )
361            .push_opt("wiki_access_level", self.wiki_access_level)
362            .push_opt("duo_availability", self.duo_availability)
363            .push_opt(
364                "experiment_features_enabled",
365                self.experiment_features_enabled,
366            )
367            .push_opt(
368                "math_rendering_limits_enabled",
369                self.math_rendering_limits_enabled,
370            )
371            .push_opt(
372                "lock_math_rendering_limits_enabled",
373                self.lock_math_rendering_limits_enabled,
374            )
375            .push_opt("duo_features_enabled", self.duo_features_enabled)
376            .push_opt("max_artifacts_size", self.max_artifacts_size)
377            .push_opt(
378                "unique_project_download_limit",
379                self.unique_project_download_limit,
380            )
381            .push_opt(
382                "unique_project_download_limit_interval_in_seconds",
383                self.unique_project_download_limit_interval
384                    .map(|interval| interval.as_secs()),
385            )
386            .extend(
387                self.unique_project_download_limit_allowlist
388                    .iter()
389                    .map(|value| ("unique_project_download_limit_allowlist[]", value)),
390            )
391            .extend(
392                self.unique_project_download_limit_alertlist
393                    .iter()
394                    .map(|&value| ("unique_project_download_limit_alertlist[]", value)),
395            )
396            .push_opt(
397                "auto_ban_user_on_excessive_projects_download",
398                self.auto_ban_user_on_excessive_projects_download,
399            );
400
401        if let Some(defaults) = self.default_branch_protection_defaults.as_ref() {
402            defaults.add_query(&mut params);
403        }
404
405        params.into_body()
406    }
407}
408
409#[cfg(test)]
410mod tests {
411    use std::time::Duration;
412
413    use http::Method;
414
415    use crate::api::common::VisibilityLevel;
416    use crate::api::groups::{
417        BranchProtectionAccessLevel, BranchProtectionDefaults, DuoAvailability, EditGroup,
418        EditGroupBuilderError, GitAccessProtocol, GroupProjectCreationAccessLevel,
419        SharedRunnersMinutesLimit, SharedRunnersSetting, SubgroupCreationAccessLevel,
420    };
421    use crate::api::projects::FeatureAccessLevel;
422    use crate::api::{self, Query};
423    use crate::test::client::{ExpectedUrl, SingleTestClient};
424
425    #[test]
426    fn shared_runners_setting_as_str() {
427        let items = &[
428            (SharedRunnersSetting::Enabled, "enabled"),
429            (
430                SharedRunnersSetting::DisabledWithOverride,
431                "disabled_with_override",
432            ),
433            (
434                SharedRunnersSetting::DisableAndUnoverridable,
435                "disabled_and_unoverridable",
436            ),
437        ];
438
439        for (i, s) in items {
440            assert_eq!(i.as_str(), *s);
441        }
442    }
443
444    #[test]
445    fn group_is_necessary() {
446        let err = EditGroup::builder().build().unwrap_err();
447        crate::test::assert_missing_field!(err, EditGroupBuilderError, "group");
448    }
449
450    #[test]
451    fn group_is_sufficient() {
452        EditGroup::builder().group("group").build().unwrap();
453    }
454
455    #[test]
456    fn endpoint() {
457        let endpoint = ExpectedUrl::builder()
458            .method(Method::PUT)
459            .endpoint("groups/simple%2Fgroup")
460            .content_type("application/x-www-form-urlencoded")
461            .build()
462            .unwrap();
463        let client = SingleTestClient::new_raw(endpoint, "");
464
465        let endpoint = EditGroup::builder().group("simple/group").build().unwrap();
466        api::ignore(endpoint).query(&client).unwrap();
467    }
468
469    #[test]
470    fn endpoint_name() {
471        let endpoint = ExpectedUrl::builder()
472            .method(Method::PUT)
473            .endpoint("groups/simple%2Fgroup")
474            .content_type("application/x-www-form-urlencoded")
475            .body_str("name=name")
476            .build()
477            .unwrap();
478        let client = SingleTestClient::new_raw(endpoint, "");
479
480        let endpoint = EditGroup::builder()
481            .group("simple/group")
482            .name("name")
483            .build()
484            .unwrap();
485        api::ignore(endpoint).query(&client).unwrap();
486    }
487
488    #[test]
489    fn endpoint_path() {
490        let endpoint = ExpectedUrl::builder()
491            .method(Method::PUT)
492            .endpoint("groups/simple%2Fgroup")
493            .content_type("application/x-www-form-urlencoded")
494            .body_str("path=path")
495            .build()
496            .unwrap();
497        let client = SingleTestClient::new_raw(endpoint, "");
498
499        let endpoint = EditGroup::builder()
500            .group("simple/group")
501            .path("path")
502            .build()
503            .unwrap();
504        api::ignore(endpoint).query(&client).unwrap();
505    }
506
507    #[test]
508    fn endpoint_description() {
509        let endpoint = ExpectedUrl::builder()
510            .method(Method::PUT)
511            .endpoint("groups/simple%2Fgroup")
512            .content_type("application/x-www-form-urlencoded")
513            .body_str("description=description")
514            .build()
515            .unwrap();
516        let client = SingleTestClient::new_raw(endpoint, "");
517
518        let endpoint = EditGroup::builder()
519            .group("simple/group")
520            .description("description")
521            .build()
522            .unwrap();
523        api::ignore(endpoint).query(&client).unwrap();
524    }
525
526    #[test]
527    fn endpoint_membership_lock() {
528        let endpoint = ExpectedUrl::builder()
529            .method(Method::PUT)
530            .endpoint("groups/simple%2Fgroup")
531            .content_type("application/x-www-form-urlencoded")
532            .body_str("membership_lock=true")
533            .build()
534            .unwrap();
535        let client = SingleTestClient::new_raw(endpoint, "");
536
537        let endpoint = EditGroup::builder()
538            .group("simple/group")
539            .membership_lock(true)
540            .build()
541            .unwrap();
542        api::ignore(endpoint).query(&client).unwrap();
543    }
544
545    #[test]
546    fn endpoint_visibility() {
547        let endpoint = ExpectedUrl::builder()
548            .method(Method::PUT)
549            .endpoint("groups/simple%2Fgroup")
550            .content_type("application/x-www-form-urlencoded")
551            .body_str("visibility=internal")
552            .build()
553            .unwrap();
554        let client = SingleTestClient::new_raw(endpoint, "");
555
556        let endpoint = EditGroup::builder()
557            .group("simple/group")
558            .visibility(VisibilityLevel::Internal)
559            .build()
560            .unwrap();
561        api::ignore(endpoint).query(&client).unwrap();
562    }
563
564    #[test]
565    fn endpoint_share_with_group_lock() {
566        let endpoint = ExpectedUrl::builder()
567            .method(Method::PUT)
568            .endpoint("groups/simple%2Fgroup")
569            .content_type("application/x-www-form-urlencoded")
570            .body_str("share_with_group_lock=true")
571            .build()
572            .unwrap();
573        let client = SingleTestClient::new_raw(endpoint, "");
574
575        let endpoint = EditGroup::builder()
576            .group("simple/group")
577            .share_with_group_lock(true)
578            .build()
579            .unwrap();
580        api::ignore(endpoint).query(&client).unwrap();
581    }
582
583    #[test]
584    fn endpoint_require_two_factor_authentication() {
585        let endpoint = ExpectedUrl::builder()
586            .method(Method::PUT)
587            .endpoint("groups/simple%2Fgroup")
588            .content_type("application/x-www-form-urlencoded")
589            .body_str("require_two_factor_authentication=true")
590            .build()
591            .unwrap();
592        let client = SingleTestClient::new_raw(endpoint, "");
593
594        let endpoint = EditGroup::builder()
595            .group("simple/group")
596            .require_two_factor_authentication(true)
597            .build()
598            .unwrap();
599        api::ignore(endpoint).query(&client).unwrap();
600    }
601
602    #[test]
603    fn endpoint_two_factor_grace_period() {
604        let endpoint = ExpectedUrl::builder()
605            .method(Method::PUT)
606            .endpoint("groups/simple%2Fgroup")
607            .content_type("application/x-www-form-urlencoded")
608            .body_str("two_factor_grace_period=1")
609            .build()
610            .unwrap();
611        let client = SingleTestClient::new_raw(endpoint, "");
612
613        let endpoint = EditGroup::builder()
614            .group("simple/group")
615            .two_factor_grace_period(1)
616            .build()
617            .unwrap();
618        api::ignore(endpoint).query(&client).unwrap();
619    }
620
621    #[test]
622    fn endpoint_project_creation_level() {
623        let endpoint = ExpectedUrl::builder()
624            .method(Method::PUT)
625            .endpoint("groups/simple%2Fgroup")
626            .content_type("application/x-www-form-urlencoded")
627            .body_str("project_creation_level=maintainer")
628            .build()
629            .unwrap();
630        let client = SingleTestClient::new_raw(endpoint, "");
631
632        let endpoint = EditGroup::builder()
633            .group("simple/group")
634            .project_creation_level(GroupProjectCreationAccessLevel::Maintainer)
635            .build()
636            .unwrap();
637        api::ignore(endpoint).query(&client).unwrap();
638    }
639
640    #[test]
641    fn endpoint_auto_devops_enabled() {
642        let endpoint = ExpectedUrl::builder()
643            .method(Method::PUT)
644            .endpoint("groups/simple%2Fgroup")
645            .content_type("application/x-www-form-urlencoded")
646            .body_str("auto_devops_enabled=false")
647            .build()
648            .unwrap();
649        let client = SingleTestClient::new_raw(endpoint, "");
650
651        let endpoint = EditGroup::builder()
652            .group("simple/group")
653            .auto_devops_enabled(false)
654            .build()
655            .unwrap();
656        api::ignore(endpoint).query(&client).unwrap();
657    }
658
659    #[test]
660    fn endpoint_subgroup_creation_level() {
661        let endpoint = ExpectedUrl::builder()
662            .method(Method::PUT)
663            .endpoint("groups/simple%2Fgroup")
664            .content_type("application/x-www-form-urlencoded")
665            .body_str("subgroup_creation_level=owner")
666            .build()
667            .unwrap();
668        let client = SingleTestClient::new_raw(endpoint, "");
669
670        let endpoint = EditGroup::builder()
671            .group("simple/group")
672            .subgroup_creation_level(SubgroupCreationAccessLevel::Owner)
673            .build()
674            .unwrap();
675        api::ignore(endpoint).query(&client).unwrap();
676    }
677
678    #[test]
679    fn endpoint_emails_enabled() {
680        let endpoint = ExpectedUrl::builder()
681            .method(Method::PUT)
682            .endpoint("groups/simple%2Fgroup")
683            .content_type("application/x-www-form-urlencoded")
684            .body_str("emails_enabled=false")
685            .build()
686            .unwrap();
687        let client = SingleTestClient::new_raw(endpoint, "");
688
689        let endpoint = EditGroup::builder()
690            .group("simple/group")
691            .emails_enabled(false)
692            .build()
693            .unwrap();
694        api::ignore(endpoint).query(&client).unwrap();
695    }
696
697    #[test]
698    fn endpoint_mentions_disabled() {
699        let endpoint = ExpectedUrl::builder()
700            .method(Method::PUT)
701            .endpoint("groups/simple%2Fgroup")
702            .content_type("application/x-www-form-urlencoded")
703            .body_str("mentions_disabled=true")
704            .build()
705            .unwrap();
706        let client = SingleTestClient::new_raw(endpoint, "");
707
708        let endpoint = EditGroup::builder()
709            .group("simple/group")
710            .mentions_disabled(true)
711            .build()
712            .unwrap();
713        api::ignore(endpoint).query(&client).unwrap();
714    }
715
716    #[test]
717    fn endpoint_prevent_sharing_groups_outside_hierarchy() {
718        let endpoint = ExpectedUrl::builder()
719            .method(Method::PUT)
720            .endpoint("groups/simple%2Fgroup")
721            .content_type("application/x-www-form-urlencoded")
722            .body_str("prevent_sharing_groups_outside_hierarchy=true")
723            .build()
724            .unwrap();
725        let client = SingleTestClient::new_raw(endpoint, "");
726
727        let endpoint = EditGroup::builder()
728            .group("simple/group")
729            .prevent_sharing_groups_outside_hierarchy(true)
730            .build()
731            .unwrap();
732        api::ignore(endpoint).query(&client).unwrap();
733    }
734
735    #[test]
736    fn endpoint_lfs_enabled() {
737        let endpoint = ExpectedUrl::builder()
738            .method(Method::PUT)
739            .endpoint("groups/simple%2Fgroup")
740            .content_type("application/x-www-form-urlencoded")
741            .body_str("lfs_enabled=true")
742            .build()
743            .unwrap();
744        let client = SingleTestClient::new_raw(endpoint, "");
745
746        let endpoint = EditGroup::builder()
747            .group("simple/group")
748            .lfs_enabled(true)
749            .build()
750            .unwrap();
751        api::ignore(endpoint).query(&client).unwrap();
752    }
753
754    #[test]
755    fn endpoint_request_access_enabled() {
756        let endpoint = ExpectedUrl::builder()
757            .method(Method::PUT)
758            .endpoint("groups/simple%2Fgroup")
759            .content_type("application/x-www-form-urlencoded")
760            .body_str("request_access_enabled=true")
761            .build()
762            .unwrap();
763        let client = SingleTestClient::new_raw(endpoint, "");
764
765        let endpoint = EditGroup::builder()
766            .group("simple/group")
767            .request_access_enabled(true)
768            .build()
769            .unwrap();
770        api::ignore(endpoint).query(&client).unwrap();
771    }
772
773    #[test]
774    fn endpoint_parent_id() {
775        let endpoint = ExpectedUrl::builder()
776            .method(Method::PUT)
777            .endpoint("groups/simple%2Fgroup")
778            .content_type("application/x-www-form-urlencoded")
779            .body_str("parent_id=1")
780            .build()
781            .unwrap();
782        let client = SingleTestClient::new_raw(endpoint, "");
783
784        let endpoint = EditGroup::builder()
785            .group("simple/group")
786            .parent_id(1)
787            .build()
788            .unwrap();
789        api::ignore(endpoint).query(&client).unwrap();
790    }
791
792    #[test]
793    fn endpoint_default_branch() {
794        let endpoint = ExpectedUrl::builder()
795            .method(Method::PUT)
796            .endpoint("groups/simple%2Fgroup")
797            .content_type("application/x-www-form-urlencoded")
798            .body_str("default_branch=main")
799            .build()
800            .unwrap();
801        let client = SingleTestClient::new_raw(endpoint, "");
802
803        let endpoint = EditGroup::builder()
804            .group("simple/group")
805            .default_branch("main")
806            .build()
807            .unwrap();
808        api::ignore(endpoint).query(&client).unwrap();
809    }
810
811    #[test]
812    fn endpoint_enabled_git_access_protocol() {
813        let endpoint = ExpectedUrl::builder()
814            .method(Method::PUT)
815            .endpoint("groups/simple%2Fgroup")
816            .content_type("application/x-www-form-urlencoded")
817            .body_str("enabled_git_access_protocol=http")
818            .build()
819            .unwrap();
820        let client = SingleTestClient::new_raw(endpoint, "");
821
822        let endpoint = EditGroup::builder()
823            .group("simple/group")
824            .enabled_git_access_protocol(GitAccessProtocol::Http)
825            .build()
826            .unwrap();
827        api::ignore(endpoint).query(&client).unwrap();
828    }
829
830    #[test]
831    fn endpoint_default_branch_protection_defaults_allowed_to_push() {
832        let endpoint = ExpectedUrl::builder()
833            .method(Method::PUT)
834            .endpoint("groups/simple%2Fgroup")
835            .content_type("application/x-www-form-urlencoded")
836            .body_str("default_branch_protection_defaults%5Ballowed_to_push%5D%5B%5D=30")
837            .build()
838            .unwrap();
839        let client = SingleTestClient::new_raw(endpoint, "");
840
841        let endpoint = EditGroup::builder()
842            .group("simple/group")
843            .default_branch_protection_defaults(
844                BranchProtectionDefaults::builder()
845                    .allowed_to_push(BranchProtectionAccessLevel::Developer)
846                    .allowed_to_push(BranchProtectionAccessLevel::Maintainer)
847                    .not_allowed_to_push(BranchProtectionAccessLevel::Maintainer)
848                    .build()
849                    .unwrap(),
850            )
851            .build()
852            .unwrap();
853        api::ignore(endpoint).query(&client).unwrap();
854    }
855
856    #[test]
857    fn endpoint_default_branch_protection_defaults_allow_force_push() {
858        let endpoint = ExpectedUrl::builder()
859            .method(Method::PUT)
860            .endpoint("groups/simple%2Fgroup")
861            .content_type("application/x-www-form-urlencoded")
862            .body_str("default_branch_protection_defaults%5Ballow_force_push%5D=true")
863            .build()
864            .unwrap();
865        let client = SingleTestClient::new_raw(endpoint, "");
866
867        let endpoint = EditGroup::builder()
868            .group("simple/group")
869            .default_branch_protection_defaults(
870                BranchProtectionDefaults::builder()
871                    .allow_force_push(true)
872                    .build()
873                    .unwrap(),
874            )
875            .build()
876            .unwrap();
877        api::ignore(endpoint).query(&client).unwrap();
878    }
879
880    #[test]
881    fn endpoint_default_branch_protection_defaults_allowed_to_merge() {
882        let endpoint = ExpectedUrl::builder()
883            .method(Method::PUT)
884            .endpoint("groups/simple%2Fgroup")
885            .content_type("application/x-www-form-urlencoded")
886            .body_str("default_branch_protection_defaults%5Ballowed_to_merge%5D%5B%5D=30")
887            .build()
888            .unwrap();
889        let client = SingleTestClient::new_raw(endpoint, "");
890
891        let endpoint = EditGroup::builder()
892            .group("simple/group")
893            .default_branch_protection_defaults(
894                BranchProtectionDefaults::builder()
895                    .allowed_to_merge(BranchProtectionAccessLevel::Developer)
896                    .allowed_to_merge(BranchProtectionAccessLevel::Maintainer)
897                    .not_allowed_to_merge(BranchProtectionAccessLevel::Maintainer)
898                    .build()
899                    .unwrap(),
900            )
901            .build()
902            .unwrap();
903        api::ignore(endpoint).query(&client).unwrap();
904    }
905
906    #[test]
907    fn endpoint_default_branch_protection_defaults_developer_can_initial_push() {
908        let endpoint = ExpectedUrl::builder()
909            .method(Method::PUT)
910            .endpoint("groups/simple%2Fgroup")
911            .content_type("application/x-www-form-urlencoded")
912            .body_str("default_branch_protection_defaults%5Bdeveloper_can_initial_push%5D=true")
913            .build()
914            .unwrap();
915        let client = SingleTestClient::new_raw(endpoint, "");
916
917        let endpoint = EditGroup::builder()
918            .group("simple/group")
919            .default_branch_protection_defaults(
920                BranchProtectionDefaults::builder()
921                    .developer_can_initial_push(true)
922                    .build()
923                    .unwrap(),
924            )
925            .build()
926            .unwrap();
927        api::ignore(endpoint).query(&client).unwrap();
928    }
929
930    #[test]
931    fn endpoint_shared_runners_setting() {
932        let endpoint = ExpectedUrl::builder()
933            .method(Method::PUT)
934            .endpoint("groups/simple%2Fgroup")
935            .content_type("application/x-www-form-urlencoded")
936            .body_str("shared_runners_setting=disabled_with_override")
937            .build()
938            .unwrap();
939        let client = SingleTestClient::new_raw(endpoint, "");
940
941        let endpoint = EditGroup::builder()
942            .group("simple/group")
943            .shared_runners_setting(SharedRunnersSetting::DisabledWithOverride)
944            .build()
945            .unwrap();
946        api::ignore(endpoint).query(&client).unwrap();
947    }
948
949    #[test]
950    fn endpoint_shared_runners_minutes_limit() {
951        let endpoint = ExpectedUrl::builder()
952            .method(Method::PUT)
953            .endpoint("groups/simple%2Fgroup")
954            .content_type("application/x-www-form-urlencoded")
955            .body_str("shared_runners_minutes_limit=0")
956            .build()
957            .unwrap();
958        let client = SingleTestClient::new_raw(endpoint, "");
959
960        let endpoint = EditGroup::builder()
961            .group("simple/group")
962            .shared_runners_minutes_limit(SharedRunnersMinutesLimit::Unlimited)
963            .build()
964            .unwrap();
965        api::ignore(endpoint).query(&client).unwrap();
966    }
967
968    #[test]
969    fn endpoint_shared_runners_minutes_limit_into() {
970        let endpoint = ExpectedUrl::builder()
971            .method(Method::PUT)
972            .endpoint("groups/simple%2Fgroup")
973            .content_type("application/x-www-form-urlencoded")
974            .body_str("shared_runners_minutes_limit=1")
975            .build()
976            .unwrap();
977        let client = SingleTestClient::new_raw(endpoint, "");
978
979        let endpoint = EditGroup::builder()
980            .group("simple/group")
981            .shared_runners_minutes_limit(1)
982            .build()
983            .unwrap();
984        api::ignore(endpoint).query(&client).unwrap();
985    }
986
987    #[test]
988    fn endpoint_extra_shared_runners_minutes_limit() {
989        let endpoint = ExpectedUrl::builder()
990            .method(Method::PUT)
991            .endpoint("groups/simple%2Fgroup")
992            .content_type("application/x-www-form-urlencoded")
993            .body_str("extra_shared_runners_minutes_limit=1")
994            .build()
995            .unwrap();
996        let client = SingleTestClient::new_raw(endpoint, "");
997
998        let endpoint = EditGroup::builder()
999            .group("simple/group")
1000            .extra_shared_runners_minutes_limit(1)
1001            .build()
1002            .unwrap();
1003        api::ignore(endpoint).query(&client).unwrap();
1004    }
1005
1006    #[test]
1007    fn endpoint_file_template_project_id() {
1008        let endpoint = ExpectedUrl::builder()
1009            .method(Method::PUT)
1010            .endpoint("groups/simple%2Fgroup")
1011            .content_type("application/x-www-form-urlencoded")
1012            .body_str("file_template_project_id=1")
1013            .build()
1014            .unwrap();
1015        let client = SingleTestClient::new_raw(endpoint, "");
1016
1017        let endpoint = EditGroup::builder()
1018            .group("simple/group")
1019            .file_template_project_id(1)
1020            .build()
1021            .unwrap();
1022        api::ignore(endpoint).query(&client).unwrap();
1023    }
1024
1025    #[test]
1026    fn endpoint_prevent_forking_outside_group() {
1027        let endpoint = ExpectedUrl::builder()
1028            .method(Method::PUT)
1029            .endpoint("groups/simple%2Fgroup")
1030            .content_type("application/x-www-form-urlencoded")
1031            .body_str("prevent_forking_outside_group=true")
1032            .build()
1033            .unwrap();
1034        let client = SingleTestClient::new_raw(endpoint, "");
1035
1036        let endpoint = EditGroup::builder()
1037            .group("simple/group")
1038            .prevent_forking_outside_group(true)
1039            .build()
1040            .unwrap();
1041        api::ignore(endpoint).query(&client).unwrap();
1042    }
1043
1044    #[test]
1045    fn endpoint_allowed_email_domains() {
1046        let endpoint = ExpectedUrl::builder()
1047            .method(Method::PUT)
1048            .endpoint("groups/simple%2Fgroup")
1049            .content_type("application/x-www-form-urlencoded")
1050            .body_str("allowed_email_domains=example.com%2Cexample.org")
1051            .build()
1052            .unwrap();
1053        let client = SingleTestClient::new_raw(endpoint, "");
1054
1055        let endpoint = EditGroup::builder()
1056            .group("simple/group")
1057            .allowed_email_domain("example.com")
1058            .allowed_email_domains(["example.org"].iter().copied())
1059            .build()
1060            .unwrap();
1061        api::ignore(endpoint).query(&client).unwrap();
1062    }
1063
1064    #[test]
1065    fn endpoint_ip_restriction_ranges() {
1066        let endpoint = ExpectedUrl::builder()
1067            .method(Method::PUT)
1068            .endpoint("groups/simple%2Fgroup")
1069            .content_type("application/x-www-form-urlencoded")
1070            .body_str("ip_restriction_ranges=10.0.0.0%2F8%2C192.168.1.1%2C192.168.1.128%2F7")
1071            .build()
1072            .unwrap();
1073        let client = SingleTestClient::new_raw(endpoint, "");
1074
1075        let endpoint = EditGroup::builder()
1076            .group("simple/group")
1077            .ip_restriction_range("10.0.0.0/8")
1078            .ip_restriction_ranges(["192.168.1.1", "192.168.1.128/7"].iter().copied())
1079            .build()
1080            .unwrap();
1081        api::ignore(endpoint).query(&client).unwrap();
1082    }
1083
1084    #[test]
1085    fn endpoint_wiki_access_level() {
1086        let endpoint = ExpectedUrl::builder()
1087            .method(Method::PUT)
1088            .endpoint("groups/simple%2Fgroup")
1089            .content_type("application/x-www-form-urlencoded")
1090            .body_str("wiki_access_level=disabled")
1091            .build()
1092            .unwrap();
1093        let client = SingleTestClient::new_raw(endpoint, "");
1094
1095        let endpoint = EditGroup::builder()
1096            .group("simple/group")
1097            .wiki_access_level(FeatureAccessLevel::Disabled)
1098            .build()
1099            .unwrap();
1100        api::ignore(endpoint).query(&client).unwrap();
1101    }
1102
1103    #[test]
1104    fn endpoint_duo_availability() {
1105        let endpoint = ExpectedUrl::builder()
1106            .method(Method::PUT)
1107            .endpoint("groups/simple%2Fgroup")
1108            .content_type("application/x-www-form-urlencoded")
1109            .body_str("duo_availability=default_off")
1110            .build()
1111            .unwrap();
1112        let client = SingleTestClient::new_raw(endpoint, "");
1113
1114        let endpoint = EditGroup::builder()
1115            .group("simple/group")
1116            .duo_availability(DuoAvailability::DefaultOff)
1117            .build()
1118            .unwrap();
1119        api::ignore(endpoint).query(&client).unwrap();
1120    }
1121
1122    #[test]
1123    fn endpoint_experiment_features_enabled() {
1124        let endpoint = ExpectedUrl::builder()
1125            .method(Method::PUT)
1126            .endpoint("groups/simple%2Fgroup")
1127            .content_type("application/x-www-form-urlencoded")
1128            .body_str("experiment_features_enabled=true")
1129            .build()
1130            .unwrap();
1131        let client = SingleTestClient::new_raw(endpoint, "");
1132
1133        let endpoint = EditGroup::builder()
1134            .group("simple/group")
1135            .experiment_features_enabled(true)
1136            .build()
1137            .unwrap();
1138        api::ignore(endpoint).query(&client).unwrap();
1139    }
1140
1141    #[test]
1142    fn endpoint_math_rendering_limits_enabled() {
1143        let endpoint = ExpectedUrl::builder()
1144            .method(Method::PUT)
1145            .endpoint("groups/simple%2Fgroup")
1146            .content_type("application/x-www-form-urlencoded")
1147            .body_str("math_rendering_limits_enabled=true")
1148            .build()
1149            .unwrap();
1150        let client = SingleTestClient::new_raw(endpoint, "");
1151
1152        let endpoint = EditGroup::builder()
1153            .group("simple/group")
1154            .math_rendering_limits_enabled(true)
1155            .build()
1156            .unwrap();
1157        api::ignore(endpoint).query(&client).unwrap();
1158    }
1159
1160    #[test]
1161    fn endpoint_lock_math_rendering_limits_enabled() {
1162        let endpoint = ExpectedUrl::builder()
1163            .method(Method::PUT)
1164            .endpoint("groups/simple%2Fgroup")
1165            .content_type("application/x-www-form-urlencoded")
1166            .body_str("lock_math_rendering_limits_enabled=true")
1167            .build()
1168            .unwrap();
1169        let client = SingleTestClient::new_raw(endpoint, "");
1170
1171        let endpoint = EditGroup::builder()
1172            .group("simple/group")
1173            .lock_math_rendering_limits_enabled(true)
1174            .build()
1175            .unwrap();
1176        api::ignore(endpoint).query(&client).unwrap();
1177    }
1178
1179    #[test]
1180    fn endpoint_duo_features_enabled() {
1181        let endpoint = ExpectedUrl::builder()
1182            .method(Method::PUT)
1183            .endpoint("groups/simple%2Fgroup")
1184            .content_type("application/x-www-form-urlencoded")
1185            .body_str("duo_features_enabled=true")
1186            .build()
1187            .unwrap();
1188        let client = SingleTestClient::new_raw(endpoint, "");
1189
1190        let endpoint = EditGroup::builder()
1191            .group("simple/group")
1192            .duo_features_enabled(true)
1193            .build()
1194            .unwrap();
1195        api::ignore(endpoint).query(&client).unwrap();
1196    }
1197
1198    #[test]
1199    fn endpoint_max_artifacts_size() {
1200        let endpoint = ExpectedUrl::builder()
1201            .method(Method::PUT)
1202            .endpoint("groups/simple%2Fgroup")
1203            .content_type("application/x-www-form-urlencoded")
1204            .body_str("max_artifacts_size=1024")
1205            .build()
1206            .unwrap();
1207        let client = SingleTestClient::new_raw(endpoint, "");
1208
1209        let endpoint = EditGroup::builder()
1210            .group("simple/group")
1211            .max_artifacts_size(1024)
1212            .build()
1213            .unwrap();
1214        api::ignore(endpoint).query(&client).unwrap();
1215    }
1216
1217    #[test]
1218    fn endpoint_unique_project_download_limit() {
1219        let endpoint = ExpectedUrl::builder()
1220            .method(Method::PUT)
1221            .endpoint("groups/simple%2Fgroup")
1222            .content_type("application/x-www-form-urlencoded")
1223            .body_str("unique_project_download_limit=100")
1224            .build()
1225            .unwrap();
1226        let client = SingleTestClient::new_raw(endpoint, "");
1227
1228        let endpoint = EditGroup::builder()
1229            .group("simple/group")
1230            .unique_project_download_limit(100)
1231            .build()
1232            .unwrap();
1233        api::ignore(endpoint).query(&client).unwrap();
1234    }
1235
1236    #[test]
1237    fn endpoint_unique_project_download_limit_interval_in_seconds() {
1238        let endpoint = ExpectedUrl::builder()
1239            .method(Method::PUT)
1240            .endpoint("groups/simple%2Fgroup")
1241            .content_type("application/x-www-form-urlencoded")
1242            .body_str("unique_project_download_limit_interval_in_seconds=3600")
1243            .build()
1244            .unwrap();
1245        let client = SingleTestClient::new_raw(endpoint, "");
1246
1247        let endpoint = EditGroup::builder()
1248            .group("simple/group")
1249            .unique_project_download_limit_interval(Duration::from_secs(3600))
1250            .build()
1251            .unwrap();
1252        api::ignore(endpoint).query(&client).unwrap();
1253    }
1254
1255    #[test]
1256    fn endpoint_unique_project_download_limit_allowlist() {
1257        let endpoint = ExpectedUrl::builder()
1258            .method(Method::PUT)
1259            .endpoint("groups/simple%2Fgroup")
1260            .content_type("application/x-www-form-urlencoded")
1261            .body_str(concat!(
1262                "unique_project_download_limit_allowlist%5B%5D=auditor",
1263                "&unique_project_download_limit_allowlist%5B%5D=robot",
1264            ))
1265            .build()
1266            .unwrap();
1267        let client = SingleTestClient::new_raw(endpoint, "");
1268
1269        let endpoint = EditGroup::builder()
1270            .group("simple/group")
1271            .unique_project_download_limit_allow("robot")
1272            .unique_project_download_limit_allow_users(["robot", "auditor"].iter().copied())
1273            .build()
1274            .unwrap();
1275        api::ignore(endpoint).query(&client).unwrap();
1276    }
1277
1278    #[test]
1279    fn endpoint_unique_project_download_limit_alertlist() {
1280        let endpoint = ExpectedUrl::builder()
1281            .method(Method::PUT)
1282            .endpoint("groups/simple%2Fgroup")
1283            .content_type("application/x-www-form-urlencoded")
1284            .body_str(concat!(
1285                "unique_project_download_limit_alertlist%5B%5D=1",
1286                "&unique_project_download_limit_alertlist%5B%5D=2",
1287            ))
1288            .build()
1289            .unwrap();
1290        let client = SingleTestClient::new_raw(endpoint, "");
1291
1292        let endpoint = EditGroup::builder()
1293            .group("simple/group")
1294            .unique_project_download_limit_alert(2)
1295            .unique_project_download_limit_alert_users([2, 1].iter().copied())
1296            .build()
1297            .unwrap();
1298        api::ignore(endpoint).query(&client).unwrap();
1299    }
1300
1301    #[test]
1302    fn endpoint_auto_ban_user_on_excessive_projects_download() {
1303        let endpoint = ExpectedUrl::builder()
1304            .method(Method::PUT)
1305            .endpoint("groups/simple%2Fgroup")
1306            .content_type("application/x-www-form-urlencoded")
1307            .body_str("auto_ban_user_on_excessive_projects_download=true")
1308            .build()
1309            .unwrap();
1310        let client = SingleTestClient::new_raw(endpoint, "");
1311
1312        let endpoint = EditGroup::builder()
1313            .group("simple/group")
1314            .auto_ban_user_on_excessive_projects_download(true)
1315            .build()
1316            .unwrap();
1317        api::ignore(endpoint).query(&client).unwrap();
1318    }
1319}