Skip to content

Commit 4007223

Browse files
committed
increase test coverage
1 parent 4910e0b commit 4007223

9 files changed

Lines changed: 1846 additions & 0 deletions

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ dev = [
2626
requires = ["uv_build>=0.11.7,<0.12.0"]
2727
build-backend = "uv_build"
2828

29+
[tool.coverage.run]
30+
omit = ["tests/*"]
31+
2932
[tool.ruff]
3033
indent-width = 4
3134
line-length = 120

tests/test_check_remote.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import datetime
2+
import os
3+
from pathlib import Path
4+
from unittest.mock import MagicMock, patch
5+
6+
import pytest
7+
from requests.exceptions import RequestException
8+
9+
from src import check_remote
10+
from src.utils import config
11+
12+
13+
@pytest.fixture
14+
def fake_dirs(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
15+
data_dir = tmp_path / "data"
16+
photon_data_dir = data_dir / "photon_data"
17+
os_node_dir = photon_data_dir / "node_1"
18+
data_dir.mkdir()
19+
photon_data_dir.mkdir()
20+
monkeypatch.setattr(config, "DATA_DIR", str(data_dir))
21+
monkeypatch.setattr(config, "PHOTON_DATA_DIR", str(photon_data_dir))
22+
monkeypatch.setattr(config, "OS_NODE_DIR", str(os_node_dir))
23+
return data_dir
24+
25+
26+
def _mock_response(status_code=200, headers=None):
27+
resp = MagicMock()
28+
resp.status_code = status_code
29+
resp.headers = headers or {}
30+
resp.raise_for_status = MagicMock()
31+
return resp
32+
33+
34+
def test_get_remote_file_size_uses_content_length():
35+
resp = _mock_response(headers={"content-length": "12345"})
36+
with patch("src.check_remote.requests.head", return_value=resp):
37+
assert check_remote.get_remote_file_size("https://example.com/x") == 12345
38+
39+
40+
def test_get_remote_file_size_falls_back_to_range_request():
41+
head_resp = _mock_response(headers={})
42+
range_resp = _mock_response(status_code=206, headers={"content-range": "bytes 0-0/9876"})
43+
with (
44+
patch("src.check_remote.requests.head", return_value=head_resp),
45+
patch("src.check_remote.requests.get", return_value=range_resp),
46+
):
47+
assert check_remote.get_remote_file_size("https://example.com/x") == 9876
48+
49+
50+
def test_get_remote_file_size_raises_when_no_size_returned():
51+
head_resp = _mock_response(headers={})
52+
range_resp = _mock_response(status_code=200, headers={})
53+
with (
54+
patch("src.check_remote.requests.head", return_value=head_resp),
55+
patch("src.check_remote.requests.get", return_value=range_resp),
56+
pytest.raises(check_remote.RemoteFileSizeError, match="did not return file size"),
57+
):
58+
check_remote.get_remote_file_size("https://example.com/x")
59+
60+
61+
def test_get_remote_file_size_wraps_request_errors():
62+
with (
63+
patch("src.check_remote.requests.head", side_effect=RequestException("boom")),
64+
pytest.raises(check_remote.RemoteFileSizeError, match="Could not determine remote file size"),
65+
):
66+
check_remote.get_remote_file_size("https://example.com/x")
67+
68+
69+
def test_get_remote_file_size_ignores_non_digit_range_total():
70+
head_resp = _mock_response(headers={})
71+
range_resp = _mock_response(status_code=206, headers={"content-range": "bytes 0-0/*"})
72+
with (
73+
patch("src.check_remote.requests.head", return_value=head_resp),
74+
patch("src.check_remote.requests.get", return_value=range_resp),
75+
pytest.raises(check_remote.RemoteFileSizeError),
76+
):
77+
check_remote.get_remote_file_size("https://example.com/x")
78+
79+
80+
def test_get_remote_time_returns_parsed_datetime():
81+
resp = _mock_response(headers={"last-modified": "Wed, 21 Oct 2026 07:28:00 GMT"})
82+
with patch("src.check_remote.requests.head", return_value=resp):
83+
result = check_remote.get_remote_time("https://example.com")
84+
assert result is not None
85+
assert result.year == 2026
86+
assert result.month == 10
87+
assert result.day == 21
88+
89+
90+
def test_get_remote_time_returns_none_when_header_missing():
91+
resp = _mock_response(headers={})
92+
with patch("src.check_remote.requests.head", return_value=resp):
93+
assert check_remote.get_remote_time("https://example.com") is None
94+
95+
96+
def test_get_remote_time_returns_none_on_request_error():
97+
with patch("src.check_remote.requests.head", side_effect=RequestException("nope")):
98+
assert check_remote.get_remote_time("https://example.com") is None
99+
100+
101+
def test_get_local_time_returns_marker_mtime_when_present(fake_dirs: Path):
102+
marker = fake_dirs / ".photon-index-updated"
103+
marker.write_text("")
104+
os.utime(marker, (1_000_000, 1_000_000))
105+
assert check_remote.get_local_time(str(fake_dirs / "missing")) == 1_000_000
106+
107+
108+
def test_get_local_time_returns_path_mtime_when_no_marker(fake_dirs: Path):
109+
target = fake_dirs / "node_1"
110+
target.mkdir()
111+
os.utime(target, (2_000_000, 2_000_000))
112+
assert check_remote.get_local_time(str(target)) == 2_000_000
113+
114+
115+
def test_get_local_time_returns_zero_when_path_missing(fake_dirs: Path):
116+
assert check_remote.get_local_time(str(fake_dirs / "missing")) == 0.0
117+
118+
119+
def test_compare_mtime_returns_false_when_remote_time_unknown(fake_dirs: Path, monkeypatch: pytest.MonkeyPatch):
120+
monkeypatch.setattr(config, "REGION", None)
121+
monkeypatch.setattr(config, "BASE_URL", "https://example.com")
122+
with patch("src.check_remote.get_remote_time", return_value=None):
123+
assert check_remote.compare_mtime() is False
124+
125+
126+
def test_compare_mtime_returns_false_on_invalid_region(monkeypatch: pytest.MonkeyPatch):
127+
monkeypatch.setattr(config, "REGION", "atlantis")
128+
assert check_remote.compare_mtime() is False
129+
130+
131+
def test_compare_mtime_with_marker_compares_directly(fake_dirs: Path, monkeypatch: pytest.MonkeyPatch):
132+
monkeypatch.setattr(config, "REGION", None)
133+
monkeypatch.setattr(config, "BASE_URL", "https://example.com")
134+
135+
marker = fake_dirs / ".photon-index-updated"
136+
marker.write_text("")
137+
local_ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC).timestamp()
138+
os.utime(marker, (local_ts, local_ts))
139+
140+
remote_dt = datetime.datetime(2026, 1, 2, tzinfo=datetime.UTC)
141+
with patch("src.check_remote.get_remote_time", return_value=remote_dt):
142+
assert check_remote.compare_mtime() is True
143+
144+
145+
def test_compare_mtime_with_marker_returns_false_when_remote_older(fake_dirs: Path, monkeypatch: pytest.MonkeyPatch):
146+
monkeypatch.setattr(config, "REGION", None)
147+
monkeypatch.setattr(config, "BASE_URL", "https://example.com")
148+
149+
marker = fake_dirs / ".photon-index-updated"
150+
marker.write_text("")
151+
local_ts = datetime.datetime(2026, 1, 10, tzinfo=datetime.UTC).timestamp()
152+
os.utime(marker, (local_ts, local_ts))
153+
154+
remote_dt = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC)
155+
with patch("src.check_remote.get_remote_time", return_value=remote_dt):
156+
assert check_remote.compare_mtime() is False
157+
158+
159+
def test_compare_mtime_directory_uses_grace_period(fake_dirs: Path, monkeypatch: pytest.MonkeyPatch):
160+
monkeypatch.setattr(config, "REGION", None)
161+
monkeypatch.setattr(config, "BASE_URL", "https://example.com")
162+
163+
node_dir = Path(config.OS_NODE_DIR)
164+
node_dir.mkdir()
165+
local_ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC).timestamp()
166+
os.utime(node_dir, (local_ts, local_ts))
167+
168+
remote_within_grace = datetime.datetime(2026, 1, 5, tzinfo=datetime.UTC)
169+
with patch("src.check_remote.get_remote_time", return_value=remote_within_grace):
170+
assert check_remote.compare_mtime() is False
171+
172+
remote_past_grace = datetime.datetime(2026, 1, 20, tzinfo=datetime.UTC)
173+
with patch("src.check_remote.get_remote_time", return_value=remote_past_grace):
174+
assert check_remote.compare_mtime() is True
175+
176+
177+
def test_check_index_age_returns_true_when_min_date_unset(monkeypatch: pytest.MonkeyPatch):
178+
monkeypatch.setattr(config, "MIN_INDEX_DATE", None)
179+
assert check_remote.check_index_age() is True
180+
181+
182+
def test_check_index_age_warns_and_returns_true_on_bad_format(monkeypatch: pytest.MonkeyPatch):
183+
monkeypatch.setattr(config, "MIN_INDEX_DATE", "2026-01-01")
184+
assert check_remote.check_index_age() is True
185+
186+
187+
def test_check_index_age_returns_true_when_no_local_index(fake_dirs: Path, monkeypatch: pytest.MonkeyPatch):
188+
monkeypatch.setattr(config, "MIN_INDEX_DATE", "01.01.26")
189+
assert check_remote.check_index_age() is True
190+
191+
192+
def test_check_index_age_returns_true_when_update_required_due_to_old_index(
193+
fake_dirs: Path, monkeypatch: pytest.MonkeyPatch
194+
):
195+
monkeypatch.setattr(config, "MIN_INDEX_DATE", "01.06.26")
196+
node_dir = Path(config.OS_NODE_DIR)
197+
node_dir.mkdir()
198+
local_ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC).timestamp()
199+
os.utime(node_dir, (local_ts, local_ts))
200+
assert check_remote.check_index_age() is True
201+
202+
203+
def test_check_index_age_returns_false_when_local_meets_min(fake_dirs: Path, monkeypatch: pytest.MonkeyPatch):
204+
monkeypatch.setattr(config, "MIN_INDEX_DATE", "01.01.26")
205+
node_dir = Path(config.OS_NODE_DIR)
206+
node_dir.mkdir()
207+
local_ts = datetime.datetime(2026, 6, 1, tzinfo=datetime.UTC).timestamp()
208+
os.utime(node_dir, (local_ts, local_ts))
209+
assert check_remote.check_index_age() is False

0 commit comments

Comments
 (0)