Skip to content

Commit

Permalink
fix: use correct upload url for github (#661)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <action@github.com>
  • Loading branch information
zckv and actions-user committed Aug 16, 2023
1 parent 9d3c023 commit 8a515ca
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 30 deletions.
2 changes: 1 addition & 1 deletion semantic_release/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ChangelogConfig(BaseModel):

class BranchConfig(BaseModel):
match: str = "(main|master)"
prerelease_token = "rc" # noqa: S105
prerelease_token: str = "rc" # noqa: S105
prerelease: bool = False


Expand Down
18 changes: 15 additions & 3 deletions semantic_release/hvcs/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Github(HvcsBase):

DEFAULT_DOMAIN = "github.com"
DEFAULT_API_DOMAIN = "api.github.com"
DEFAULT_UPLOAD_DOMAIN = "uploads.github.com"

def __init__(
self,
Expand All @@ -59,6 +60,7 @@ def __init__(
).replace("https://", "")

self.api_url = f"https://{self.hvcs_api_domain}"
self.upload_url = f"https://{self.DEFAULT_UPLOAD_DOMAIN}"

self.token = token
auth = None if not self.token else TokenAuth(self.token)
Expand Down Expand Up @@ -179,15 +181,19 @@ def create_or_update_release(
return self.edit_release_notes(release_id, release_notes)

@logged_function(log)
def asset_upload_url(self, release_id: str) -> str:
@suppress_not_found
def asset_upload_url(self, release_id: str) -> str | None:
"""
Get the correct upload url for a release
https://docs.github.com/en/enterprise-server@3.5/rest/releases/releases#get-a-release
:param release_id: ID of the release to upload to
:return: URL found to upload for a release
:return: URL to upload for a release if found, else None
"""
# https://docs.github.com/en/enterprise-server@3.5/rest/releases/assets#upload-a-release-asset
return f"{self.api_url}/repos/{self.owner}/{self.repo_name}/releases/{release_id}/assets" # noqa: E501
response = self.session.get(
f"{self.api_url}/repos/{self.owner}/{self.repo_name}/releases/{release_id}",
)
return response.json().get("upload_url").replace("{?name,label}", "")

@logged_function(log)
def upload_asset(
Expand All @@ -202,6 +208,12 @@ def upload_asset(
:return: The status of the request
"""
url = self.asset_upload_url(release_id)
if url is None:
raise HTTPError(
"There is no associated url for uploading asset for release "
f"{release_id}. Release url: "
f"{self.api_url}/repos/{self.owner}/{self.repo_name}/releases/{release_id}"
)
content_type = (
mimetypes.guess_type(file, strict=False)[0] or "application/octet-stream"
)
Expand Down
105 changes: 79 additions & 26 deletions tests/unit/semantic_release/hvcs/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,24 +213,14 @@ def test_pull_request_url(default_gh_client, pr_number):
)


def test_asset_upload_url(default_gh_client):
assert default_gh_client.asset_upload_url(
release_id=420
) == "https://{domain}/repos/{owner}/{repo}/releases/{release_id}/assets".format(
domain=default_gh_client.hvcs_api_domain,
owner=default_gh_client.owner,
repo=default_gh_client.repo_name,
release_id=420,
)


############
# Tests which need http response mocking
############


github_matcher = re.compile(rf"^https://{Github.DEFAULT_DOMAIN}")
github_api_matcher = re.compile(rf"^https://{Github.DEFAULT_API_DOMAIN}")
github_upload_matcher = re.compile(rf"^https://{Github.DEFAULT_UPLOAD_DOMAIN}")


@pytest.mark.parametrize("status_code", (200, 201))
Expand Down Expand Up @@ -533,16 +523,65 @@ def test_create_or_update_release_when_create_fails_and_no_release_for_tag(
mock_edit_release_notes.assert_not_called()


def test_asset_upload_url(default_gh_client):
release_id = 1
# '{?name,label}' are added by github.com at least, maybe custom too
# https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release
resp_payload = {
"upload_url": (
f"{default_gh_client.upload_url}/repos/"
f"{default_gh_client.owner}/{default_gh_client.repo_name}/"
f"releases/{release_id}/"
"assets{?name,label}"
),
"status": "success",
}
with requests_mock.Mocker(session=default_gh_client.session) as m:
m.register_uri("GET", github_api_matcher, json=resp_payload, status_code=200)
assert default_gh_client.asset_upload_url(
release_id
) == "https://{domain}/repos/{owner}/{repo}/releases/{release_id}/assets".format(
domain=default_gh_client.DEFAULT_UPLOAD_DOMAIN,
owner=default_gh_client.owner,
repo=default_gh_client.repo_name,
release_id=release_id,
)
assert m.called
assert len(m.request_history) == 1
assert m.last_request.method == "GET"
assert (
m.last_request.url
== "{api_url}/repos/{owner}/{repo_name}/releases/{release_id}".format(
api_url=default_gh_client.api_url,
owner=default_gh_client.owner,
repo_name=default_gh_client.repo_name,
release_id=1,
)
)


@pytest.mark.parametrize("status_code", (200, 201))
@pytest.mark.parametrize("mock_release_id", range(3))
def test_upload_asset_succeeds(
default_gh_client, example_changelog_md, status_code, mock_release_id
):
label = "abc123"
urlparams = {"name": example_changelog_md.name, "label": label}
expected_upload_url = (
f"{default_gh_client.upload_url}/repos/{default_gh_client.owner}/"
f"{default_gh_client.repo_name}/releases/{mock_release_id}/"
r"assets{?name,label}"
)
json_get_up_url = {"status": "ok", "upload_url": expected_upload_url}
with requests_mock.Mocker(session=default_gh_client.session) as m:
m.register_uri(
"POST", github_api_matcher, json={"status": "ok"}, status_code=status_code
"POST",
github_upload_matcher,
json={"status": "ok"},
status_code=status_code,
)
m.register_uri(
"GET", github_api_matcher, json=json_get_up_url, status_code=status_code
)
assert (
default_gh_client.upload_asset(
Expand All @@ -553,22 +592,26 @@ def test_upload_asset_succeeds(
is True
)
assert m.called
assert len(m.request_history) == 1
assert m.last_request.method == "POST"
assert m.last_request.url == "{url}?{params}".format(
url=default_gh_client.asset_upload_url(mock_release_id),
assert len(m.request_history) == 2
get_req, post_req = m.request_history
assert isinstance(get_req, requests_mock.request._RequestObjectProxy)
assert isinstance(post_req, requests_mock.request._RequestObjectProxy)
assert get_req.method == "GET"

assert post_req.method == "POST"
assert post_req.url == "{url}?{params}".format(
url=expected_upload_url.replace(r"{?name,label}", ""),
params=urlencode(urlparams),
)

# Check if content-type header was correctly set according to
# mimetypes - not retesting guessing functionality
assert {
"Content-Type": mimetypes.guess_type(
example_changelog_md.resolve(), strict=False
)[0]
or "application/octet-stream"
}.items() <= m.last_request.headers.items()
assert m.last_request.body == example_changelog_md.read_bytes()
}.items() <= post_req.headers.items()
assert post_req.body == example_changelog_md.read_bytes()


@pytest.mark.parametrize("status_code", (400, 404, 429, 500, 503))
Expand All @@ -578,14 +621,23 @@ def test_upload_asset_fails(
):
label = "abc123"
urlparams = {"name": example_changelog_md.name, "label": label}
json_get_up_url = {
"status": "ok",
"upload_url": "{up_url}/repos/{owner}/{repo_name}/releases/{release_id}".format(
up_url=default_gh_client.upload_url,
owner=default_gh_client.owner,
repo_name=default_gh_client.repo_name,
release_id=mock_release_id,
),
}
with requests_mock.Mocker(session=default_gh_client.session) as m:
m.register_uri(
"POST",
github_api_matcher,
github_upload_matcher,
json={"message": "error"},
status_code=status_code,
)

m.register_uri("GET", github_api_matcher, json=json_get_up_url, status_code=200)
with pytest.raises(HTTPError):
default_gh_client.upload_asset(
release_id=mock_release_id,
Expand All @@ -594,9 +646,10 @@ def test_upload_asset_fails(
)

assert m.called
assert len(m.request_history) == 1
assert m.last_request.method == "POST"
assert m.last_request.url == "{url}?{params}".format(
assert len(m.request_history) == 2
post_req = m.last_request.copy()
assert post_req.method == "POST"
assert post_req.url == "{url}?{params}".format(
url=default_gh_client.asset_upload_url(mock_release_id),
params=urlencode(urlparams),
)
Expand All @@ -608,8 +661,8 @@ def test_upload_asset_fails(
example_changelog_md.resolve(), strict=False
)[0]
or "application/octet-stream"
}.items() <= m.last_request.headers.items()
assert m.last_request.body == example_changelog_md.read_bytes()
}.items() <= post_req.headers.items()
assert post_req.body == example_changelog_md.read_bytes()


# Note - mocking as the logic for uploading an asset
Expand Down

0 comments on commit 8a515ca

Please sign in to comment.