Files
EvoScientist/tests/test_update_check.py
Xi Zhang 3503142af6 feat(tui): UX polish — multi-line input, timestamps, update check & v0.0.3 (#85)
* feat: implement background update check and startup notifications

* feat: enhance user experience with timestamp notifications and UI polish

* feat: implement multi-line chat input with Enter-to-submit and modifier+Enter newline

* update

* update

* v0.0.3

* feat: improve code readability with consistent formatting in TUI and test files

* feat: update PyPI badge version to v0.0.3 in README files

* feat: add docstrings for test classes in test_update_check.py
2026-03-20 19:57:35 +00:00

158 lines
5.6 KiB
Python

"""Tests for EvoScientist.update_check module."""
import json
import time
from unittest.mock import MagicMock, patch
from EvoScientist.update_check import (
CACHE_TTL,
_parse_version,
get_latest_version,
is_update_available,
)
class TestParseVersion:
"""Tests for _parse_version."""
def test_basic(self):
assert _parse_version("1.2.3") == (1, 2, 3)
def test_single_digit(self):
assert _parse_version("5") == (5,)
def test_whitespace(self):
assert _parse_version(" 0.0.2 ") == (0, 0, 2)
def test_comparison(self):
assert _parse_version("0.0.3") > _parse_version("0.0.2")
assert _parse_version("0.1.0") > _parse_version("0.0.9")
assert _parse_version("1.0.0") > _parse_version("0.9.9")
def test_equal(self):
assert _parse_version("0.0.2") == _parse_version("0.0.2")
class TestGetLatestVersion:
"""Tests for get_latest_version."""
def test_fresh_cache_hit(self, tmp_path):
cache_file = tmp_path / "latest_version.json"
cache_file.write_text(
json.dumps({"version": "1.0.0", "checked_at": time.time()})
)
with patch("EvoScientist.update_check.CACHE_FILE", cache_file):
assert get_latest_version() == "1.0.0"
def test_stale_cache_fetches_pypi(self, tmp_path):
cache_file = tmp_path / "latest_version.json"
cache_file.write_text(
json.dumps(
{
"version": "0.0.1",
"checked_at": time.time() - CACHE_TTL - 1,
}
)
)
mock_resp = MagicMock()
mock_resp.read.return_value = json.dumps(
{"info": {"version": "2.0.0"}}
).encode()
mock_resp.__enter__ = lambda s: s
mock_resp.__exit__ = MagicMock(return_value=False)
with (
patch("EvoScientist.update_check.CACHE_FILE", cache_file),
patch("EvoScientist.update_check.CACHE_DIR", tmp_path),
patch("urllib.request.urlopen", return_value=mock_resp),
):
assert get_latest_version() == "2.0.0"
def test_no_cache_fetches_pypi(self, tmp_path):
cache_file = tmp_path / "latest_version.json"
mock_resp = MagicMock()
mock_resp.read.return_value = json.dumps(
{"info": {"version": "3.0.0"}}
).encode()
mock_resp.__enter__ = lambda s: s
mock_resp.__exit__ = MagicMock(return_value=False)
with (
patch("EvoScientist.update_check.CACHE_FILE", cache_file),
patch("EvoScientist.update_check.CACHE_DIR", tmp_path),
patch("urllib.request.urlopen", return_value=mock_resp),
):
assert get_latest_version() == "3.0.0"
# Cache should have been written
assert cache_file.exists()
data = json.loads(cache_file.read_text())
assert data["version"] == "3.0.0"
def test_network_error_returns_none(self, tmp_path):
cache_file = tmp_path / "latest_version.json"
with (
patch("EvoScientist.update_check.CACHE_FILE", cache_file),
patch("urllib.request.urlopen", side_effect=OSError("network down")),
):
assert get_latest_version() is None
def test_corrupt_cache_recovers(self, tmp_path):
cache_file = tmp_path / "latest_version.json"
cache_file.write_text("not valid json!!!")
mock_resp = MagicMock()
mock_resp.read.return_value = json.dumps(
{"info": {"version": "1.5.0"}}
).encode()
mock_resp.__enter__ = lambda s: s
mock_resp.__exit__ = MagicMock(return_value=False)
with (
patch("EvoScientist.update_check.CACHE_FILE", cache_file),
patch("EvoScientist.update_check.CACHE_DIR", tmp_path),
patch("urllib.request.urlopen", return_value=mock_resp),
):
assert get_latest_version() == "1.5.0"
class TestIsUpdateAvailable:
"""Tests for is_update_available."""
def test_newer_version_available(self):
with (
patch("EvoScientist.update_check.get_latest_version", return_value="9.9.9"),
patch("EvoScientist.update_check._installed_version", return_value="0.0.2"),
):
available, latest = is_update_available()
assert available is True
assert latest == "9.9.9"
def test_same_version(self):
with (
patch("EvoScientist.update_check.get_latest_version", return_value="0.0.2"),
patch("EvoScientist.update_check._installed_version", return_value="0.0.2"),
):
available, _latest = is_update_available()
assert available is False
def test_older_pypi_version(self):
with (
patch("EvoScientist.update_check.get_latest_version", return_value="0.0.1"),
patch("EvoScientist.update_check._installed_version", return_value="0.0.2"),
):
available, _latest = is_update_available()
assert available is False
def test_pypi_unreachable(self):
with patch("EvoScientist.update_check.get_latest_version", return_value=None):
available, latest = is_update_available()
assert available is False
assert latest is None
def test_invalid_version_string(self):
with (
patch("EvoScientist.update_check.get_latest_version", return_value="abc"),
patch("EvoScientist.update_check._installed_version", return_value="0.0.2"),
):
available, _latest = is_update_available()
assert available is False