Skip to content

Commit cd81aea

Browse files
fix: adds missing status check methods for merge requests
1 parent 6eee494 commit cd81aea

File tree

5 files changed

+223
-14
lines changed

5 files changed

+223
-14
lines changed

docs/gl_objects/status_checks.rst

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ Examples
2424

2525
List external status checks for a project::
2626

27-
status_checks = project.external_status_checks.list(get_all=True)
27+
external_status_checks = project.external_status_checks.list()
2828

2929
Create an external status check with shared secret::
3030

31-
status_checks = project.external_status_checks.create({
31+
external_status_checks = project.external_status_checks.create({
3232
"name": "mr_blocker",
3333
"external_url": "https://example.com/mr-status-check",
3434
"shared_secret": "secret-string"
@@ -38,7 +38,7 @@ Create an external status check with shared secret for protected branches::
3838

3939
protected_branch = project.protectedbranches.get('main')
4040

41-
status_check = project.external_status_checks.create({
41+
external_status_check = project.external_status_checks.create({
4242
"name": "mr_blocker",
4343
"external_url": "https://example.com/mr-status-check",
4444
"shared_secret": "secret-string",
@@ -48,10 +48,25 @@ Create an external status check with shared secret for protected branches::
4848

4949
Update an external status check::
5050

51-
status_check.external_url = "https://example.com/mr-blocker"
52-
status_check.save()
51+
external_status_check.external_url = "https://example.com/mr-blocker"
52+
external_status_check.save()
5353

5454
Delete an external status check::
5555

56-
status_check.delete(status_check_id)
56+
external_status_check.delete(externa_status_check_id)
5757

58+
List external status check for a project merge request::
59+
60+
merge_request = project.mergerequests.get(1)
61+
62+
merge_request.external_status_checks.list()
63+
64+
Set external status check for a project merge request::
65+
66+
merge_request = project.mergerequests.get(1)
67+
68+
merge_request.external_status_check_response.update({
69+
"external_status_check_id": external_status_check_id,
70+
"status": "passed",
71+
"sha": merge_request.sha
72+
})

gitlab/v4/objects/merge_requests.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@
4646
from .notes import ProjectMergeRequestNoteManager # noqa: F401
4747
from .pipelines import ProjectMergeRequestPipelineManager # noqa: F401
4848
from .reviewers import ProjectMergeRequestReviewerDetailManager
49-
from .status_checks import ProjectMergeRequestStatusCheckManager
49+
from .status_checks import (
50+
ProjectMergeRequestStatusCheckManager,
51+
ProjectMergeRequestStatusCheckResponseManager,
52+
)
5053

5154
__all__ = [
5255
"MergeRequest",
@@ -170,7 +173,8 @@ class ProjectMergeRequest(
170173
resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager
171174
resourcestateevents: ProjectMergeRequestResourceStateEventManager
172175
reviewer_details: ProjectMergeRequestReviewerDetailManager
173-
status_checks: ProjectMergeRequestStatusCheckManager
176+
external_status_checks: ProjectMergeRequestStatusCheckManager
177+
external_status_check_response: ProjectMergeRequestStatusCheckResponseManager
174178

175179
@cli.register_custom_action(cls_names="ProjectMergeRequest")
176180
@exc.on_http_error(exc.GitlabMROnBuildSuccessError)

gitlab/v4/objects/status_checks.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from gitlab.base import RESTObject
1+
from typing import Any, Dict, Optional
2+
3+
from gitlab.base import RESTManager, RESTObject
24
from gitlab.mixins import (
35
CreateMixin,
46
DeleteMixin,
@@ -27,6 +29,7 @@ class ProjectExternalStatusCheckManager(
2729
CreateMixin[ProjectExternalStatusCheck],
2830
UpdateMixin[ProjectExternalStatusCheck],
2931
DeleteMixin[ProjectExternalStatusCheck],
32+
RESTManager[ProjectExternalStatusCheck],
3033
):
3134
_path = "/projects/{project_id}/external_status_checks"
3235
_obj_cls = ProjectExternalStatusCheck
@@ -41,15 +44,41 @@ class ProjectExternalStatusCheckManager(
4144
_types = {"protected_branch_ids": ArrayAttribute}
4245

4346

44-
class ProjectMergeRequestStatusCheck(SaveMixin, RESTObject):
47+
class ProjectMergeRequestStatusCheckResponse(SaveMixin, RESTObject):
4548
pass
4649

4750

48-
class ProjectMergeRequestStatusCheckManager(ListMixin[ProjectMergeRequestStatusCheck]):
49-
_path = "/projects/{project_id}/merge_requests/{merge_request_iid}/status_checks"
50-
_obj_cls = ProjectMergeRequestStatusCheck
51-
_from_parent_attrs = {"project_id": "project_id", "merge_request_iid": "iid"}
51+
class ProjectMergeRequestStatusCheckResponseManager(
52+
UpdateMixin[ProjectMergeRequestStatusCheckResponse],
53+
RESTManager[ProjectMergeRequestStatusCheckResponse],
54+
):
55+
_path = "/projects/{project_id}/merge_requests/{mr_iid}/status_check_responses"
56+
_obj_cls = ProjectMergeRequestStatusCheckResponse
57+
_from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
5258
_update_attrs = RequiredOptional(
5359
required=("sha", "external_status_check_id", "status")
5460
)
5561
_update_method = UpdateMethod.POST
62+
63+
def update( # type: ignore[override]
64+
self, new_data: Optional[Dict[str, Any]] = None, **kwargs: Any
65+
) -> Dict[str, Any]:
66+
"""Update a Label on the server.
67+
68+
Args:
69+
**kwargs: Extra options to send to the server (e.g. sudo)
70+
"""
71+
return super().update(id=None, new_data=new_data, **kwargs)
72+
73+
74+
class ProjectMergeRequestStatusCheck(RESTObject):
75+
pass
76+
77+
78+
class ProjectMergeRequestStatusCheckManager(
79+
ListMixin[ProjectMergeRequestStatusCheck],
80+
RESTManager[ProjectMergeRequestStatusCheck],
81+
):
82+
_path = "/projects/{project_id}/merge_requests/{mr_iid}/status_checks"
83+
_obj_cls = ProjectMergeRequestStatusCheck
84+
_from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}

tests/functional/api/test_merge_requests.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,46 @@ def test_merge_request_merge_ref_should_fail(project, merge_request) -> None:
300300
with pytest.raises(gitlab.exceptions.GitlabGetError):
301301
response = merge_request.merge_ref()
302302
assert "commit_id" not in response
303+
304+
305+
@pytest.mark.gitlab_premium
306+
def test_merge_request_external_status_check_set_status(project, merge_request):
307+
project.external_status_checks.create(
308+
{
309+
"name": "external_status_check",
310+
"external_url": "https://example.com/mr-blocker",
311+
}
312+
)
313+
314+
mr_external_status_checks = merge_request.external_status_checks.list()
315+
assert len(mr_external_status_checks) == 1
316+
317+
expected_external_status_check = None
318+
319+
for mr_external_status_check in mr_external_status_checks:
320+
if mr_external_status_check.name == "external_status_check":
321+
expected_external_status_check = mr_external_status_check
322+
break
323+
324+
assert expected_external_status_check is not None
325+
326+
# set the external status check value to 'passed'
327+
merge_request.external_status_check_response.update(
328+
{
329+
"external_status_check_id": expected_external_status_check.id,
330+
"status": "passed",
331+
"sha": merge_request.sha,
332+
}
333+
)
334+
335+
time.sleep(2)
336+
337+
# Check the status again to validate the passed status
338+
mr_status_checks = merge_request.external_status_checks.list()
339+
340+
for mr_status_check in mr_status_checks:
341+
if mr_status_check.name == "external_status_check":
342+
expected_status_check = mr_status_check
343+
break
344+
345+
assert expected_status_check.status == "passed"

tests/unit/objects/test_status_checks.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,49 @@
55
import pytest
66
import responses
77

8+
mr_content = {
9+
"id": 1,
10+
"iid": 1,
11+
"project_id": 1,
12+
"title": "test1",
13+
"description": "fixed login page css paddings",
14+
"state": "merged",
15+
"sha": "somerandomstring",
16+
"merged_by": {
17+
"id": 87854,
18+
"name": "Douwe Maan",
19+
"username": "DouweM",
20+
"state": "active",
21+
"avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
22+
"web_url": "https://gitlab.com/DouweM",
23+
},
24+
"reviewers": [
25+
{
26+
"id": 2,
27+
"name": "Sam Bauch",
28+
"username": "kenyatta_oconnell",
29+
"state": "active",
30+
"avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon",
31+
"web_url": "http://gitlab.example.com//kenyatta_oconnell",
32+
}
33+
],
34+
}
35+
36+
external_status_checks_content = [
37+
{
38+
"id": 2,
39+
"name": "Service 2",
40+
"external_url": "https://gitlab.example.com/test-endpoint-2",
41+
"status": "pending",
42+
},
43+
{
44+
"id": 1,
45+
"name": "Service 1",
46+
"external_url": "https://gitlab.example.com/test-endpoint-1",
47+
"status": "pending",
48+
},
49+
]
50+
851

952
@pytest.fixture
1053
def external_status_check():
@@ -104,6 +147,54 @@ def resp_delete_external_status_checks():
104147
content_type="application/json",
105148
status=200,
106149
)
150+
151+
yield rsps
152+
153+
154+
@pytest.fixture
155+
def resp_list_merge_requests_status_checks():
156+
with responses.RequestsMock() as rsps:
157+
rsps.add(
158+
method=responses.GET,
159+
url="http://localhost/api/v4/projects/1/merge_requests/1",
160+
json=mr_content,
161+
content_type="application/json",
162+
status=200,
163+
)
164+
rsps.add(
165+
method=responses.GET,
166+
url="http://localhost/api/v4/projects/1/merge_requests/1/status_checks",
167+
json=external_status_checks_content,
168+
content_type="application/json",
169+
status=200,
170+
)
171+
yield rsps
172+
173+
174+
@pytest.fixture
175+
def resp_list_merge_requests_status_checks_set_value():
176+
with responses.RequestsMock() as rsps:
177+
rsps.add(
178+
method=responses.GET,
179+
url="http://localhost/api/v4/projects/1/merge_requests/1",
180+
json=mr_content,
181+
content_type="application/json",
182+
status=200,
183+
)
184+
rsps.add(
185+
method=responses.GET,
186+
url="http://localhost/api/v4/projects/1/merge_requests/1/status_checks",
187+
json=external_status_checks_content,
188+
content_type="application/json",
189+
status=200,
190+
)
191+
rsps.add(
192+
method=responses.POST,
193+
url="http://localhost/api/v4/projects/1/merge_requests/1/status_check_responses",
194+
json={"status": "passed"},
195+
content_type="application/json",
196+
status=200,
197+
)
107198
yield rsps
108199

109200

@@ -125,3 +216,30 @@ def test_delete_external_status_checks(gl, resp_delete_external_status_checks):
125216
gl.projects.get(1, lazy=True).external_status_checks.delete(1)
126217
status_checks = gl.projects.get(1, lazy=True).external_status_checks.list()
127218
assert len(status_checks) == 0
219+
220+
221+
def test_get_merge_request_external_status_checks(
222+
gl, resp_list_merge_requests_status_checks
223+
):
224+
merge_request = gl.projects.get(1, lazy=True).mergerequests.get(1)
225+
external_status_checks = merge_request.external_status_checks.list()
226+
assert len(external_status_checks) == 2
227+
228+
229+
def test_get_merge_request_external_status_checks_set_value(
230+
gl, resp_list_merge_requests_status_checks_set_value
231+
):
232+
merge_request = gl.projects.get(1, lazy=True).mergerequests.get(1)
233+
external_status_checks = merge_request.external_status_checks.list()
234+
235+
assert len(external_status_checks) == 2
236+
for external_status_check in external_status_checks:
237+
if external_status_check.name == "Service 2":
238+
response = merge_request.external_status_check_response.update(
239+
{
240+
"external_status_check_id": external_status_check.id,
241+
"status": "passed",
242+
"sha": merge_request.sha,
243+
}
244+
)
245+
response["status"] == "passed"

0 commit comments

Comments
 (0)