Skip to content

Commit 0ae5f0d

Browse files
feat: add X-Tokenless-PR header (#342)
* feat: add X-Tokenless-PR header If you look at codecov/codecov-api#304 you'll notice that it expects 2 headers - `X-Tokenless` and `X-Tokenless-PR`. We were not yet sending `X-Tokenless-PR` (because I only realized we'd need it after doing the API side of things). These changes add that header to tokenless requests. * improve coverage * fix: exception if PR is from same repo If a PR exists and base and head are from the same repo it seems that the response["head"]["repo"] is set to None, causing exception when the code runs. This fixes that bug so that the CLI can fail gracefully (if a token is not provided)
1 parent e905e58 commit 0ae5f0d

8 files changed

Lines changed: 96 additions & 6 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,5 @@ cython_debug/
163163
# Vim temporary files
164164
*.swp
165165
*.swo
166+
167+
.debug

codecov_cli/helpers/git_services/github.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ def get_pull_request(self, slug, pr_number) -> PullDict:
2222
"sha": res["head"]["sha"],
2323
"label": res["head"]["label"],
2424
"ref": res["head"]["ref"],
25-
"slug": res["head"]["repo"]["full_name"],
25+
# Through empiric test data it seems that the "repo" key in "head" is set to None
26+
# If the PR is from the same repo (e.g. not from a fork)
27+
"slug": res["head"]["repo"]["full_name"]
28+
if res["head"]["repo"]
29+
else res["base"]["repo"]["full_name"],
2630
},
2731
"base": {
2832
"sha": res["base"]["sha"],

codecov_cli/services/commit/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from codecov_cli.helpers.config import CODECOV_API_URL
55
from codecov_cli.helpers.encoder import decode_slug, encode_slug
6-
from codecov_cli.helpers.git import get_git_service, get_pull, is_fork_pr
6+
from codecov_cli.helpers.git import get_pull, is_fork_pr
77
from codecov_cli.helpers.request import (
88
get_token_header_or_fail,
99
log_warnings_and_errors_if_any,
@@ -46,7 +46,7 @@ def send_commit_data(
4646
decoded_slug = decode_slug(slug)
4747
pull_dict = get_pull(service, decoded_slug, pr) if not token else None
4848
if is_fork_pr(pull_dict):
49-
headers = {"X-Tokenless": pull_dict["head"]["slug"]}
49+
headers = {"X-Tokenless": pull_dict["head"]["slug"], "X-Tokenless-PR": pr}
5050
branch = pull_dict["head"]["slug"] + ":" + branch
5151
logger.info("The PR is happening in a forked repo. Using tokenless upload.")
5252
else:

codecov_cli/services/report/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ def send_create_report_request(
5252
get_pull(service, decoded_slug, pull_request_number) if not token else None
5353
)
5454
if is_fork_pr(pull_dict):
55-
headers = {"X-Tokenless": pull_dict["head"]["slug"]}
55+
headers = {
56+
"X-Tokenless": pull_dict["head"]["slug"],
57+
"X-Tokenless-PR": pull_request_number,
58+
}
5659
else:
5760
headers = get_token_header_or_fail(token)
5861
upload_url = enterprise_url or CODECOV_API_URL

codecov_cli/services/upload/upload_sender.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ def send_upload_data(
5858
)
5959

6060
if is_fork_pr(pull_dict):
61-
headers = {"X-Tokenless": pull_dict["head"]["slug"]}
61+
headers = {
62+
"X-Tokenless": pull_dict["head"]["slug"],
63+
"X-Tokenless-PR": pull_request_number,
64+
}
6265
else:
6366
headers = get_token_header_or_fail(token)
6467
encoded_slug = encode_slug(slug)

tests/helpers/test_upload_sender.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,47 @@ def test_upload_sender_post_called_with_right_parameters(
159159
post_req_made.headers.items() >= headers.items()
160160
) # test dict is a subset of the other
161161

162+
def test_upload_sender_post_called_with_right_parameters_tokenless(
163+
self,
164+
mocked_responses,
165+
mocked_legacy_upload_endpoint,
166+
mocked_storage_server,
167+
mocker,
168+
):
169+
headers = {"X-Tokenless": "user-forked/repo", "X-Tokenless-PR": "pr"}
170+
mock_get_pull = mocker.patch(
171+
"codecov_cli.services.upload.upload_sender.get_pull",
172+
return_value={
173+
"head": {"slug": "user-forked/repo"},
174+
"base": {"slug": "org/repo"},
175+
},
176+
)
177+
mocked_legacy_upload_endpoint.match = [
178+
matchers.json_params_matcher(request_data),
179+
matchers.header_matcher(headers),
180+
]
181+
182+
sending_result = UploadSender().send_upload_data(
183+
upload_collection, random_sha, None, **named_upload_data
184+
)
185+
assert sending_result.error is None
186+
assert sending_result.warnings == []
187+
188+
assert len(mocked_responses.calls) == 2
189+
190+
post_req_made = mocked_responses.calls[0].request
191+
encoded_slug = encode_slug(named_upload_data["slug"])
192+
response = json.loads(mocked_responses.calls[0].response.text)
193+
assert response.get("url") == "https://app.codecov.io/commit-url"
194+
assert (
195+
post_req_made.url
196+
== f"https://api.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads"
197+
)
198+
assert (
199+
post_req_made.headers.items() >= headers.items()
200+
) # test dict is a subset of the other
201+
mock_get_pull.assert_called()
202+
162203
def test_upload_sender_put_called_with_right_parameters(
163204
self, mocked_responses, mocked_legacy_upload_endpoint, mocked_storage_server
164205
):

tests/services/commit/test_commit_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,5 +195,5 @@ def mock_request(*args, headers={}, **kwargs):
195195
"pullid": "1",
196196
"branch": "user_forked_repo/codecov-cli:branch",
197197
},
198-
headers={"X-Tokenless": "user_forked_repo/codecov-cli"},
198+
headers={"X-Tokenless": "user_forked_repo/codecov-cli", "X-Tokenless-PR": "1"},
199199
)

tests/services/report/test_report_service.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,43 @@ def test_send_create_report_request_200(mocker):
2626
mocked_response.assert_called_once()
2727

2828

29+
def test_send_create_report_request_200_tokneless(mocker):
30+
mocked_response = mocker.patch(
31+
"codecov_cli.services.report.send_post_request",
32+
return_value=RequestResult(
33+
status_code=200,
34+
error=None,
35+
warnings=[],
36+
text="mocked response",
37+
),
38+
)
39+
40+
mocked_get_pull = mocker.patch(
41+
"codecov_cli.services.report.get_pull",
42+
return_value={
43+
"head": {"slug": "user-forked/repo"},
44+
"base": {"slug": "org/repo"},
45+
},
46+
)
47+
res = send_create_report_request(
48+
"commit_sha",
49+
"code",
50+
"github",
51+
None,
52+
"owner::::repo",
53+
"enterprise_url",
54+
1,
55+
)
56+
assert res.error is None
57+
assert res.warnings == []
58+
mocked_response.assert_called_with(
59+
url=f"enterprise_url/upload/github/owner::::repo/commits/commit_sha/reports",
60+
headers={"X-Tokenless": "user-forked/repo", "X-Tokenless-PR": 1},
61+
data={"code": "code"},
62+
)
63+
mocked_get_pull.assert_called()
64+
65+
2966
def test_send_create_report_request_403(mocker):
3067
mocked_response = mocker.patch(
3168
"codecov_cli.services.report.requests.post",

0 commit comments

Comments
 (0)