Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 6fa33eb

Browse files
authored
feat: Create coverage single upload endpoint (#962)
1 parent f7e2a78 commit 6fa33eb

File tree

8 files changed

+636
-152
lines changed

8 files changed

+636
-152
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
from unittest.mock import patch
2+
3+
from django.conf import settings
4+
from django.test import override_settings
5+
from django.urls import reverse
6+
from rest_framework.test import APIClient
7+
from shared.django_apps.core.tests.factories import (
8+
CommitFactory,
9+
RepositoryFactory,
10+
)
11+
12+
from reports.models import (
13+
ReportSession,
14+
RepositoryFlag,
15+
UploadFlagMembership,
16+
)
17+
from services.archive import ArchiveService, MinioEndpoints
18+
from upload.views.combined_upload import CanDoCoverageUploadsPermission
19+
20+
21+
def test_get_repo(db):
22+
repository = RepositoryFactory(
23+
name="the_repo", author__username="codecov", author__service="github"
24+
)
25+
repository.save()
26+
repo_slug = f"{repository.author.username}::::{repository.name}"
27+
url = reverse(
28+
"new_upload.combined_upload",
29+
args=[repository.author.service, repo_slug],
30+
)
31+
client = APIClient()
32+
client.credentials(HTTP_AUTHORIZATION="token " + repository.upload_token)
33+
response = client.post(url, {}, format="json")
34+
assert response.status_code == 400 # Bad request due to missing required fields
35+
assert "commitid" in response.json()
36+
37+
38+
@patch("services.task.TaskService.upload")
39+
def test_get_repo_not_found(upload, db):
40+
repository = RepositoryFactory(
41+
name="the_repo", author__username="codecov", author__service="github"
42+
)
43+
repo_slug = "codecov::::wrong-repo-name"
44+
url = reverse(
45+
"new_upload.combined_upload",
46+
args=[repository.author.service, repo_slug],
47+
)
48+
client = APIClient()
49+
response = client.post(url, {}, format="json")
50+
assert response.status_code == 401
51+
assert response.json() == {"detail": "Not valid tokenless upload"}
52+
assert not upload.called
53+
54+
55+
def test_deactivated_repo(db):
56+
repository = RepositoryFactory(
57+
name="the_repo",
58+
author__username="codecov",
59+
author__service="github",
60+
active=True,
61+
activated=False,
62+
)
63+
repository.save()
64+
repo_slug = f"{repository.author.username}::::{repository.name}"
65+
url = reverse(
66+
"new_upload.combined_upload",
67+
args=[repository.author.service, repo_slug],
68+
)
69+
client = APIClient()
70+
client.credentials(HTTP_AUTHORIZATION="token " + repository.upload_token)
71+
response = client.post(url, {"commit_sha": "abc123"}, format="json")
72+
assert response.status_code == 400
73+
assert "This repository is deactivated" in str(response.json())
74+
75+
76+
def test_combined_upload_with_errors(db):
77+
repository = RepositoryFactory()
78+
repo_slug = f"{repository.author.username}::::{repository.name}"
79+
url = reverse(
80+
"new_upload.combined_upload",
81+
args=[repository.author.service, repo_slug],
82+
)
83+
84+
client = APIClient()
85+
client.credentials(HTTP_AUTHORIZATION="token " + repository.upload_token)
86+
87+
# Missing required fields
88+
response = client.post(url, {}, format="json")
89+
assert response.status_code == 400
90+
assert "commitid" in response.json()
91+
92+
# Invalid flag format
93+
response = client.post(
94+
url, {"commit_sha": "abc123", "flags": "not-a-list"}, format="json"
95+
)
96+
assert response.status_code == 400
97+
assert "flags" in response.json()
98+
99+
100+
def test_combined_upload_post(db, mocker):
101+
mocker.patch.object(
102+
CanDoCoverageUploadsPermission, "has_permission", return_value=True
103+
)
104+
presigned_put_mock = mocker.patch(
105+
"services.archive.StorageService.create_presigned_put",
106+
return_value="presigned put",
107+
)
108+
upload_task_mock = mocker.patch(
109+
"upload.views.uploads.trigger_upload_task", return_value=True
110+
)
111+
112+
repository = RepositoryFactory(
113+
name="the_repo1", author__username="codecov", author__service="github"
114+
)
115+
commit = CommitFactory(repository=repository)
116+
repository.save()
117+
commit.save()
118+
119+
owner = repository.author
120+
client = APIClient()
121+
client.force_authenticate(user=owner)
122+
repo_slug = f"{repository.author.username}::::{repository.name}"
123+
url = reverse(
124+
"new_upload.combined_upload",
125+
args=[repository.author.service, repo_slug],
126+
)
127+
response = client.post(
128+
url,
129+
{
130+
"branch": "branch",
131+
"ci_service": "ci_service",
132+
"ci_url": "ci_url",
133+
"code": "code",
134+
"commit_sha": commit.commitid,
135+
"flags": ["flag1", "flag2"],
136+
"job_code": "job_code",
137+
"version": "version",
138+
},
139+
format="json",
140+
)
141+
response_json = response.json()
142+
upload = ReportSession.objects.filter(
143+
report__commit=commit,
144+
report__code="code",
145+
upload_extras={"format_version": "v1"},
146+
).first()
147+
assert response.status_code == 201
148+
assert all(
149+
map(
150+
lambda x: x in response_json.keys(),
151+
["external_id", "created_at", "raw_upload_location", "url"],
152+
)
153+
)
154+
assert (
155+
response_json.get("url")
156+
== f"{settings.CODECOV_DASHBOARD_URL}/{repository.author.service}/{repository.author.username}/{repository.name}/commit/{commit.commitid}"
157+
)
158+
159+
assert ReportSession.objects.filter(
160+
report__commit=commit,
161+
report__code="code",
162+
upload_extras={"format_version": "v1"},
163+
).exists()
164+
assert RepositoryFlag.objects.filter(
165+
repository_id=repository.repoid, flag_name="flag1"
166+
).exists()
167+
assert RepositoryFlag.objects.filter(
168+
repository_id=repository.repoid, flag_name="flag2"
169+
).exists()
170+
flag1 = RepositoryFlag.objects.filter(
171+
repository_id=repository.repoid, flag_name="flag1"
172+
).first()
173+
flag2 = RepositoryFlag.objects.filter(
174+
repository_id=repository.repoid, flag_name="flag2"
175+
).first()
176+
assert UploadFlagMembership.objects.filter(
177+
report_session_id=upload.id, flag_id=flag1.id
178+
).exists()
179+
assert UploadFlagMembership.objects.filter(
180+
report_session_id=upload.id, flag_id=flag2.id
181+
).exists()
182+
assert [flag for flag in upload.flags.all()] == [flag1, flag2]
183+
184+
archive_service = ArchiveService(repository)
185+
assert upload.storage_path == MinioEndpoints.raw_with_upload_id.get_path(
186+
version="v4",
187+
date=upload.created_at.strftime("%Y-%m-%d"),
188+
repo_hash=archive_service.storage_hash,
189+
commit_sha=commit.commitid,
190+
reportid=upload.report.external_id,
191+
uploadid=upload.external_id,
192+
)
193+
presigned_put_mock.assert_called_with("archive", upload.storage_path, 10)
194+
upload_task_mock.assert_called()
195+
196+
197+
@override_settings(SHELTER_SHARED_SECRET="shelter-shared-secret")
198+
def test_combined_upload_post_shelter(db, mocker):
199+
mocker.patch.object(
200+
CanDoCoverageUploadsPermission, "has_permission", return_value=True
201+
)
202+
presigned_put_mock = mocker.patch(
203+
"services.archive.StorageService.create_presigned_put",
204+
return_value="presigned put",
205+
)
206+
upload_task_mock = mocker.patch(
207+
"upload.views.uploads.trigger_upload_task", return_value=True
208+
)
209+
210+
repository = RepositoryFactory(
211+
name="the_repo", author__username="codecov", author__service="github"
212+
)
213+
commit = CommitFactory(repository=repository)
214+
repository.save()
215+
commit.save()
216+
217+
owner = repository.author
218+
client = APIClient()
219+
client.force_authenticate(user=owner)
220+
repo_slug = f"{repository.author.username}::::{repository.name}"
221+
url = reverse(
222+
"new_upload.combined_upload",
223+
args=[repository.author.service, repo_slug],
224+
)
225+
response = client.post(
226+
url,
227+
{
228+
"branch": "branch",
229+
"ci_service": "ci_service",
230+
"ci_url": "ci_url",
231+
"code": "code",
232+
"commit_sha": commit.commitid,
233+
"flags": ["flag1", "flag2"],
234+
"job_code": "job_code",
235+
"storage_path": "shelter/test/path.txt",
236+
"version": "version",
237+
},
238+
headers={
239+
"X-Shelter-Token": "shelter-shared-secret",
240+
"User-Agent": "codecov-cli/0.4.7",
241+
},
242+
format="json",
243+
)
244+
response_json = response.json()
245+
upload = ReportSession.objects.filter(
246+
report__commit=commit,
247+
report__code="code",
248+
upload_extras={"format_version": "v1"},
249+
).first()
250+
assert response.status_code == 201
251+
assert all(
252+
map(
253+
lambda x: x in response_json.keys(),
254+
["external_id", "created_at", "raw_upload_location", "url"],
255+
)
256+
)
257+
assert (
258+
response_json.get("url")
259+
== f"{settings.CODECOV_DASHBOARD_URL}/{repository.author.service}/{repository.author.username}/{repository.name}/commit/{commit.commitid}"
260+
)
261+
262+
assert ReportSession.objects.filter(
263+
report__commit=commit,
264+
report__code="code",
265+
upload_extras={"format_version": "v1"},
266+
).exists()
267+
assert RepositoryFlag.objects.filter(
268+
repository_id=repository.repoid, flag_name="flag1"
269+
).exists()
270+
assert RepositoryFlag.objects.filter(
271+
repository_id=repository.repoid, flag_name="flag2"
272+
).exists()
273+
flag1 = RepositoryFlag.objects.filter(
274+
repository_id=repository.repoid, flag_name="flag1"
275+
).first()
276+
flag2 = RepositoryFlag.objects.filter(
277+
repository_id=repository.repoid, flag_name="flag2"
278+
).first()
279+
assert UploadFlagMembership.objects.filter(
280+
report_session_id=upload.id, flag_id=flag1.id
281+
).exists()
282+
assert UploadFlagMembership.objects.filter(
283+
report_session_id=upload.id, flag_id=flag2.id
284+
).exists()
285+
assert [flag for flag in upload.flags.all()] == [flag1, flag2]
286+
287+
assert upload.storage_path == "shelter/test/path.txt"
288+
presigned_put_mock.assert_called_with("archive", upload.storage_path, 10)
289+
upload_task_mock.assert_called()

upload/tests/views/test_uploads.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
)
2424
from reports.tests.factories import CommitReportFactory, UploadFactory
2525
from services.archive import ArchiveService, MinioEndpoints
26-
from upload.views.uploads import CanDoCoverageUploadsPermission, UploadViews
26+
from upload.views.uploads import (
27+
CanDoCoverageUploadsPermission,
28+
UploadViews,
29+
activate_repo,
30+
trigger_upload_task,
31+
)
2732

2833

2934
def test_upload_permission_class_pass(db, mocker):
@@ -197,7 +202,7 @@ def test_uploads_post(db, mocker, mock_redis):
197202
return_value="presigned put",
198203
)
199204
upload_task_mock = mocker.patch(
200-
"upload.views.uploads.UploadViews.trigger_upload_task", return_value=True
205+
"upload.views.uploads.trigger_upload_task", return_value=True
201206
)
202207

203208
repository = RepositoryFactory(
@@ -293,7 +298,7 @@ def test_uploads_post_tokenless(db, mocker, mock_redis, private, branch, branch_
293298
return_value="presigned put",
294299
)
295300
upload_task_mock = mocker.patch(
296-
"upload.views.uploads.UploadViews.trigger_upload_task", return_value=True
301+
"upload.views.uploads.trigger_upload_task", return_value=True
297302
)
298303
analytics_service_mock = mocker.patch("upload.views.uploads.AnalyticsService")
299304

@@ -433,7 +438,7 @@ def test_uploads_post_token_required_auth_check(
433438
return_value="presigned put",
434439
)
435440
upload_task_mock = mocker.patch(
436-
"upload.views.uploads.UploadViews.trigger_upload_task", return_value=True
441+
"upload.views.uploads.trigger_upload_task", return_value=True
437442
)
438443
analytics_service_mock = mocker.patch("upload.views.uploads.AnalyticsService")
439444

@@ -577,7 +582,7 @@ def test_uploads_post_github_oidc_auth(
577582
return_value="presigned put",
578583
)
579584
upload_task_mock = mocker.patch(
580-
"upload.views.uploads.UploadViews.trigger_upload_task", return_value=True
585+
"upload.views.uploads.trigger_upload_task", return_value=True
581586
)
582587

583588
repository = RepositoryFactory(
@@ -696,9 +701,7 @@ def test_uploads_post_shelter(db, mocker, mock_redis):
696701
"services.archive.StorageService.create_presigned_put",
697702
return_value="presigned put",
698703
)
699-
mocker.patch(
700-
"upload.views.uploads.UploadViews.trigger_upload_task", return_value=True
701-
)
704+
mocker.patch("upload.views.uploads.trigger_upload_task", return_value=True)
702705
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
703706

704707
repository = RepositoryFactory(
@@ -798,14 +801,13 @@ def test_deactivated_repo(db, mocker, mock_redis):
798801

799802

800803
def test_trigger_upload_task(db, mocker):
801-
upload_views = UploadViews()
802804
repo = RepositoryFactory.create()
803805
upload = UploadFactory.create()
804806
report = CommitReportFactory.create()
805807
commitid = "commit id"
806808
mocked_redis = mocker.patch("upload.views.uploads.get_redis_connection")
807809
mocked_dispatched_task = mocker.patch("upload.views.uploads.dispatch_upload_task")
808-
upload_views.trigger_upload_task(repo, commitid, upload, report)
810+
trigger_upload_task(repo, commitid, upload, report)
809811
mocked_redis.assert_called()
810812
mocked_dispatched_task.assert_called()
811813

@@ -814,8 +816,7 @@ def test_activate_repo(db):
814816
repo = RepositoryFactory(
815817
active=False, deleted=True, activated=False, coverage_enabled=False
816818
)
817-
upload_views = UploadViews()
818-
upload_views.activate_repo(repo)
819+
activate_repo(repo)
819820
assert repo.active
820821
assert repo.activated
821822
assert not repo.deleted
@@ -826,8 +827,7 @@ def test_activate_already_activated_repo(db):
826827
repo = RepositoryFactory(
827828
active=True, activated=True, deleted=False, coverage_enabled=True
828829
)
829-
upload_views = UploadViews()
830-
upload_views.activate_repo(repo)
830+
activate_repo(repo)
831831
assert repo.active
832832

833833

@@ -855,9 +855,7 @@ def test_uploads_post_github_enterprise_oidc_auth_jwks_url(
855855
"services.archive.StorageService.create_presigned_put",
856856
return_value="presigned put",
857857
)
858-
self.mocker.patch(
859-
"upload.views.uploads.UploadViews.trigger_upload_task", return_value=True
860-
)
858+
self.mocker.patch("upload.views.uploads.trigger_upload_task", return_value=True)
861859

862860
repository = RepositoryFactory(
863861
name="the_repo",

0 commit comments

Comments
 (0)