|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import json |
| 4 | +from datetime import datetime |
| 5 | +from pathlib import Path |
| 6 | +from unittest import mock |
| 7 | + |
| 8 | +import pytest |
| 9 | +import requests |
| 10 | +from freezegun import freeze_time |
| 11 | + |
| 12 | +from python_eol.cache import ( |
| 13 | + CACHE_EXPIRY, |
| 14 | + _fetch_eol_data, |
| 15 | + _read_cache, |
| 16 | + _write_cache, |
| 17 | + get_eol_data, |
| 18 | +) |
| 19 | + |
| 20 | +FAKE_EOL_DATA = [{"Version": "3.9", "End of Life": "2025-10-01"}] |
| 21 | + |
| 22 | + |
| 23 | +@pytest.fixture |
| 24 | +def mock_cache_file(tmp_path: Path) -> Path: |
| 25 | + """Mock the cache file and its directory.""" |
| 26 | + cache_dir = tmp_path / "python-eol" |
| 27 | + cache_file = cache_dir / "eol_data.json" |
| 28 | + with mock.patch("python_eol.cache.CACHE_DIR", cache_dir), \ |
| 29 | + mock.patch("python_eol.cache.CACHE_FILE", cache_file): |
| 30 | + yield cache_file |
| 31 | + |
| 32 | + |
| 33 | +def test_fetch_eol_data_success() -> None: |
| 34 | + """Test fetching EOL data successfully.""" |
| 35 | + with mock.patch("requests.get") as mock_get: |
| 36 | + mock_get.return_value.raise_for_status.return_value = None |
| 37 | + mock_get.return_value.json.return_value = [ |
| 38 | + {"latest": "3.9.0", "eol": "2025-10-01"} |
| 39 | + ] |
| 40 | + data = _fetch_eol_data() |
| 41 | + assert data == FAKE_EOL_DATA |
| 42 | + |
| 43 | + |
| 44 | +def test_fetch_eol_data_failure() -> None: |
| 45 | + """Test fetching EOL data with a request failure.""" |
| 46 | + with mock.patch("requests.get", side_effect=requests.RequestException("API is down")): |
| 47 | + data = _fetch_eol_data() |
| 48 | + assert data is None |
| 49 | + |
| 50 | + |
| 51 | +def test_read_write_cache(mock_cache_file: Path) -> None: |
| 52 | + """Test writing to and reading from the cache.""" |
| 53 | + _write_cache(FAKE_EOL_DATA) |
| 54 | + assert mock_cache_file.exists() |
| 55 | + with mock_cache_file.open() as f: |
| 56 | + data = json.load(f) |
| 57 | + assert data == FAKE_EOL_DATA |
| 58 | + |
| 59 | + read_data = _read_cache() |
| 60 | + assert read_data == FAKE_EOL_DATA |
| 61 | + |
| 62 | + |
| 63 | +def test_read_cache_expired(mock_cache_file: Path) -> None: |
| 64 | + """Test that an expired cache returns None.""" |
| 65 | + _write_cache(FAKE_EOL_DATA) |
| 66 | + with freeze_time(datetime.now() + CACHE_EXPIRY + CACHE_EXPIRY): |
| 67 | + assert _read_cache() is None |
| 68 | + |
| 69 | + |
| 70 | +def test_read_cache_not_found(mock_cache_file: Path) -> None: |
| 71 | + """Test that a non-existent cache returns None.""" |
| 72 | + assert _read_cache() is None |
| 73 | + |
| 74 | + |
| 75 | +def test_get_eol_data_from_cache(mock_cache_file: Path) -> None: |
| 76 | + """Test get_eol_data reads from a valid cache.""" |
| 77 | + _write_cache(FAKE_EOL_DATA) |
| 78 | + with mock.patch("python_eol.cache._fetch_eol_data") as mock_fetch: |
| 79 | + data = get_eol_data() |
| 80 | + mock_fetch.assert_not_called() |
| 81 | + assert data == FAKE_EOL_DATA |
| 82 | + |
| 83 | + |
| 84 | +def test_get_eol_data_fetches_when_cache_is_stale(mock_cache_file: Path) -> None: |
| 85 | + """Test get_eol_data fetches new data when cache is stale.""" |
| 86 | + _write_cache(FAKE_EOL_DATA) |
| 87 | + with freeze_time(datetime.now() + CACHE_EXPIRY + CACHE_EXPIRY): |
| 88 | + with mock.patch("python_eol.cache._fetch_eol_data") as mock_fetch: |
| 89 | + mock_fetch.return_value = [{"Version": "3.10", "End of Life": "2026-10-01"}] |
| 90 | + data = get_eol_data() |
| 91 | + mock_fetch.assert_called_once() |
| 92 | + assert data == [{"Version": "3.10", "End of Life": "2026-10-01"}] |
| 93 | + |
| 94 | + |
| 95 | +def test_get_eol_data_fetches_when_no_cache(mock_cache_file: Path) -> None: |
| 96 | + """Test get_eol_data fetches new data when no cache exists.""" |
| 97 | + with mock.patch("python_eol.cache._fetch_eol_data") as mock_fetch: |
| 98 | + mock_fetch.return_value = FAKE_EOL_DATA |
| 99 | + data = get_eol_data() |
| 100 | + mock_fetch.assert_called_once() |
| 101 | + assert data == FAKE_EOL_DATA |
0 commit comments