diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index 479cbd27e..1a921f87f 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -89,7 +89,7 @@ Remove a group:: group.delete() Restore a Group marked for deletion (Premium only)::: - + group.restore() @@ -368,9 +368,9 @@ SAML group links Add a SAML group link to an existing GitLab group:: - saml_link = group.saml_group_links.create({ - "saml_group_name": "", - "access_level": + saml_link = group.saml_group_links.create({ + "saml_group_name": "", + "access_level": }) List a group's SAML group links:: @@ -419,6 +419,10 @@ Update a group hook:: hook.push_events = 0 hook.save() +Test a group hook:: + + hook.test("push_events") + Delete a group hook:: group.hooks.delete(hook_id) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index ba024ce05..5697fd206 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -353,7 +353,7 @@ Import the project using file stored on a remote URL:: output = gl.projects.remote_import( url="https://whatever.com/url/file.tar.gz", path="my_new_remote_project", - name="My New Remote Project", + name="My New Remote Project", namespace="my-group", override_params={'visibility': 'private'}, ) @@ -367,7 +367,7 @@ Import the project using file stored on AWS S3:: file_key="aws-file-key", access_key_id="aws-access-key-id", secret_access_key="secret-access-key", - name="My New Remote Project", + name="My New Remote Project", namespace="my-group", override_params={'visibility': 'private'}, ) @@ -449,7 +449,7 @@ Get file details from headers, without fetching its entire content:: print(headers["X-Gitlab-Size"]) Get a raw file:: - + raw_content = project.files.raw(file_path='README.rst', ref='main') print(raw_content) with open('/tmp/raw-download.txt', 'wb') as f: @@ -689,6 +689,10 @@ Update a project hook:: hook.push_events = 0 hook.save() +Test a project hook:: + + hook.test("push_events") + Delete a project hook:: project.hooks.delete(hook_id) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 1ff67b9c7..35f7dc11c 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -316,6 +316,10 @@ class GitlabDeploymentApprovalError(GitlabOperationError): pass +class GitlabHookTestError(GitlabOperationError): + pass + + # For an explanation of how these type-hints work see: # https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators # @@ -370,6 +374,7 @@ def wrapped_f(*args: Any, **kwargs: Any) -> Any: "GitlabGetError", "GitlabGroupTransferError", "GitlabHeadError", + "GitlabHookTestError", "GitlabHousekeepingError", "GitlabHttpError", "GitlabImportError", diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py index aa0ff0368..798f92e4d 100644 --- a/gitlab/v4/objects/hooks.py +++ b/gitlab/v4/objects/hooks.py @@ -1,5 +1,6 @@ from typing import Any, cast, Union +from gitlab import exceptions as exc from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CRUDMixin, NoUpdateMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -31,6 +32,20 @@ def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Hook: class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "url" + @exc.on_http_error(exc.GitlabHookTestError) + def test(self, trigger: str) -> None: + """ + Test a Project Hook + + Args: + trigger: Type of trigger event to test + + Raises: + GitlabHookTestError: If the hook test attempt failed + """ + path = f"{self.manager.path}/{self.encoded_id}/test/{trigger}" + self.manager.gitlab.http_post(path) + class ProjectHookManager(CRUDMixin, RESTManager): _path = "/projects/{project_id}/hooks" @@ -78,6 +93,20 @@ def get( class GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "url" + @exc.on_http_error(exc.GitlabHookTestError) + def test(self, trigger: str) -> None: + """ + Test a Group Hook + + Args: + trigger: Type of trigger event to test + + Raises: + GitlabHookTestError: If the hook test attempt failed + """ + path = f"{self.manager.path}/{self.encoded_id}/test/{trigger}" + self.manager.gitlab.http_post(path) + class GroupHookManager(CRUDMixin, RESTManager): _path = "/groups/{group_id}/hooks" diff --git a/tests/unit/objects/test_hooks.py b/tests/unit/objects/test_hooks.py index 0f9dbe282..550ea2ccc 100644 --- a/tests/unit/objects/test_hooks.py +++ b/tests/unit/objects/test_hooks.py @@ -9,6 +9,7 @@ import pytest import responses +import gitlab from gitlab.v4.objects import GroupHook, Hook, ProjectHook hooks_content = [ @@ -89,6 +90,58 @@ def resp_hook_update(): yield rsps +@pytest.fixture +def resp_hook_test(): + with responses.RequestsMock() as rsps: + hook_pattern = re.compile( + r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1" + ) + test_pattern = re.compile( + r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1/test/[a-z_]+" + ) + rsps.add( + method=responses.GET, + url=hook_pattern, + json=hook_content, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.POST, + url=test_pattern, + json={"message": "201 Created"}, + content_type="application/json", + status=201, + ) + yield rsps + + +@pytest.fixture +def resp_hook_test_error(): + with responses.RequestsMock() as rsps: + hook_pattern = re.compile( + r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1" + ) + test_pattern = re.compile( + r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1/test/[a-z_]+" + ) + rsps.add( + method=responses.GET, + url=hook_pattern, + json=hook_content, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.POST, + url=test_pattern, + json={"message": "error"}, + content_type="application/json", + status=422, + ) + yield rsps + + @pytest.fixture def resp_hook_delete(): with responses.RequestsMock() as rsps: @@ -174,6 +227,17 @@ def test_delete_group_hook(group, resp_hook_delete): group.hooks.delete(1) +def test_test_group_hook(group, resp_hook_test): + hook = group.hooks.get(1) + hook.test("push_events") + + +def test_test_error_group_hook(group, resp_hook_test_error): + hook = group.hooks.get(1) + with pytest.raises(gitlab.exceptions.GitlabHookTestError): + hook.test("push_events") + + def test_list_project_hooks(project, resp_hooks_list): hooks = project.hooks.list() assert hooks[0].id == 1