From 1f2da4a3233db67cd6270ca0f6b5983b66b8a7db Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Fri, 9 Aug 2024 12:24:55 -0400 Subject: [PATCH] feat(api): project/group hook test triggering Add the ability to trigger tests of project and group hooks. Fixes #2924 --- docs/gl_objects/groups.rst | 12 ++++-- docs/gl_objects/projects.rst | 10 +++-- gitlab/exceptions.py | 5 +++ gitlab/v4/objects/hooks.py | 29 +++++++++++++++ tests/unit/objects/test_hooks.py | 64 ++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 7 deletions(-) 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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy