Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai> Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai> Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai>
247 lines
8.1 KiB
Python
247 lines
8.1 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from vibe.cli.update_notifier import (
|
|
GitHubVersionUpdateGateway,
|
|
VersionUpdateGatewayCause,
|
|
VersionUpdateGatewayError,
|
|
)
|
|
|
|
Handler = Callable[[httpx.Request], httpx.Response]
|
|
|
|
GITHUB_API_URL = "https://api.github.com"
|
|
|
|
|
|
def _raise_connect_timeout(request: httpx.Request) -> httpx.Response:
|
|
raise httpx.ConnectTimeout("boom", request=request)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_retrieves_latest_version_when_available() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
assert request.headers.get("Authorization") == "Bearer token"
|
|
return httpx.Response(
|
|
status_code=httpx.codes.OK,
|
|
json=[{"tag_name": "v1.2.3", "prerelease": False, "draft": False}],
|
|
)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway(
|
|
"owner", "repo", token="token", client=client
|
|
)
|
|
update = await notifier.fetch_update()
|
|
|
|
assert update is not None
|
|
assert update.latest_version == "1.2.3"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_strips_uppercase_prefix_from_tag_name() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
status_code=httpx.codes.OK,
|
|
json=[{"tag_name": "V0.9.0", "prerelease": False, "draft": False}],
|
|
)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway("owner", "repo", client=client)
|
|
update = await notifier.fetch_update()
|
|
|
|
assert update is not None
|
|
assert update.latest_version == "0.9.0"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_considers_no_update_available_when_no_releases_are_found() -> None:
|
|
"""If the repository cannot be accessed (e.g. invalid token), the response will be 404.
|
|
But using API 'releases/latest', if no release has been created, the response will ALSO be 404.
|
|
|
|
This test ensures that we consider no update available when no releases are found.
|
|
(And this is why we are using "releases" with a per_page=1 parameter, instead of "releases/latest")
|
|
"""
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(status_code=httpx.codes.OK, json=[])
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway("owner", "repo", client=client)
|
|
update = await notifier.fetch_update()
|
|
|
|
assert update is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_considers_no_update_available_when_only_drafts_and_prereleases_are_found() -> (
|
|
None
|
|
):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
status_code=httpx.codes.OK,
|
|
json=[
|
|
{"tag_name": "v2.0.0-beta", "prerelease": True, "draft": False},
|
|
{"tag_name": "v2.0.0", "prerelease": False, "draft": True},
|
|
],
|
|
)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway("owner", "repo", client=client)
|
|
update = await notifier.fetch_update()
|
|
|
|
assert update is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_picks_the_most_recently_published_non_prerelease_and_non_draft() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
status_code=httpx.codes.OK,
|
|
json=[
|
|
{
|
|
"tag_name": "v2.0.0-beta",
|
|
"prerelease": True,
|
|
"draft": False,
|
|
"published_at": "2025-10-25T112:00:00Z",
|
|
},
|
|
{
|
|
"tag_name": "v2.0.0",
|
|
"prerelease": False,
|
|
"draft": True,
|
|
"published_at": "2025-10-26T112:00:00Z",
|
|
},
|
|
{
|
|
"tag_name": "v1.12.455",
|
|
"prerelease": False,
|
|
"draft": False,
|
|
"published_at": "2025-11-02T112:00:00Z",
|
|
},
|
|
{
|
|
"tag_name": "1.12.400",
|
|
"prerelease": False,
|
|
"draft": False,
|
|
"published_at": "2025-11-10T112:00:00Z",
|
|
},
|
|
{
|
|
"tag_name": "1.12.300",
|
|
"prerelease": False,
|
|
"draft": False,
|
|
"published_at": "2025-11-11T112:00:00Z",
|
|
},
|
|
],
|
|
)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway("owner", "repo", client=client)
|
|
update = await notifier.fetch_update()
|
|
|
|
assert update is not None
|
|
assert update.latest_version == "1.12.300"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"payload",
|
|
[
|
|
[{"tag_name": "v2.0.0-beta", "prerelease": True, "draft": False}],
|
|
[{"tag_name": "v2.0.0", "prerelease": False, "draft": True}],
|
|
],
|
|
)
|
|
@pytest.mark.asyncio
|
|
async def test_ignores_draft_releases_and_prereleases(
|
|
payload: dict[str, object],
|
|
) -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(status_code=httpx.codes.OK, json=payload)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway("owner", "repo", client=client)
|
|
update = await notifier.fetch_update()
|
|
|
|
assert update is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("handler", "expected_cause", "expected_custom_message"),
|
|
[
|
|
(
|
|
lambda _: httpx.Response(status_code=httpx.codes.NOT_FOUND),
|
|
VersionUpdateGatewayCause.NOT_FOUND,
|
|
"Unable to fetch the GitHub releases. Did you export a GITHUB_TOKEN environment variable?",
|
|
),
|
|
(
|
|
lambda _: httpx.Response(
|
|
status_code=httpx.codes.FORBIDDEN,
|
|
headers={"X-RateLimit-Remaining": "0"},
|
|
),
|
|
VersionUpdateGatewayCause.TOO_MANY_REQUESTS,
|
|
None,
|
|
),
|
|
(
|
|
lambda _: httpx.Response(status_code=httpx.codes.TOO_MANY_REQUESTS),
|
|
VersionUpdateGatewayCause.TOO_MANY_REQUESTS,
|
|
None,
|
|
),
|
|
(
|
|
lambda _: httpx.Response(status_code=httpx.codes.FORBIDDEN),
|
|
VersionUpdateGatewayCause.FORBIDDEN,
|
|
None,
|
|
),
|
|
(
|
|
lambda _: httpx.Response(status_code=httpx.codes.INTERNAL_SERVER_ERROR),
|
|
VersionUpdateGatewayCause.ERROR_RESPONSE,
|
|
None,
|
|
),
|
|
(
|
|
lambda _: httpx.Response(status_code=httpx.codes.OK, text="not json"),
|
|
VersionUpdateGatewayCause.INVALID_RESPONSE,
|
|
None,
|
|
),
|
|
(_raise_connect_timeout, VersionUpdateGatewayCause.REQUEST_FAILED, None),
|
|
],
|
|
ids=[
|
|
"not_found",
|
|
"rate_limit_header",
|
|
"rate_limit_status",
|
|
"forbidden",
|
|
"error_response",
|
|
"invalid_json",
|
|
"request_error",
|
|
],
|
|
)
|
|
@pytest.mark.asyncio
|
|
async def test_retrieves_nothing_when_fetching_update_fails(
|
|
handler: Handler,
|
|
expected_cause: VersionUpdateGatewayCause,
|
|
expected_custom_message: str | None,
|
|
) -> None:
|
|
transport = httpx.MockTransport(handler)
|
|
async with httpx.AsyncClient(
|
|
transport=transport, base_url=GITHUB_API_URL
|
|
) as client:
|
|
notifier = GitHubVersionUpdateGateway("owner", "repo", client=client)
|
|
with pytest.raises(VersionUpdateGatewayError) as excinfo:
|
|
await notifier.fetch_update()
|
|
|
|
assert excinfo.value.cause == expected_cause
|
|
if expected_custom_message is not None:
|
|
assert str(excinfo.value) == expected_custom_message
|