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)