-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(ci-issues): add junit upload CLI
Adds the cli `mergify ci-issues junit-upload` to upload JUnit xml reports to CI Issues. Fixes MRGFY-4339
- Loading branch information
1 parent
427ccc6
commit e9af926
Showing
9 changed files
with
299 additions
and
4 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import click | ||
|
||
from mergify_cli import utils | ||
from mergify_cli.ci_issues import junit_upload as junit_upload_mod | ||
|
||
|
||
ci_issues = click.Group( | ||
"ci-issues", | ||
help="Interact with Mergify's CI Issues", | ||
) | ||
|
||
|
||
@ci_issues.command(help="Upload JUnit XML reports") | ||
@click.option( | ||
"--api-url", | ||
"-u", | ||
help="URL of the Mergify API", | ||
required=True, | ||
envvar="MERGIFY_API_SERVER", | ||
default="https://api.mergify.com/v1", | ||
show_default=True, | ||
) | ||
@click.option( | ||
"--token", | ||
"-t", | ||
help="CI Issues Application Key", | ||
required=True, | ||
envvar="MERGIFY_CI_ISSUES_TOKEN", | ||
) | ||
@click.option( | ||
"--repository", | ||
"-r", | ||
help="Repository full name (owner/repo)", | ||
required=True, | ||
envvar="REPOSITORY", | ||
) | ||
@click.option( | ||
"--head-sha", | ||
"-s", | ||
help="Head SHA of the triggered job", | ||
required=True, | ||
envvar="HEAD_SHA", | ||
) | ||
@click.option( | ||
"--job-name", | ||
"-j", | ||
help="Job's name", | ||
required=True, | ||
envvar="JOB_NAME", | ||
) | ||
@click.option( | ||
"--provider", | ||
"-p", | ||
help="CI provider", | ||
default=None, | ||
envvar="CI_PROVIDER", | ||
) | ||
@click.argument( | ||
"files", | ||
nargs=-1, | ||
required=True, | ||
type=click.Path(exists=True, dir_okay=False), | ||
) | ||
@utils.run_with_asyncio | ||
async def junit_upload( # noqa: PLR0913, PLR0917 | ||
api_url: str, | ||
token: str, | ||
repository: str, | ||
head_sha: str, | ||
job_name: str, | ||
provider: str | None, | ||
files: tuple[str, ...], | ||
) -> None: | ||
await junit_upload_mod.upload( | ||
api_url=api_url, | ||
token=token, | ||
repository=repository, | ||
head_sha=head_sha, | ||
job_name=job_name, | ||
provider=provider, | ||
files=files, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from mergify_cli import console | ||
from mergify_cli.ci_issues import utils | ||
|
||
|
||
async def upload( # noqa: PLR0913, PLR0917 | ||
api_url: str, | ||
token: str, | ||
repository: str, | ||
head_sha: str, | ||
job_name: str, | ||
provider: str | None, | ||
files: tuple[str, ...], | ||
) -> None: | ||
files_to_upload = utils.get_files_to_upload(files) | ||
|
||
form_data = { | ||
"head_sha": head_sha, | ||
"name": job_name, | ||
} | ||
if provider is not None: | ||
form_data["provider"] = provider | ||
|
||
async with utils.get_ci_issues_client(api_url, token) as client: | ||
response = await client.post( | ||
f"/repos/{repository}/ci_issues_upload", | ||
data=form_data, | ||
files=files_to_upload, | ||
) | ||
|
||
console.log( | ||
f"[green]:tada: File(s) uploaded (gigid={response.json()['gigid']})[/]", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import pathlib | ||
|
||
import httpx | ||
|
||
from mergify_cli import console | ||
from mergify_cli import utils | ||
|
||
|
||
async def raise_for_status(response: httpx.Response) -> None: | ||
if response.is_error: | ||
await response.aread() | ||
details = response.text or "<empty_response>" | ||
console.log(f"[red]Error details: {details}[/]") | ||
|
||
response.raise_for_status() | ||
|
||
|
||
def get_ci_issues_client( | ||
api_url: str, | ||
token: str, | ||
) -> httpx.AsyncClient: | ||
return utils.get_http_client( | ||
api_url, | ||
headers={ | ||
"Authorization": f"Bearer {token}", | ||
}, | ||
event_hooks={ | ||
"request": [], | ||
"response": [raise_for_status], | ||
}, | ||
) | ||
|
||
|
||
def get_files_to_upload( | ||
files: tuple[str, ...], | ||
) -> list[tuple[str, tuple[str, bytes, str]]]: | ||
files_to_upload = [] | ||
|
||
for file in set(files): | ||
file_path = pathlib.Path(file) | ||
# TODO(leo): stream the files instead of loading them | ||
with file_path.open("rb") as f: | ||
files_to_upload.append( | ||
("files", (file_path.name, f.read(), "application/xml")), | ||
) | ||
|
||
return files_to_upload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<testsuites> | ||
<testsuite name="pytest" errors="0" failures="1" skipped="0" tests="2" time="0.026" | ||
timestamp="2024-08-14T12:25:18.210796+02:00" hostname="mergify-MBP"> | ||
<testcase classname="mergify.tests.test_junit" name="test_success" time="0.000"/> | ||
<testcase classname="mergify.tests.test_junit" name="test_failed" time="0.000"> | ||
<failure message="assert 1 == 0">def test_failed() -> None: | ||
> assert 1 == 0 | ||
E assert 1 == 0 | ||
|
||
mergify/tests/test_junit.py:6: AssertionError | ||
</failure> | ||
</testcase> | ||
</testsuite> | ||
</testsuites> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import pathlib | ||
from unittest import mock | ||
|
||
from click import testing | ||
import httpx | ||
import pytest | ||
import respx | ||
|
||
from mergify_cli.ci_issues import cli as cli_junit_upload | ||
from mergify_cli.ci_issues import junit_upload as junit_upload_mod | ||
from mergify_cli.ci_issues import utils as ci_issues_utils | ||
|
||
|
||
REPORT_XML = pathlib.Path(__file__).parent / "reports" / "report.xml" | ||
|
||
|
||
def test_options_values_from_env(monkeypatch: pytest.MonkeyPatch) -> None: | ||
monkeypatch.setenv("MERGIFY_API_SERVER", "https://api.mergify.com/v2") | ||
monkeypatch.setenv("MERGIFY_CI_ISSUES_TOKEN", "abc") | ||
monkeypatch.setenv("REPOSITORY", "user/repo") | ||
monkeypatch.setenv("HEAD_SHA", "3af96aa24f1d32fcfbb7067793cacc6dc0c6b199") | ||
monkeypatch.setenv("JOB_NAME", "JOB") | ||
monkeypatch.setenv("CI_PROVIDER", "circleci") | ||
|
||
runner = testing.CliRunner() | ||
|
||
with mock.patch.object( | ||
junit_upload_mod, | ||
"upload", | ||
mock.AsyncMock(), | ||
) as mocked_upload: | ||
result = runner.invoke( | ||
cli_junit_upload.junit_upload, | ||
[str(REPORT_XML)], | ||
) | ||
assert result.exit_code == 0 | ||
assert mocked_upload.call_count == 1 | ||
assert mocked_upload.call_args.kwargs == { | ||
"api_url": "https://api.mergify.com/v2", | ||
"token": "abc", | ||
"repository": "user/repo", | ||
"files": (str(REPORT_XML),), | ||
"head_sha": "3af96aa24f1d32fcfbb7067793cacc6dc0c6b199", | ||
"job_name": "JOB", | ||
"provider": "circleci", | ||
} | ||
|
||
|
||
def test_get_files_to_upload() -> None: | ||
files_to_upload = ci_issues_utils.get_files_to_upload( | ||
(str(REPORT_XML),), | ||
) | ||
assert files_to_upload == [ | ||
( | ||
"files", | ||
( | ||
"report.xml", | ||
REPORT_XML.read_bytes(), | ||
"application/xml", | ||
), | ||
), | ||
] | ||
|
||
|
||
async def test_junit_upload(respx_mock: respx.MockRouter) -> None: | ||
respx_mock.post( | ||
"/v1/repos/user/repo/ci_issues_upload", | ||
).respond( | ||
200, | ||
json={"gigid": "1234azertyuiop"}, | ||
) | ||
|
||
await junit_upload_mod.upload( | ||
"https://api.mergify.com/v1", | ||
"token", | ||
"user/repo", | ||
"3af96aa24f1d32fcfbb7067793cacc6dc0c6b199", | ||
"ci-test-job", | ||
"circleci", | ||
(str(REPORT_XML),), | ||
) | ||
|
||
|
||
async def test_junit_upload_http_error(respx_mock: respx.MockRouter) -> None: | ||
respx_mock.post("/v1/repos/user/repo/ci_issues_upload").respond( | ||
422, | ||
json={"detail": "CI Issues is not enabled on this repository"}, | ||
) | ||
|
||
with pytest.raises(httpx.HTTPStatusError): | ||
await junit_upload_mod.upload( | ||
"https://api.mergify.com/v1", | ||
"token", | ||
"user/repo", | ||
"head-sha", | ||
"ci-job", | ||
"circleci", | ||
(str(REPORT_XML),), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters