v1.1.3
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>
This commit is contained in:
parent
a340a721ea
commit
661588de0c
48 changed files with 1679 additions and 467 deletions
|
|
@ -0,0 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from vibe.cli.update_notifier.ports.update_cache_repository import (
|
||||
UpdateCache,
|
||||
UpdateCacheRepository,
|
||||
)
|
||||
|
||||
|
||||
class FakeUpdateCacheRepository(UpdateCacheRepository):
|
||||
def __init__(self, update_cache: UpdateCache | None = None) -> None:
|
||||
self.update_cache: UpdateCache | None = update_cache
|
||||
|
||||
async def get(self) -> UpdateCache | None:
|
||||
return self.update_cache
|
||||
|
||||
async def set(self, update_cache: UpdateCache) -> None:
|
||||
self.update_cache = update_cache
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from vibe.cli.update_notifier.ports.version_update_gateway import (
|
||||
VersionUpdate,
|
||||
VersionUpdateGateway,
|
||||
VersionUpdateGatewayError,
|
||||
)
|
||||
|
||||
|
||||
class FakeVersionUpdateGateway(VersionUpdateGateway):
|
||||
def __init__(
|
||||
self,
|
||||
update: VersionUpdate | None = None,
|
||||
error: VersionUpdateGatewayError | None = None,
|
||||
) -> None:
|
||||
self._update: VersionUpdate | None = update
|
||||
self._error = error
|
||||
self.fetch_update_calls = 0
|
||||
|
||||
async def fetch_update(self) -> VersionUpdate | None:
|
||||
self.fetch_update_calls += 1
|
||||
if self._error is not None:
|
||||
raise self._error
|
||||
return self._update
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe.cli.update_notifier.adapters.filesystem_update_cache_repository import (
|
||||
FileSystemUpdateCacheRepository,
|
||||
)
|
||||
from vibe.cli.update_notifier.ports.update_cache_repository import UpdateCache
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reads_cache_from_file_when_present(tmp_path: Path) -> None:
|
||||
cache_file = tmp_path / "update_cache.json"
|
||||
cache_file.write_text(
|
||||
json.dumps({"latest_version": "1.2.3", "stored_at_timestamp": 1_700_000_000})
|
||||
)
|
||||
repository = FileSystemUpdateCacheRepository(base_path=tmp_path)
|
||||
|
||||
cache = await repository.get()
|
||||
|
||||
assert cache is not None
|
||||
assert cache.latest_version == "1.2.3"
|
||||
assert cache.stored_at_timestamp == 1_700_000_000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_none_when_cache_file_is_missing(tmp_path: Path) -> None:
|
||||
repository = FileSystemUpdateCacheRepository(base_path=tmp_path)
|
||||
|
||||
cache = await repository.get()
|
||||
|
||||
assert cache is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_none_when_cache_file_is_corrupted(tmp_path: Path) -> None:
|
||||
cache_dir = tmp_path / ".vibe"
|
||||
cache_dir.mkdir()
|
||||
(cache_dir / "update_cache.json").write_text("{not-json")
|
||||
repository = FileSystemUpdateCacheRepository(base_path=tmp_path)
|
||||
|
||||
cache = await repository.get()
|
||||
|
||||
assert cache is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_overwrites_existing_cache(tmp_path: Path) -> None:
|
||||
cache_file = tmp_path / "update_cache.json"
|
||||
cache_file.write_text(
|
||||
json.dumps({"latest_version": "1.0.0", "stored_at_timestamp": 1_600_000_000})
|
||||
)
|
||||
repository = FileSystemUpdateCacheRepository(base_path=tmp_path)
|
||||
|
||||
await repository.set(
|
||||
UpdateCache(latest_version="1.1.0", stored_at_timestamp=1_700_200_000)
|
||||
)
|
||||
|
||||
content = json.loads(cache_file.read_text())
|
||||
assert content["latest_version"] == "1.1.0"
|
||||
assert content["stored_at_timestamp"] == 1_700_200_000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_silently_ignores_errors_when_writing_cache_fails(tmp_path: Path) -> None:
|
||||
cache_dir = tmp_path / ".vibe"
|
||||
cache_dir.mkdir()
|
||||
(cache_dir / "update_cache.json").mkdir()
|
||||
repository = FileSystemUpdateCacheRepository(base_path=tmp_path)
|
||||
|
||||
await repository.set(
|
||||
UpdateCache(latest_version="1.2.0", stored_at_timestamp=1_700_300_000)
|
||||
)
|
||||
|
||||
assert (cache_dir / "update_cache.json").is_dir()
|
||||
|
|
@ -5,10 +5,8 @@ from collections.abc import Callable
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
from vibe.cli.update_notifier.github_version_update_gateway import (
|
||||
from vibe.cli.update_notifier import (
|
||||
GitHubVersionUpdateGateway,
|
||||
)
|
||||
from vibe.cli.update_notifier.version_update_gateway import (
|
||||
VersionUpdateGatewayCause,
|
||||
VersionUpdateGatewayError,
|
||||
)
|
||||
|
|
|
|||
157
tests/update_notifier/test_pypi_version_update_gateway.py
Normal file
157
tests/update_notifier/test_pypi_version_update_gateway.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from vibe.cli.update_notifier.adapters.pypi_version_update_gateway import (
|
||||
PyPIVersionUpdateGateway,
|
||||
)
|
||||
from vibe.cli.update_notifier.ports.version_update_gateway import (
|
||||
VersionUpdate,
|
||||
VersionUpdateGatewayCause,
|
||||
VersionUpdateGatewayError,
|
||||
)
|
||||
|
||||
Handler = Callable[[httpx.Request], httpx.Response]
|
||||
|
||||
PYPI_API_URL = "https://pypi.org"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieves_nothing_when_no_versions_are_available() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
status_code=httpx.codes.OK, json={"versions": [], "files": []}
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
async with httpx.AsyncClient(transport=transport, base_url=PYPI_API_URL) as client:
|
||||
gateway = PyPIVersionUpdateGateway(project_name="mistral-vibe", client=client)
|
||||
update = await gateway.fetch_update()
|
||||
|
||||
assert update is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieves_the_latest_non_yanked_version() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert request.headers["Accept"] == "application/vnd.pypi.simple.v1+json"
|
||||
assert request.url.path == "/simple/mistral-vibe/"
|
||||
return httpx.Response(
|
||||
status_code=httpx.codes.OK,
|
||||
json={
|
||||
"versions": ["1.0.0", "1.0.1", "1.0.2"],
|
||||
"files": [
|
||||
{
|
||||
"filename": "mistral_vibe-1.0.0-py3-none-any.whl",
|
||||
"yanked": False,
|
||||
},
|
||||
{"filename": "mistral_vibe-1.0.1-py3-none-any.whl", "yanked": True},
|
||||
{
|
||||
"filename": "mistral_vibe-1.0.2-py3-none-any.whl",
|
||||
"yanked": False,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
async with httpx.AsyncClient(transport=transport, base_url=PYPI_API_URL) as client:
|
||||
gateway = PyPIVersionUpdateGateway(project_name="mistral-vibe", client=client)
|
||||
update = await gateway.fetch_update()
|
||||
|
||||
assert update == VersionUpdate(latest_version="1.0.2")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieves_nothing_when_only_yanked_versions_are_available() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
status_code=httpx.codes.OK,
|
||||
json={
|
||||
"versions": ["1.0.0"],
|
||||
"files": [
|
||||
{"filename": "mistral_vibe-1.0.0-py3-none-any.whl", "yanked": True}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
async with httpx.AsyncClient(transport=transport, base_url=PYPI_API_URL) as client:
|
||||
gateway = PyPIVersionUpdateGateway(project_name="mistral-vibe", client=client)
|
||||
update = await gateway.fetch_update()
|
||||
|
||||
assert update is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_does_not_match_versions_by_substring() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
status_code=httpx.codes.OK,
|
||||
json={
|
||||
"versions": ["1.0.1"],
|
||||
"files": [
|
||||
{
|
||||
"filename": "mistral_vibe-1.0.10-py3-none-any.whl",
|
||||
"yanked": False,
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
async with httpx.AsyncClient(transport=transport, base_url=PYPI_API_URL) as client:
|
||||
gateway = PyPIVersionUpdateGateway(project_name="mistral-vibe", client=client)
|
||||
update = await gateway.fetch_update()
|
||||
|
||||
assert update is None
|
||||
|
||||
|
||||
def _raise_connect_timeout(request: httpx.Request) -> httpx.Response:
|
||||
raise httpx.ConnectTimeout("boom", request=request)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("handler", "expected_cause", "expected_message"),
|
||||
[
|
||||
(
|
||||
lambda _: httpx.Response(status_code=httpx.codes.NOT_FOUND),
|
||||
VersionUpdateGatewayCause.NOT_FOUND,
|
||||
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, content=b"{not-json"),
|
||||
VersionUpdateGatewayCause.INVALID_RESPONSE,
|
||||
None,
|
||||
),
|
||||
(_raise_connect_timeout, VersionUpdateGatewayCause.REQUEST_FAILED, None),
|
||||
],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieves_nothing_when_fetching_update_fails(
|
||||
handler: Callable[[httpx.Request], httpx.Response],
|
||||
expected_cause: VersionUpdateGatewayCause,
|
||||
expected_message: str | None,
|
||||
) -> None:
|
||||
transport = httpx.MockTransport(handler)
|
||||
async with httpx.AsyncClient(transport=transport, base_url=PYPI_API_URL) as client:
|
||||
gateway = PyPIVersionUpdateGateway(project_name="mistral-vibe", client=client)
|
||||
with pytest.raises(VersionUpdateGatewayError) as excinfo:
|
||||
await gateway.fetch_update()
|
||||
|
||||
assert excinfo.value.cause == expected_cause
|
||||
if expected_message is not None:
|
||||
assert str(excinfo.value) == expected_message
|
||||
|
|
@ -1,16 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Protocol
|
||||
|
||||
import pytest
|
||||
from textual.app import Notification
|
||||
|
||||
from vibe.cli.textual_ui.app import VibeApp
|
||||
from vibe.cli.update_notifier.fake_version_update_gateway import (
|
||||
from tests.update_notifier.adapters.fake_update_cache_repository import (
|
||||
FakeUpdateCacheRepository,
|
||||
)
|
||||
from tests.update_notifier.adapters.fake_version_update_gateway import (
|
||||
FakeVersionUpdateGateway,
|
||||
)
|
||||
from vibe.cli.update_notifier.version_update_gateway import (
|
||||
from vibe.cli.textual_ui.app import VibeApp
|
||||
from vibe.cli.update_notifier import (
|
||||
UpdateCache,
|
||||
VersionUpdate,
|
||||
VersionUpdateGatewayCause,
|
||||
VersionUpdateGatewayError,
|
||||
|
|
@ -59,6 +64,7 @@ class VibeAppFactory(Protocol):
|
|||
self,
|
||||
*,
|
||||
notifier: FakeVersionUpdateGateway,
|
||||
update_cache_repository: FakeUpdateCacheRepository | None = None,
|
||||
config: VibeConfig | None = None,
|
||||
auto_approve: bool = False,
|
||||
current_version: str = "0.1.0",
|
||||
|
|
@ -67,9 +73,13 @@ class VibeAppFactory(Protocol):
|
|||
|
||||
@pytest.fixture
|
||||
def make_vibe_app(vibe_config_with_update_checks_enabled: VibeConfig) -> VibeAppFactory:
|
||||
update_cache_repository = FakeUpdateCacheRepository()
|
||||
|
||||
def _make_app(
|
||||
*,
|
||||
notifier: FakeVersionUpdateGateway,
|
||||
update_cache_repository: FakeUpdateCacheRepository
|
||||
| None = update_cache_repository,
|
||||
config: VibeConfig | None = None,
|
||||
auto_approve: bool = False,
|
||||
current_version: str = "0.1.0",
|
||||
|
|
@ -78,6 +88,7 @@ def make_vibe_app(vibe_config_with_update_checks_enabled: VibeConfig) -> VibeApp
|
|||
config=config or vibe_config_with_update_checks_enabled,
|
||||
auto_approve=auto_approve,
|
||||
version_update_notifier=notifier,
|
||||
update_cache_repository=update_cache_repository,
|
||||
current_version=current_version,
|
||||
)
|
||||
|
||||
|
|
@ -159,3 +170,52 @@ async def test_ui_does_not_invoke_gateway_nor_show_update_notification_when_upda
|
|||
await _assert_no_notifications(app, pilot, timeout=0.3)
|
||||
|
||||
assert notifier.fetch_update_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_does_not_show_toast_when_update_is_known_in_recent_cache_already(
|
||||
make_vibe_app: VibeAppFactory,
|
||||
) -> None:
|
||||
timestamp_two_hours_ago = int(time.time()) - 2 * 60 * 60
|
||||
notifier = FakeVersionUpdateGateway(update=VersionUpdate(latest_version="0.2.0"))
|
||||
update_cache = UpdateCache(
|
||||
latest_version="0.2.0", stored_at_timestamp=timestamp_two_hours_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
app = make_vibe_app(
|
||||
notifier=notifier, update_cache_repository=update_cache_repository
|
||||
)
|
||||
|
||||
async with app.run_test() as pilot:
|
||||
await _assert_no_notifications(app, pilot, timeout=0.3)
|
||||
|
||||
assert notifier.fetch_update_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_does_show_toast_when_cache_entry_is_too_old(
|
||||
make_vibe_app: VibeAppFactory,
|
||||
) -> None:
|
||||
timestamp_two_days_ago = int(time.time()) - 2 * 24 * 60 * 60
|
||||
notifier = FakeVersionUpdateGateway(update=VersionUpdate(latest_version="0.2.0"))
|
||||
update_cache = UpdateCache(
|
||||
latest_version="0.2.0", stored_at_timestamp=timestamp_two_days_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
app = make_vibe_app(
|
||||
notifier=notifier, update_cache_repository=update_cache_repository
|
||||
)
|
||||
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.pause(0.3)
|
||||
notifications = list(app._notifications)
|
||||
|
||||
assert notifications
|
||||
notification = notifications[-1]
|
||||
assert notification.severity == "information"
|
||||
assert notification.title == "Update available"
|
||||
assert (
|
||||
notification.message
|
||||
== '0.1.0 => 0.2.0\nRun "uv tool upgrade mistral-vibe" to update'
|
||||
)
|
||||
assert notifier.fetch_update_calls == 1
|
||||
|
|
|
|||
|
|
@ -2,18 +2,27 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from vibe.cli.update_notifier.fake_version_update_gateway import (
|
||||
from tests.update_notifier.adapters.fake_update_cache_repository import (
|
||||
FakeUpdateCacheRepository,
|
||||
)
|
||||
from tests.update_notifier.adapters.fake_version_update_gateway import (
|
||||
FakeVersionUpdateGateway,
|
||||
)
|
||||
from vibe.cli.update_notifier.version_update import (
|
||||
VersionUpdateError,
|
||||
is_version_update_available,
|
||||
)
|
||||
from vibe.cli.update_notifier.version_update_gateway import (
|
||||
from vibe.cli.update_notifier import (
|
||||
UpdateCache,
|
||||
VersionUpdate,
|
||||
VersionUpdateGatewayCause,
|
||||
VersionUpdateGatewayError,
|
||||
)
|
||||
from vibe.cli.update_notifier.version_update import (
|
||||
VersionUpdateError,
|
||||
get_update_if_available,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def current_timestamp() -> int:
|
||||
return 1765278683
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -23,8 +32,10 @@ async def test_retrieves_the_latest_version_update_when_available() -> None:
|
|||
update=VersionUpdate(latest_version=latest_update)
|
||||
)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version="1.0.0"
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is not None
|
||||
|
|
@ -39,8 +50,10 @@ async def test_retrieves_nothing_when_the_current_version_is_the_latest() -> Non
|
|||
update=VersionUpdate(latest_version=latest_version)
|
||||
)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version=current_version
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version=current_version,
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is None
|
||||
|
|
@ -56,8 +69,10 @@ async def test_retrieves_nothing_when_the_current_version_is_greater_than_the_la
|
|||
update=VersionUpdate(latest_version=latest_version)
|
||||
)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version=current_version
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version=current_version,
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is None
|
||||
|
|
@ -67,8 +82,10 @@ async def test_retrieves_nothing_when_the_current_version_is_greater_than_the_la
|
|||
async def test_retrieves_nothing_when_no_version_is_available() -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(update=None)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version="1.0.0"
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is None
|
||||
|
|
@ -80,8 +97,10 @@ async def test_retrieves_nothing_when_latest_version_is_invalid() -> None:
|
|||
update=VersionUpdate(latest_version="invalid-version")
|
||||
)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version="1.0.0"
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is None
|
||||
|
|
@ -96,8 +115,10 @@ async def test_replaces_hyphens_with_plus_signs_in_latest_version_to_conform_wit
|
|||
update=VersionUpdate(latest_version="1.6.1-jetbrains")
|
||||
)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version="1.0.0"
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is not None
|
||||
|
|
@ -110,8 +131,10 @@ async def test_retrieves_nothing_when_current_version_is_invalid() -> None:
|
|||
update=VersionUpdate(latest_version="1.0.1")
|
||||
)
|
||||
|
||||
update = await is_version_update_available(
|
||||
version_update_notifier, current_version="invalid-version"
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="invalid-version",
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert update is None
|
||||
|
|
@ -139,8 +162,166 @@ async def test_raises_version_update_error(
|
|||
)
|
||||
|
||||
with pytest.raises(VersionUpdateError) as excinfo:
|
||||
await is_version_update_available(
|
||||
version_update_notifier, current_version="1.0.0"
|
||||
await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=FakeUpdateCacheRepository(),
|
||||
)
|
||||
|
||||
assert expected_message_substring in str(excinfo.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notifies_and_updates_cache_when_repository_is_empty(
|
||||
current_timestamp: int,
|
||||
) -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(
|
||||
update=VersionUpdate(latest_version="1.0.1")
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository()
|
||||
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=update_cache_repository,
|
||||
get_current_timestamp=lambda: current_timestamp,
|
||||
)
|
||||
|
||||
assert update is not None
|
||||
assert update.latest_version == "1.0.1"
|
||||
assert update.should_notify is True
|
||||
assert version_update_notifier.fetch_update_calls == 1
|
||||
assert update_cache_repository.update_cache is not None
|
||||
assert update_cache_repository.update_cache.latest_version == "1.0.1"
|
||||
assert update_cache_repository.update_cache.stored_at_timestamp == current_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_does_not_notify_when_an_available_update_has_been_recently_cached(
|
||||
current_timestamp: int,
|
||||
) -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(
|
||||
update=VersionUpdate(latest_version="1.0.1")
|
||||
)
|
||||
timestamp_twelve_hours_ago = current_timestamp - 12 * 60 * 60
|
||||
update_cache = UpdateCache(
|
||||
latest_version="1.0.1", stored_at_timestamp=timestamp_twelve_hours_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=update_cache_repository,
|
||||
get_current_timestamp=lambda: current_timestamp,
|
||||
)
|
||||
|
||||
assert update is not None
|
||||
assert update.latest_version == "1.0.1"
|
||||
assert update.should_notify is False
|
||||
assert version_update_notifier.fetch_update_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieves_nothing_when_the_recently_cached_update_is_the_one_currently_in_use(
|
||||
current_timestamp: int,
|
||||
) -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(
|
||||
update=VersionUpdate(latest_version="1.0.1")
|
||||
)
|
||||
timestamp_twelve_hours_ago = current_timestamp - 12 * 60 * 60
|
||||
update_cache = UpdateCache(
|
||||
latest_version="1.0.1", stored_at_timestamp=timestamp_twelve_hours_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.1",
|
||||
update_cache_repository=update_cache_repository,
|
||||
get_current_timestamp=lambda: current_timestamp,
|
||||
)
|
||||
|
||||
assert update is None
|
||||
assert version_update_notifier.fetch_update_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieves_fresh_update_and_notifies_and_updates_cache_when_cache_is_not_fresh(
|
||||
current_timestamp: int,
|
||||
) -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(
|
||||
update=VersionUpdate(latest_version="1.0.2")
|
||||
)
|
||||
timestamp_two_days_ago = current_timestamp - 48 * 60 * 60
|
||||
update_cache = UpdateCache(
|
||||
latest_version="1.0.1", stored_at_timestamp=timestamp_two_days_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=update_cache_repository,
|
||||
get_current_timestamp=lambda: current_timestamp,
|
||||
)
|
||||
|
||||
assert update is not None
|
||||
assert version_update_notifier.fetch_update_calls == 1
|
||||
assert update.should_notify is True
|
||||
assert update.latest_version == "1.0.2"
|
||||
assert update_cache_repository.update_cache is not None
|
||||
assert update_cache_repository.update_cache.stored_at_timestamp == current_timestamp
|
||||
assert update_cache_repository.update_cache.latest_version == "1.0.2"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_updates_cache_timestamp_with_current_version_when_no_update_is_available(
|
||||
current_timestamp: int,
|
||||
) -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(update=None)
|
||||
timestamp_two_days_ago = current_timestamp - 48 * 60 * 60
|
||||
update_cache = UpdateCache(
|
||||
latest_version="1.0.0", stored_at_timestamp=timestamp_two_days_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
|
||||
update = await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=update_cache_repository,
|
||||
get_current_timestamp=lambda: current_timestamp,
|
||||
)
|
||||
|
||||
assert update is None
|
||||
assert version_update_notifier.fetch_update_calls == 1
|
||||
assert update_cache_repository.update_cache is not None
|
||||
assert update_cache_repository.update_cache.latest_version == "1.0.0"
|
||||
assert update_cache_repository.update_cache.stored_at_timestamp == current_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_updates_cache_timestamp_with_current_version_when_gateway_errors(
|
||||
current_timestamp: int,
|
||||
) -> None:
|
||||
version_update_notifier = FakeVersionUpdateGateway(
|
||||
error=VersionUpdateGatewayError(cause=VersionUpdateGatewayCause.ERROR_RESPONSE)
|
||||
)
|
||||
timestamp_two_days_ago = current_timestamp - 48 * 60 * 60
|
||||
update_cache = UpdateCache(
|
||||
latest_version="1.0.0", stored_at_timestamp=timestamp_two_days_ago
|
||||
)
|
||||
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
|
||||
|
||||
with pytest.raises(VersionUpdateError):
|
||||
await get_update_if_available(
|
||||
version_update_notifier,
|
||||
current_version="1.0.0",
|
||||
update_cache_repository=update_cache_repository,
|
||||
get_current_timestamp=lambda: current_timestamp,
|
||||
)
|
||||
|
||||
assert version_update_notifier.fetch_update_calls == 1
|
||||
assert update_cache_repository.update_cache is not None
|
||||
assert update_cache_repository.update_cache.latest_version == "1.0.0"
|
||||
assert update_cache_repository.update_cache.stored_at_timestamp == current_timestamp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue