diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index 596db0a40..44fb11ddd 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -338,3 +338,43 @@ You can use the ``ldapgroups`` manager to list available LDAP groups:: # list the groups for a specific LDAP provider ldap_groups = gl.ldapgroups.list(search='foo', provider='ldapmain') + +Groups hooks +============ + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.GroupHook` + + :class:`gitlab.v4.objects.GroupHookManager` + + :attr:`gitlab.v4.objects.Group.hooks` + +* GitLab API: https://docs.gitlab.com/ce/api/groups.html#hooks + +Examples +-------- + +List the group hooks:: + + hooks = group.hooks.list() + +Get a group hook:: + + hook = group.hooks.get(hook_id) + +Create a group hook:: + + hook = group.hooks.create({'url': 'http://my/action/url', 'push_events': 1}) + +Update a group hook:: + + hook.push_events = 0 + hook.save() + +Delete a group hook:: + + group.hooks.delete(hook_id) + # or + hook.delete() diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index 429d95da0..ee82415e1 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -13,6 +13,7 @@ from .deploy_tokens import GroupDeployTokenManager # noqa: F401 from .epics import GroupEpicManager # noqa: F401 from .export_import import GroupExportManager, GroupImportManager # noqa: F401 +from .hooks import GroupHookManager # noqa: F401 from .issues import GroupIssueManager # noqa: F401 from .labels import GroupLabelManager # noqa: F401 from .members import ( # noqa: F401 @@ -52,6 +53,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): ("descendant_groups", "GroupDescendantGroupManager"), ("exports", "GroupExportManager"), ("epics", "GroupEpicManager"), + ("hooks", "GroupHookManager"), ("imports", "GroupImportManager"), ("issues", "GroupIssueManager"), ("issues_statistics", "GroupIssuesStatisticsManager"), diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py index 69b324e8c..428fd765c 100644 --- a/gitlab/v4/objects/hooks.py +++ b/gitlab/v4/objects/hooks.py @@ -6,6 +6,8 @@ "HookManager", "ProjectHook", "ProjectHookManager", + "GroupHook", + "GroupHookManager", ] @@ -60,3 +62,53 @@ class ProjectHookManager(CRUDMixin, RESTManager): "token", ), ) + + +class GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject): + _short_print_attr = "url" + + +class GroupHookManager(CRUDMixin, RESTManager): + _path = "/groups/%(group_id)s/hooks" + _obj_cls = GroupHook + _from_parent_attrs = {"group_id": "id"} + _create_attrs = RequiredOptional( + required=("url",), + optional=( + "push_events", + "issues_events", + "confidential_issues_events", + "merge_requests_events", + "tag_push_events", + "note_events", + "confidential_note_events", + "job_events", + "pipeline_events", + "wiki_page_events", + "deployment_events", + "releases_events", + "subgroup_events", + "enable_ssl_verification", + "token", + ), + ) + _update_attrs = RequiredOptional( + required=("url",), + optional=( + "push_events", + "issues_events", + "confidential_issues_events", + "merge_requests_events", + "tag_push_events", + "note_events", + "confidential_note_events", + "job_events", + "pipeline_events", + "wiki_page_events", + "deployment_events", + "releases_events", + "subgroup_events", + "enable_ssl_verification", + "token", + ), + ) diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py index 439d01ccd..312fc7ec9 100644 --- a/tests/functional/api/test_groups.py +++ b/tests/functional/api/test_groups.py @@ -209,3 +209,16 @@ def test_group_wiki(group): wiki.save() wiki.delete() assert len(group.wikis.list()) == 0 + + +@pytest.mark.skip(reason="EE feature") +def test_group_hooks(group): + hook = group.hooks.create({"url": "http://hook.url"}) + assert len(group.hooks.list()) == 1 + + hook.note_events = True + hook.save() + + hook = group.hooks.get(hook.id) + assert hook.note_events is True + hook.delete() diff --git a/tests/unit/objects/test_hooks.py b/tests/unit/objects/test_hooks.py index fe5c21c98..0f9dbe282 100644 --- a/tests/unit/objects/test_hooks.py +++ b/tests/unit/objects/test_hooks.py @@ -1,29 +1,209 @@ """ GitLab API: https://docs.gitlab.com/ce/api/system_hooks.html +GitLab API: https://docs.gitlab.com/ce/api/groups.html#hooks +GitLab API: https://docs.gitlab.com/ee/api/projects.html#hooks """ + +import re + import pytest import responses -from gitlab.v4.objects import Hook +from gitlab.v4.objects import GroupHook, Hook, ProjectHook + +hooks_content = [ + { + "id": 1, + "url": "testurl", + "push_events": True, + "tag_push_events": True, + }, + { + "id": 2, + "url": "testurl_second", + "push_events": False, + "tag_push_events": False, + }, +] + +hook_content = hooks_content[0] + + +@pytest.fixture +def resp_hooks_list(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks"), + json=hooks_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_hook_get(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1"), + json=hook_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_hook_create(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks"), + json=hook_content, + content_type="application/json", + status=200, + ) + yield rsps @pytest.fixture -def resp_get_hook(): - content = {"url": "testurl", "id": 1} +def resp_hook_update(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1") + rsps.add( + method=responses.GET, + url=pattern, + json=hook_content, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.PUT, + url=pattern, + json=hook_content, + content_type="application/json", + status=200, + ) + yield rsps + +@pytest.fixture +def resp_hook_delete(): with responses.RequestsMock() as rsps: + pattern = re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1") rsps.add( method=responses.GET, - url="http://localhost/api/v4/hooks/1", - json=content, + url=pattern, + json=hook_content, content_type="application/json", status=200, ) + rsps.add( + method=responses.DELETE, + url=pattern, + status=204, + ) yield rsps -def test_hooks(gl, resp_get_hook): +def test_list_system_hooks(gl, resp_hooks_list): + hooks = gl.hooks.list() + assert hooks[0].id == 1 + assert hooks[0].url == "testurl" + assert hooks[1].id == 2 + assert hooks[1].url == "testurl_second" + + +def test_get_system_hook(gl, resp_hook_get): data = gl.hooks.get(1) assert isinstance(data, Hook) assert data.url == "testurl" assert data.id == 1 + + +def test_create_system_hook(gl, resp_hook_create): + hook = gl.hooks.create(hook_content) + assert hook.url == "testurl" + assert hook.push_events is True + assert hook.tag_push_events is True + + +# there is no update method for system hooks + + +def test_delete_system_hook(gl, resp_hook_delete): + hook = gl.hooks.get(1) + hook.delete() + gl.hooks.delete(1) + + +def test_list_group_hooks(group, resp_hooks_list): + hooks = group.hooks.list() + assert hooks[0].id == 1 + assert hooks[0].url == "testurl" + assert hooks[1].id == 2 + assert hooks[1].url == "testurl_second" + + +def test_get_group_hook(group, resp_hook_get): + data = group.hooks.get(1) + assert isinstance(data, GroupHook) + assert data.url == "testurl" + assert data.id == 1 + + +def test_create_group_hook(group, resp_hook_create): + hook = group.hooks.create(hook_content) + assert hook.url == "testurl" + assert hook.push_events is True + assert hook.tag_push_events is True + + +def test_update_group_hook(group, resp_hook_update): + hook = group.hooks.get(1) + assert hook.id == 1 + hook.url = "testurl_more" + hook.save() + + +def test_delete_group_hook(group, resp_hook_delete): + hook = group.hooks.get(1) + hook.delete() + group.hooks.delete(1) + + +def test_list_project_hooks(project, resp_hooks_list): + hooks = project.hooks.list() + assert hooks[0].id == 1 + assert hooks[0].url == "testurl" + assert hooks[1].id == 2 + assert hooks[1].url == "testurl_second" + + +def test_get_project_hook(project, resp_hook_get): + data = project.hooks.get(1) + assert isinstance(data, ProjectHook) + assert data.url == "testurl" + assert data.id == 1 + + +def test_create_project_hook(project, resp_hook_create): + hook = project.hooks.create(hook_content) + assert hook.url == "testurl" + assert hook.push_events is True + assert hook.tag_push_events is True + + +def test_update_project_hook(project, resp_hook_update): + hook = project.hooks.get(1) + assert hook.id == 1 + hook.url = "testurl_more" + hook.save() + + +def test_delete_project_hook(project, resp_hook_delete): + hook = project.hooks.get(1) + hook.delete() + project.hooks.delete(1) diff --git a/tests/unit/objects/test_projects.py b/tests/unit/objects/test_projects.py index 73e119bd3..039d5ec75 100644 --- a/tests/unit/objects/test_projects.py +++ b/tests/unit/objects/test_projects.py @@ -177,31 +177,6 @@ def test_delete_shared_project_link(gl): pass -@pytest.mark.skip(reason="missing test") -def test_list_project_hooks(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_get_project_hook(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_create_project_hook(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_update_project_hook(gl): - pass - - -@pytest.mark.skip(reason="missing test") -def test_delete_project_hook(gl): - pass - - @pytest.mark.skip(reason="missing test") def test_create_forked_from_relationship(gl): pass