Skip to content

Commit f099108

Browse files
committed
feat(api): project/group hook test triggering
Add the ability to trigger tests of project and group hooks. Fixes python-gitlab#2924
1 parent 8d74b88 commit f099108

File tree

5 files changed

+113
-7
lines changed

5 files changed

+113
-7
lines changed

docs/gl_objects/groups.rst

+8-4
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Remove a group::
8989
group.delete()
9090

9191
Restore a Group marked for deletion (Premium only):::
92-
92+
9393
group.restore()
9494

9595

@@ -368,9 +368,9 @@ SAML group links
368368

369369
Add a SAML group link to an existing GitLab group::
370370

371-
saml_link = group.saml_group_links.create({
372-
"saml_group_name": "<your_saml_group_name>",
373-
"access_level": <chosen_access_level>
371+
saml_link = group.saml_group_links.create({
372+
"saml_group_name": "<your_saml_group_name>",
373+
"access_level": <chosen_access_level>
374374
})
375375

376376
List a group's SAML group links::
@@ -419,6 +419,10 @@ Update a group hook::
419419
hook.push_events = 0
420420
hook.save()
421421

422+
Test a group hook::
423+
424+
hook.test("push_events")
425+
422426
Delete a group hook::
423427

424428
group.hooks.delete(hook_id)

docs/gl_objects/projects.rst

+7-3
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ Import the project using file stored on a remote URL::
353353
output = gl.projects.remote_import(
354354
url="https://whatever.com/url/file.tar.gz",
355355
path="my_new_remote_project",
356-
name="My New Remote Project",
356+
name="My New Remote Project",
357357
namespace="my-group",
358358
override_params={'visibility': 'private'},
359359
)
@@ -367,7 +367,7 @@ Import the project using file stored on AWS S3::
367367
file_key="aws-file-key",
368368
access_key_id="aws-access-key-id",
369369
secret_access_key="secret-access-key",
370-
name="My New Remote Project",
370+
name="My New Remote Project",
371371
namespace="my-group",
372372
override_params={'visibility': 'private'},
373373
)
@@ -449,7 +449,7 @@ Get file details from headers, without fetching its entire content::
449449
print(headers["X-Gitlab-Size"])
450450

451451
Get a raw file::
452-
452+
453453
raw_content = project.files.raw(file_path='README.rst', ref='main')
454454
print(raw_content)
455455
with open('/tmp/raw-download.txt', 'wb') as f:
@@ -689,6 +689,10 @@ Update a project hook::
689689
hook.push_events = 0
690690
hook.save()
691691

692+
Test a project hook::
693+
694+
hook.test("push_events")
695+
692696
Delete a project hook::
693697

694698
project.hooks.delete(hook_id)

gitlab/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ class GitlabDeploymentApprovalError(GitlabOperationError):
316316
pass
317317

318318

319+
class GitlabHookTestError(GitlabOperationError):
320+
pass
321+
322+
319323
# For an explanation of how these type-hints work see:
320324
# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
321325
#
@@ -370,6 +374,7 @@ def wrapped_f(*args: Any, **kwargs: Any) -> Any:
370374
"GitlabGetError",
371375
"GitlabGroupTransferError",
372376
"GitlabHeadError",
377+
"GitlabHookTestError",
373378
"GitlabHousekeepingError",
374379
"GitlabHttpError",
375380
"GitlabImportError",

gitlab/v4/objects/hooks.py

+29
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from gitlab.base import RESTManager, RESTObject
44
from gitlab.mixins import CRUDMixin, NoUpdateMixin, ObjectDeleteMixin, SaveMixin
55
from gitlab.types import RequiredOptional
6+
from gitlab import exceptions as exc
67

78
__all__ = [
89
"Hook",
@@ -31,6 +32,20 @@ def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Hook:
3132
class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject):
3233
_repr_attr = "url"
3334

35+
@exc.on_http_error(exc.GitlabHookTestError)
36+
def test(self, trigger: str) -> None:
37+
"""
38+
Test a Project Hook
39+
40+
Args:
41+
trigger: Type of trigger event to test
42+
43+
Raises:
44+
GitlabHookTestError: If the hook test attempt failed
45+
"""
46+
path = f"{self.manager.path}/{self.encoded_id}/test/{trigger}"
47+
self.manager.gitlab.http_post(path)
48+
3449

3550
class ProjectHookManager(CRUDMixin, RESTManager):
3651
_path = "/projects/{project_id}/hooks"
@@ -78,6 +93,20 @@ def get(
7893
class GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject):
7994
_repr_attr = "url"
8095

96+
@exc.on_http_error(exc.GitlabHookTestError)
97+
def test(self, trigger: str) -> None:
98+
"""
99+
Test a Group Hook
100+
101+
Args:
102+
trigger: Type of trigger event to test
103+
104+
Raises:
105+
GitlabHookTestError: If the hook test attempt failed
106+
"""
107+
path = f"{self.manager.path}/{self.encoded_id}/test/{trigger}"
108+
self.manager.gitlab.http_post(path)
109+
81110

82111
class GroupHookManager(CRUDMixin, RESTManager):
83112
_path = "/groups/{group_id}/hooks"

tests/unit/objects/test_hooks.py

+64
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
import responses
1111

12+
import gitlab
1213
from gitlab.v4.objects import GroupHook, Hook, ProjectHook
1314

1415
hooks_content = [
@@ -89,6 +90,58 @@ def resp_hook_update():
8990
yield rsps
9091

9192

93+
@pytest.fixture
94+
def resp_hook_test():
95+
with responses.RequestsMock() as rsps:
96+
hook_pattern = re.compile(
97+
r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1"
98+
)
99+
test_pattern = re.compile(
100+
r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1/test/[a-z_]+"
101+
)
102+
rsps.add(
103+
method=responses.GET,
104+
url=hook_pattern,
105+
json=hook_content,
106+
content_type="application/json",
107+
status=200,
108+
)
109+
rsps.add(
110+
method=responses.POST,
111+
url=test_pattern,
112+
json={"message": "201 Created"},
113+
content_type="application/json",
114+
status=201,
115+
)
116+
yield rsps
117+
118+
119+
@pytest.fixture
120+
def resp_hook_test_error():
121+
with responses.RequestsMock() as rsps:
122+
hook_pattern = re.compile(
123+
r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1"
124+
)
125+
test_pattern = re.compile(
126+
r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1/test/[a-z_]+"
127+
)
128+
rsps.add(
129+
method=responses.GET,
130+
url=hook_pattern,
131+
json=hook_content,
132+
content_type="application/json",
133+
status=200,
134+
)
135+
rsps.add(
136+
method=responses.POST,
137+
url=test_pattern,
138+
json={"message": "<html>error</html>"},
139+
content_type="application/json",
140+
status=422,
141+
)
142+
yield rsps
143+
144+
92145
@pytest.fixture
93146
def resp_hook_delete():
94147
with responses.RequestsMock() as rsps:
@@ -174,6 +227,17 @@ def test_delete_group_hook(group, resp_hook_delete):
174227
group.hooks.delete(1)
175228

176229

230+
def test_test_group_hook(group, resp_hook_test):
231+
hook = group.hooks.get(1)
232+
hook.test("push_events")
233+
234+
235+
def test_test_error_group_hook(group, resp_hook_test_error):
236+
hook = group.hooks.get(1)
237+
with pytest.raises(gitlab.exceptions.GitlabHookTestError):
238+
hook.test("push_events")
239+
240+
177241
def test_list_project_hooks(project, resp_hooks_list):
178242
hooks = project.hooks.list()
179243
assert hooks[0].id == 1

0 commit comments

Comments
 (0)