summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNejc Habjan <hab.nejc@gmail.com>2021-05-29 23:47:46 +0200
committerJohn Villalovos <john@sodarock.com>2021-05-29 21:06:15 -0700
commit1b70580020825adf2d1f8c37803bc4655a97be41 (patch)
tree8e754db6fb365ce7c7fa01e51e527e5fdded630f
parent237b97ceb0614821e59ea041f43a9806b65cdf8c (diff)
downloadgitlab-1b70580020825adf2d1f8c37803bc4655a97be41.tar.gz
feat(objects): add support for descendant groups API
-rw-r--r--docs/gl_objects/groups.rst25
-rw-r--r--gitlab/v4/objects/groups.py17
-rw-r--r--tests/functional/api/test_groups.py1
-rw-r--r--tests/unit/objects/test_groups.py58
4 files changed, 101 insertions, 0 deletions
diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst
index 9f1b049..596db0a 100644
--- a/docs/gl_objects/groups.rst
+++ b/docs/gl_objects/groups.rst
@@ -167,6 +167,31 @@ List the subgroups for a group::
real_group = gl.groups.get(subgroup_id, lazy=True)
real_group.issues.list()
+Descendant Groups
+=================
+
+Reference
+---------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.GroupDescendantGroup`
+ + :class:`gitlab.v4.objects.GroupDescendantGroupManager`
+ + :attr:`gitlab.v4.objects.Group.descendant_groups`
+
+Examples
+--------
+
+List the descendant groups of a group::
+
+ descendant_groups = group.descendant_groups.list()
+
+.. note::
+
+ Like the ``GroupSubgroup`` objects described above, ``GroupDescendantGroup``
+ objects do not expose the same API as the ``Group`` objects. Create a new
+ ``Group`` object instead if needed, as shown in the subgroup example.
+
Group custom attributes
=======================
diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py
index e29edc8..860a056 100644
--- a/gitlab/v4/objects/groups.py
+++ b/gitlab/v4/objects/groups.py
@@ -31,6 +31,8 @@ from .variables import GroupVariableManager # noqa: F401
__all__ = [
"Group",
"GroupManager",
+ "GroupDescendantGroup",
+ "GroupDescendantGroupManager",
"GroupSubgroup",
"GroupSubgroupManager",
]
@@ -45,6 +47,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
("billable_members", "GroupBillableMemberManager"),
("boards", "GroupBoardManager"),
("customattributes", "GroupCustomAttributeManager"),
+ ("descendant_groups", "GroupDescendantGroupManager"),
("exports", "GroupExportManager"),
("epics", "GroupEpicManager"),
("imports", "GroupImportManager"),
@@ -310,3 +313,17 @@ class GroupSubgroupManager(ListMixin, RESTManager):
"min_access_level",
)
_types = {"skip_groups": types.ListAttribute}
+
+
+class GroupDescendantGroup(RESTObject):
+ pass
+
+
+class GroupDescendantGroupManager(GroupSubgroupManager):
+ """
+ This manager inherits from GroupSubgroupManager as descendant groups
+ share all attributes with subgroups, except the path and object class.
+ """
+
+ _path = "/groups/%(group_id)s/descendant_groups"
+ _obj_cls = GroupDescendantGroup
diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py
index eae2d9b..c2b8cbd 100644
--- a/tests/functional/api/test_groups.py
+++ b/tests/functional/api/test_groups.py
@@ -32,6 +32,7 @@ def test_groups(gl):
assert len(gl.groups.list(search="oup1")) == 1
assert group3.parent_id == p_id
assert group2.subgroups.list()[0].id == group3.id
+ assert group2.descendant_groups.list()[0].id == group3.id
filtered_groups = gl.groups.list(skip_groups=[group3.id, group4.id])
assert group3 not in filtered_groups
diff --git a/tests/unit/objects/test_groups.py b/tests/unit/objects/test_groups.py
index d4786f4..37023d8 100644
--- a/tests/unit/objects/test_groups.py
+++ b/tests/unit/objects/test_groups.py
@@ -2,10 +2,41 @@
GitLab API: https://docs.gitlab.com/ce/api/groups.html
"""
+import re
+
import pytest
import responses
import gitlab
+from gitlab.v4.objects import GroupDescendantGroup, GroupSubgroup
+
+subgroup_descgroup_content = [
+ {
+ "id": 2,
+ "name": "Bar Group",
+ "path": "foo/bar",
+ "description": "A subgroup of Foo Group",
+ "visibility": "public",
+ "share_with_group_lock": False,
+ "require_two_factor_authentication": False,
+ "two_factor_grace_period": 48,
+ "project_creation_level": "developer",
+ "auto_devops_enabled": None,
+ "subgroup_creation_level": "owner",
+ "emails_disabled": None,
+ "mentions_disabled": None,
+ "lfs_enabled": True,
+ "default_branch_protection": 2,
+ "avatar_url": "http://gitlab.example.com/uploads/group/avatar/1/bar.jpg",
+ "web_url": "http://gitlab.example.com/groups/foo/bar",
+ "request_access_enabled": False,
+ "full_name": "Bar Group",
+ "full_path": "foo/bar",
+ "file_template_project_id": 1,
+ "parent_id": 123,
+ "created_at": "2020-01-15T12:36:29.590Z",
+ },
+]
@pytest.fixture
@@ -38,6 +69,21 @@ def resp_groups():
@pytest.fixture
+def resp_list_subgroups_descendant_groups():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url=re.compile(
+ r"http://localhost/api/v4/groups/1/(subgroups|descendant_groups)"
+ ),
+ json=subgroup_descgroup_content,
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
def resp_create_import(accepted_content):
with responses.RequestsMock() as rsps:
rsps.add(
@@ -71,6 +117,18 @@ def test_create_group_export(group, resp_export):
assert export.message == "202 Accepted"
+def test_list_group_subgroups(group, resp_list_subgroups_descendant_groups):
+ subgroups = group.subgroups.list()
+ assert isinstance(subgroups[0], GroupSubgroup)
+ assert subgroups[0].path == subgroup_descgroup_content[0]["path"]
+
+
+def test_list_group_descendant_groups(group, resp_list_subgroups_descendant_groups):
+ descendant_groups = group.descendant_groups.list()
+ assert isinstance(descendant_groups[0], GroupDescendantGroup)
+ assert descendant_groups[0].path == subgroup_descgroup_content[0]["path"]
+
+
@pytest.mark.skip("GitLab API endpoint not implemented")
def test_refresh_group_export_status(group, resp_export):
export = group.exports.create()