"""Renderings shared by the site pages, and the icon cache they read. No network: icon checks are answered by a stub. """ from __future__ import annotations import json import sys import tempfile import unittest import urllib.error from pathlib import Path from unittest import mock REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) import siterender # noqa: E402 class _Served: status = 200 def __enter__(self) -> "_Served": return self def __exit__(self, *exc: object) -> None: return None class IconCacheIsReplacedWhole(unittest.TestCase): """The cache was truncated then dumped in place: a concurrent reader saw half a file, took it for empty, and an offline run then wrote ``{}`` over every recorded icon.""" def setUp(self) -> None: tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) self.path = Path(tmp.name) / "system_icons.json" patcher = mock.patch.object(siterender, "ICON_CACHE_PATH", self.path) patcher.start() self.addCleanup(patcher.stop) saved = dict(siterender._icon_available) self.addCleanup(lambda: (siterender._icon_available.clear(), siterender._icon_available.update(saved))) def test_an_offline_run_leaves_an_unreadable_cache_untouched(self): self.path.write_text('{"Sega - Mega Drive": tr') offline = mock.patch.object( siterender.urllib.request, "urlopen", side_effect=urllib.error.URLError("offline")) with offline, mock.patch("sys.stderr"), mock.patch("builtins.print"): siterender.prime_system_icons({"Sega - Mega Drive"}) self.assertEqual(self.path.read_text(), '{"Sega - Mega Drive": tr') def test_names_another_run_recorded_meanwhile_are_kept(self): self.path.write_text(json.dumps({"A - One": True})) def answer(*_args: object, **_kwargs: object) -> _Served: # A second build records its own check while this one runs. self.path.write_text(json.dumps({"A - One": True, "C - Three": False})) return _Served() with mock.patch.object(siterender.urllib.request, "urlopen", side_effect=answer), \ mock.patch("builtins.print"): siterender.prime_system_icons({"A - One", "B - Two"}) self.assertEqual(json.loads(self.path.read_text()), {"A - One": True, "B - Two": True, "C - Three": False}) self.assertEqual(sorted(p.name for p in self.path.parent.iterdir()), ["system_icons.json"]) def test_the_write_goes_through_a_scratch_file(self): with mock.patch.object(siterender.urllib.request, "urlopen", return_value=_Served()), \ mock.patch.object(siterender, "write_text_atomic", wraps=siterender.write_text_atomic) as atomic, \ mock.patch("builtins.print"): siterender.prime_system_icons({"B - Two"}) atomic.assert_called_once() if __name__ == "__main__": unittest.main()