Skip to content

Commit 6abf13a

Browse files
authored
Merge pull request #1533 from sugonyak/add-group-hooks
feat(api): add group hooks
2 parents 33d3428 + 953f207 commit 6abf13a

File tree

6 files changed

+293
-31
lines changed

6 files changed

+293
-31
lines changed

docs/gl_objects/groups.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,43 @@ You can use the ``ldapgroups`` manager to list available LDAP groups::
338338

339339
# list the groups for a specific LDAP provider
340340
ldap_groups = gl.ldapgroups.list(search='foo', provider='ldapmain')
341+
342+
Groups hooks
343+
============
344+
345+
Reference
346+
---------
347+
348+
* v4 API:
349+
350+
+ :class:`gitlab.v4.objects.GroupHook`
351+
+ :class:`gitlab.v4.objects.GroupHookManager`
352+
+ :attr:`gitlab.v4.objects.Group.hooks`
353+
354+
* GitLab API: https://docs.gitlab.com/ce/api/groups.html#hooks
355+
356+
Examples
357+
--------
358+
359+
List the group hooks::
360+
361+
hooks = group.hooks.list()
362+
363+
Get a group hook::
364+
365+
hook = group.hooks.get(hook_id)
366+
367+
Create a group hook::
368+
369+
hook = group.hooks.create({'url': 'http://my/action/url', 'push_events': 1})
370+
371+
Update a group hook::
372+
373+
hook.push_events = 0
374+
hook.save()
375+
376+
Delete a group hook::
377+
378+
group.hooks.delete(hook_id)
379+
# or
380+
hook.delete()

gitlab/v4/objects/groups.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .deploy_tokens import GroupDeployTokenManager # noqa: F401
1414
from .epics import GroupEpicManager # noqa: F401
1515
from .export_import import GroupExportManager, GroupImportManager # noqa: F401
16+
from .hooks import GroupHookManager # noqa: F401
1617
from .issues import GroupIssueManager # noqa: F401
1718
from .labels import GroupLabelManager # noqa: F401
1819
from .members import ( # noqa: F401
@@ -52,6 +53,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
5253
("descendant_groups", "GroupDescendantGroupManager"),
5354
("exports", "GroupExportManager"),
5455
("epics", "GroupEpicManager"),
56+
("hooks", "GroupHookManager"),
5557
("imports", "GroupImportManager"),
5658
("issues", "GroupIssueManager"),
5759
("issues_statistics", "GroupIssuesStatisticsManager"),

gitlab/v4/objects/hooks.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"HookManager",
77
"ProjectHook",
88
"ProjectHookManager",
9+
"GroupHook",
10+
"GroupHookManager",
911
]
1012

1113

@@ -60,3 +62,53 @@ class ProjectHookManager(CRUDMixin, RESTManager):
6062
"token",
6163
),
6264
)
65+
66+
67+
class GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject):
68+
_short_print_attr = "url"
69+
70+
71+
class GroupHookManager(CRUDMixin, RESTManager):
72+
_path = "/groups/%(group_id)s/hooks"
73+
_obj_cls = GroupHook
74+
_from_parent_attrs = {"group_id": "id"}
75+
_create_attrs = RequiredOptional(
76+
required=("url",),
77+
optional=(
78+
"push_events",
79+
"issues_events",
80+
"confidential_issues_events",
81+
"merge_requests_events",
82+
"tag_push_events",
83+
"note_events",
84+
"confidential_note_events",
85+
"job_events",
86+
"pipeline_events",
87+
"wiki_page_events",
88+
"deployment_events",
89+
"releases_events",
90+
"subgroup_events",
91+
"enable_ssl_verification",
92+
"token",
93+
),
94+
)
95+
_update_attrs = RequiredOptional(
96+
required=("url",),
97+
optional=(
98+
"push_events",
99+
"issues_events",
100+
"confidential_issues_events",
101+
"merge_requests_events",
102+
"tag_push_events",
103+
"note_events",
104+
"confidential_note_events",
105+
"job_events",
106+
"pipeline_events",
107+
"wiki_page_events",
108+
"deployment_events",
109+
"releases_events",
110+
"subgroup_events",
111+
"enable_ssl_verification",
112+
"token",
113+
),
114+
)

tests/functional/api/test_groups.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,16 @@ def test_group_wiki(group):
209209
wiki.save()
210210
wiki.delete()
211211
assert len(group.wikis.list()) == 0
212+
213+
214+
@pytest.mark.skip(reason="EE feature")
215+
def test_group_hooks(group):
216+
hook = group.hooks.create({"url": "http://hook.url"})
217+
assert len(group.hooks.list()) == 1
218+
219+
hook.note_events = True
220+
hook.save()
221+
222+
hook = group.hooks.get(hook.id)
223+
assert hook.note_events is True
224+
hook.delete()

tests/unit/objects/test_hooks.py

Lines changed: 186 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,209 @@
11
"""
22
GitLab API: https://docs.gitlab.com/ce/api/system_hooks.html
3+
GitLab API: https://docs.gitlab.com/ce/api/groups.html#hooks
4+
GitLab API: https://docs.gitlab.com/ee/api/projects.html#hooks
35
"""
6+
7+
import re
8+
49
import pytest
510
import responses
611

7-
from gitlab.v4.objects import Hook
12+
from gitlab.v4.objects import GroupHook, Hook, ProjectHook
13+
14+
hooks_content = [
15+
{
16+
"id": 1,
17+
"url": "testurl",
18+
"push_events": True,
19+
"tag_push_events": True,
20+
},
21+
{
22+
"id": 2,
23+
"url": "testurl_second",
24+
"push_events": False,
25+
"tag_push_events": False,
26+
},
27+
]
28+
29+
hook_content = hooks_content[0]
30+
31+
32+
@pytest.fixture
33+
def resp_hooks_list():
34+
with responses.RequestsMock() as rsps:
35+
rsps.add(
36+
method=responses.GET,
37+
url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks"),
38+
json=hooks_content,
39+
content_type="application/json",
40+
status=200,
41+
)
42+
yield rsps
43+
44+
45+
@pytest.fixture
46+
def resp_hook_get():
47+
with responses.RequestsMock() as rsps:
48+
rsps.add(
49+
method=responses.GET,
50+
url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1"),
51+
json=hook_content,
52+
content_type="application/json",
53+
status=200,
54+
)
55+
yield rsps
56+
57+
58+
@pytest.fixture
59+
def resp_hook_create():
60+
with responses.RequestsMock() as rsps:
61+
rsps.add(
62+
method=responses.POST,
63+
url=re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks"),
64+
json=hook_content,
65+
content_type="application/json",
66+
status=200,
67+
)
68+
yield rsps
869

970

1071
@pytest.fixture
11-
def resp_get_hook():
12-
content = {"url": "testurl", "id": 1}
72+
def resp_hook_update():
73+
with responses.RequestsMock() as rsps:
74+
pattern = re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1")
75+
rsps.add(
76+
method=responses.GET,
77+
url=pattern,
78+
json=hook_content,
79+
content_type="application/json",
80+
status=200,
81+
)
82+
rsps.add(
83+
method=responses.PUT,
84+
url=pattern,
85+
json=hook_content,
86+
content_type="application/json",
87+
status=200,
88+
)
89+
yield rsps
90+
1391

92+
@pytest.fixture
93+
def resp_hook_delete():
1494
with responses.RequestsMock() as rsps:
95+
pattern = re.compile(r"http://localhost/api/v4/((groups|projects)/1/|)hooks/1")
1596
rsps.add(
1697
method=responses.GET,
17-
url="http://localhost/api/v4/hooks/1",
18-
json=content,
98+
url=pattern,
99+
json=hook_content,
19100
content_type="application/json",
20101
status=200,
21102
)
103+
rsps.add(
104+
method=responses.DELETE,
105+
url=pattern,
106+
status=204,
107+
)
22108
yield rsps
23109

24110

25-
def test_hooks(gl, resp_get_hook):
111+
def test_list_system_hooks(gl, resp_hooks_list):
112+
hooks = gl.hooks.list()
113+
assert hooks[0].id == 1
114+
assert hooks[0].url == "testurl"
115+
assert hooks[1].id == 2
116+
assert hooks[1].url == "testurl_second"
117+
118+
119+
def test_get_system_hook(gl, resp_hook_get):
26120
data = gl.hooks.get(1)
27121
assert isinstance(data, Hook)
28122
assert data.url == "testurl"
29123
assert data.id == 1
124+
125+
126+
def test_create_system_hook(gl, resp_hook_create):
127+
hook = gl.hooks.create(hook_content)
128+
assert hook.url == "testurl"
129+
assert hook.push_events is True
130+
assert hook.tag_push_events is True
131+
132+
133+
# there is no update method for system hooks
134+
135+
136+
def test_delete_system_hook(gl, resp_hook_delete):
137+
hook = gl.hooks.get(1)
138+
hook.delete()
139+
gl.hooks.delete(1)
140+
141+
142+
def test_list_group_hooks(group, resp_hooks_list):
143+
hooks = group.hooks.list()
144+
assert hooks[0].id == 1
145+
assert hooks[0].url == "testurl"
146+
assert hooks[1].id == 2
147+
assert hooks[1].url == "testurl_second"
148+
149+
150+
def test_get_group_hook(group, resp_hook_get):
151+
data = group.hooks.get(1)
152+
assert isinstance(data, GroupHook)
153+
assert data.url == "testurl"
154+
assert data.id == 1
155+
156+
157+
def test_create_group_hook(group, resp_hook_create):
158+
hook = group.hooks.create(hook_content)
159+
assert hook.url == "testurl"
160+
assert hook.push_events is True
161+
assert hook.tag_push_events is True
162+
163+
164+
def test_update_group_hook(group, resp_hook_update):
165+
hook = group.hooks.get(1)
166+
assert hook.id == 1
167+
hook.url = "testurl_more"
168+
hook.save()
169+
170+
171+
def test_delete_group_hook(group, resp_hook_delete):
172+
hook = group.hooks.get(1)
173+
hook.delete()
174+
group.hooks.delete(1)
175+
176+
177+
def test_list_project_hooks(project, resp_hooks_list):
178+
hooks = project.hooks.list()
179+
assert hooks[0].id == 1
180+
assert hooks[0].url == "testurl"
181+
assert hooks[1].id == 2
182+
assert hooks[1].url == "testurl_second"
183+
184+
185+
def test_get_project_hook(project, resp_hook_get):
186+
data = project.hooks.get(1)
187+
assert isinstance(data, ProjectHook)
188+
assert data.url == "testurl"
189+
assert data.id == 1
190+
191+
192+
def test_create_project_hook(project, resp_hook_create):
193+
hook = project.hooks.create(hook_content)
194+
assert hook.url == "testurl"
195+
assert hook.push_events is True
196+
assert hook.tag_push_events is True
197+
198+
199+
def test_update_project_hook(project, resp_hook_update):
200+
hook = project.hooks.get(1)
201+
assert hook.id == 1
202+
hook.url = "testurl_more"
203+
hook.save()
204+
205+
206+
def test_delete_project_hook(project, resp_hook_delete):
207+
hook = project.hooks.get(1)
208+
hook.delete()
209+
project.hooks.delete(1)

tests/unit/objects/test_projects.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -177,31 +177,6 @@ def test_delete_shared_project_link(gl):
177177
pass
178178

179179

180-
@pytest.mark.skip(reason="missing test")
181-
def test_list_project_hooks(gl):
182-
pass
183-
184-
185-
@pytest.mark.skip(reason="missing test")
186-
def test_get_project_hook(gl):
187-
pass
188-
189-
190-
@pytest.mark.skip(reason="missing test")
191-
def test_create_project_hook(gl):
192-
pass
193-
194-
195-
@pytest.mark.skip(reason="missing test")
196-
def test_update_project_hook(gl):
197-
pass
198-
199-
200-
@pytest.mark.skip(reason="missing test")
201-
def test_delete_project_hook(gl):
202-
pass
203-
204-
205180
@pytest.mark.skip(reason="missing test")
206181
def test_create_forked_from_relationship(gl):
207182
pass

0 commit comments

Comments
 (0)
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