Skip to content

Commit d0abf43

Browse files
fix: adds missing status check methods for merge requests
1 parent f62dda7 commit d0abf43

File tree

5 files changed

+225
-14
lines changed

5 files changed

+225
-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()
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,55 @@ 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_list(project, merge_request):
307+
308+
time.sleep(2)
309+
310+
mr_status_checks = merge_request.status_checks.list()
311+
assert len(mr_status_checks) == 1
312+
313+
314+
@pytest.mark.gitlab_premium
315+
def test_merge_request_external_status_check_set_status(project, merge_request):
316+
project.external_status_checks.create(
317+
{
318+
"name": "external_status_check",
319+
"external_url": "https://example.com/mr-blocker",
320+
}
321+
)
322+
323+
mr_external_status_checks = merge_request.external_status_checks.list()
324+
assert len(mr_external_status_checks) == 1
325+
326+
expected_external_status_check = None
327+
328+
for mr_external_status_check in mr_external_status_checks:
329+
if mr_external_status_check.name == "external_status_check":
330+
expected_external_status_check = mr_external_status_check
331+
break
332+
333+
assert expected_external_status_check is not None
334+
335+
# set the external status check value to 'passed'
336+
expected_external_status_check.external_status_check_response.update(
337+
{
338+
"external_status_check_id": expected_external_status_check.id,
339+
"status": "passed",
340+
"sha": merge_request.sha,
341+
}
342+
)
343+
344+
time.sleep(2)
345+
346+
# Check the status again to validate the passed status
347+
mr_status_checks = merge_request.external_status_checks.list()
348+
349+
for mr_status_check in mr_status_checks:
350+
if mr_status_check.name == "external_status_check":
351+
expected_status_check = mr_status_check
352+
break
353+
354+
assert expected_status_check.status == "passed"

tests/unit/objects/test_status_checks.py

Lines changed: 111 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,48 @@ def resp_delete_external_status_checks():
104147
content_type="application/json",
105148
status=200,
106149
)
150+
151+
yield rsps
152+
153+
154+
def before_passing_exeternal_status_check_matcher(request):
155+
data = request.json().get("data", {})
156+
if "a" in data.keys():
157+
return True
158+
return None
159+
160+
161+
def after_passing_exeternal_status_check_matcher(request):
162+
data = request.json().get("data", {})
163+
if "b" in data.keys():
164+
return True
165+
return None
166+
167+
168+
@pytest.fixture
169+
def resp_list_merge_requests_status_checks():
170+
with responses.RequestsMock() as rsps:
171+
rsps.add(
172+
method=responses.GET,
173+
url="http://localhost/api/v4/projects/1/merge_requests/1",
174+
json=mr_content,
175+
content_type="application/json",
176+
status=200,
177+
)
178+
rsps.add(
179+
method=responses.GET,
180+
url="http://localhost/api/v4/projects/1/merge_requests/1/status_checks",
181+
json=external_status_checks_content,
182+
content_type="application/json",
183+
status=200,
184+
)
185+
rsps.add(
186+
method=responses.POST,
187+
url="http://localhost/api/v4/projects/1/merge_requests/1/status_check_responses",
188+
json={"status": "passed"},
189+
content_type="application/json",
190+
status=200,
191+
)
107192
yield rsps
108193

109194

@@ -125,3 +210,29 @@ def test_delete_external_status_checks(gl, resp_delete_external_status_checks):
125210
gl.projects.get(1, lazy=True).external_status_checks.delete(1)
126211
status_checks = gl.projects.get(1, lazy=True).external_status_checks.list()
127212
assert len(status_checks) == 0
213+
214+
215+
def test_get_merge_request_external_status_checks(
216+
gl, resp_list_merge_requests_status_checks
217+
):
218+
merge_request = gl.projects.get(1, lazy=True).mergerequests.get(1)
219+
external_status_checks = merge_request.external_status_checks.list()
220+
assert len(external_status_checks) == 2
221+
222+
223+
def test_get_merge_request_status_checks_set_value(
224+
gl, resp_list_merge_requests_status_checks
225+
):
226+
merge_request = gl.projects.get(1, lazy=True).mergerequests.get(1)
227+
external_status_checks = merge_request.status_checks.list()
228+
229+
for external_status_check in external_status_checks:
230+
if external_status_check == "Service 2":
231+
response = external_status_check.external_status_check_response.update(
232+
{
233+
"external_status_check_id": external_status_check.id,
234+
"status": "passed",
235+
"sha": merge_request.sha,
236+
}
237+
)
238+
response.status == "passed"

0 commit comments

Comments
 (0)