Skip to content

Commit c5d0404

Browse files
Guilherme GalloJohnVillalovos
Guilherme Gallo
authored andcommitted
fix: Consider scope an ArrayAttribute in PipelineJobManager
List query params like 'scope' were not being handled correctly for pipeline/jobs endpoint. This change ensures multiple values are appended with '[]', resulting in the correct URL structure. Signed-off-by: Guilherme Gallo <guilherme.gallo@collabora.com> --- Background: If one queries for pipeline jobs with `scope=["failed", "success"]` One gets: GET /api/v4/projects/176/pipelines/1113028/jobs?scope=success&scope=failed But it is supposed to get: GET /api/v4/projects/176/pipelines/1113028/jobs?scope[]=success&scope[]=failed The current version only considers the last element of the list argument. Signed-off-by: Guilherme Gallo <guilherme.gallo@collabora.com>
1 parent c23e6bd commit c5d0404

File tree

3 files changed

+81
-7
lines changed

3 files changed

+81
-7
lines changed

gitlab/v4/objects/pipelines.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
SaveMixin,
1818
UpdateMixin,
1919
)
20-
from gitlab.types import RequiredOptional
20+
from gitlab.types import ArrayAttribute, RequiredOptional
2121

2222
__all__ = [
2323
"ProjectMergeRequestPipeline",
@@ -149,6 +149,7 @@ class ProjectPipelineJobManager(ListMixin, RESTManager):
149149
_obj_cls = ProjectPipelineJob
150150
_from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"}
151151
_list_filters = ("scope", "include_retried")
152+
_types = {"scope": ArrayAttribute}
152153

153154

154155
class ProjectPipelineBridge(RESTObject):

tests/unit/objects/test_jobs.py

+77-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
GitLab API: https://docs.gitlab.com/ee/api/jobs.html
33
"""
44

5+
from functools import partial
6+
57
import pytest
68
import responses
79

810
from gitlab.v4.objects import ProjectJob
911

10-
job_content = {
12+
failed_job_content = {
1113
"commit": {
1214
"author_email": "admin@example.com",
1315
"author_name": "Administrator",
@@ -37,14 +39,20 @@
3739
"user": {"id": 1},
3840
}
3941

42+
success_job_content = {
43+
**failed_job_content,
44+
"status": "success",
45+
"id": failed_job_content["id"] + 1,
46+
}
47+
4048

4149
@pytest.fixture
4250
def resp_get_job():
4351
with responses.RequestsMock() as rsps:
4452
rsps.add(
4553
method=responses.GET,
4654
url="http://localhost/api/v4/projects/1/jobs/1",
47-
json=job_content,
55+
json=failed_job_content,
4856
content_type="application/json",
4957
status=200,
5058
)
@@ -57,7 +65,7 @@ def resp_cancel_job():
5765
rsps.add(
5866
method=responses.POST,
5967
url="http://localhost/api/v4/projects/1/jobs/1/cancel",
60-
json=job_content,
68+
json=failed_job_content,
6169
content_type="application/json",
6270
status=201,
6371
)
@@ -70,13 +78,53 @@ def resp_retry_job():
7078
rsps.add(
7179
method=responses.POST,
7280
url="http://localhost/api/v4/projects/1/jobs/1/retry",
73-
json=job_content,
81+
json=failed_job_content,
7482
content_type="application/json",
7583
status=201,
7684
)
7785
yield rsps
7886

7987

88+
@pytest.fixture
89+
def resp_list_job():
90+
urls = [
91+
"http://localhost/api/v4/projects/1/jobs",
92+
"http://localhost/api/v4/projects/1/pipelines/1/jobs",
93+
]
94+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
95+
register_endpoint = partial(
96+
rsps.add,
97+
method=responses.GET,
98+
content_type="application/json",
99+
status=200,
100+
)
101+
for url in urls:
102+
register_endpoint(
103+
url=url,
104+
json=[failed_job_content],
105+
match=[responses.matchers.query_param_matcher({"scope[]": "failed"})],
106+
)
107+
register_endpoint(
108+
url=url,
109+
json=[success_job_content],
110+
match=[responses.matchers.query_param_matcher({"scope[]": "success"})],
111+
)
112+
register_endpoint(
113+
url=url,
114+
json=[success_job_content, failed_job_content],
115+
match=[
116+
responses.matchers.query_string_matcher(
117+
"scope[]=success&scope[]failed"
118+
)
119+
],
120+
)
121+
register_endpoint(
122+
url=url,
123+
json=[success_job_content, failed_job_content],
124+
)
125+
yield rsps
126+
127+
80128
def test_get_project_job(project, resp_get_job):
81129
job = project.jobs.get(1)
82130
assert isinstance(job, ProjectJob)
@@ -95,3 +143,28 @@ def test_retry_project_job(project, resp_retry_job):
95143

96144
output = job.retry()
97145
assert output["ref"] == "main"
146+
147+
148+
def test_list_project_job(project, resp_list_job):
149+
failed_jobs = project.jobs.list(scope="failed")
150+
success_jobs = project.jobs.list(scope="success")
151+
failed_and_success_jobs = project.jobs.list(scope=["failed", "success"])
152+
pipeline_lazy = project.pipelines.get(1, lazy=True)
153+
pjobs_failed = pipeline_lazy.jobs.list(scope="failed")
154+
pjobs_success = pipeline_lazy.jobs.list(scope="success")
155+
pjobs_failed_and_success = pipeline_lazy.jobs.list(scope=["failed", "success"])
156+
157+
prepared_urls = [c.request.url for c in resp_list_job.calls]
158+
159+
# Both pipelines and pipelines/jobs should behave the same way
160+
# When `scope` is scalar, one can use scope=value or scope[]=value
161+
assert set(failed_and_success_jobs) == set(failed_jobs + success_jobs)
162+
assert set(pjobs_failed_and_success) == set(pjobs_failed + pjobs_success)
163+
assert prepared_urls == [
164+
"http://localhost/api/v4/projects/1/jobs?scope%5B%5D=failed",
165+
"http://localhost/api/v4/projects/1/jobs?scope%5B%5D=success",
166+
"http://localhost/api/v4/projects/1/jobs?scope%5B%5D=failed&scope%5B%5D=success",
167+
"http://localhost/api/v4/projects/1/pipelines/1/jobs?scope%5B%5D=failed",
168+
"http://localhost/api/v4/projects/1/pipelines/1/jobs?scope%5B%5D=success",
169+
"http://localhost/api/v4/projects/1/pipelines/1/jobs?scope%5B%5D=failed&scope%5B%5D=success",
170+
]

tests/unit/objects/test_resource_groups.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from gitlab.v4.objects import ProjectResourceGroup, ProjectResourceGroupUpcomingJob
1010

11-
from .test_jobs import job_content
11+
from .test_jobs import failed_job_content
1212

1313
resource_group_content = {
1414
"id": 3,
@@ -51,7 +51,7 @@ def resp_list_upcoming_jobs():
5151
rsps.add(
5252
method=responses.GET,
5353
url="http://localhost/api/v4/projects/1/resource_groups/production/upcoming_jobs",
54-
json=[job_content],
54+
json=[failed_job_content],
5555
content_type="application/json",
5656
status=200,
5757
)

0 commit comments

Comments
 (0)