import importlib.util import json from pathlib import Path import sys import tempfile import unittest MODULE_PATH = Path(__file__).resolve().parents[1] / "static_site.py" SPEC = importlib.util.spec_from_file_location("static_site_deploy", MODULE_PATH) assert SPEC and SPEC.loader static_site = importlib.util.module_from_spec(SPEC) sys.modules[SPEC.name] = static_site SPEC.loader.exec_module(static_site) DeployError = static_site.DeployError bunny_plan = static_site.bunny_plan collect_files = static_site.collect_files manifest_for = static_site.manifest_for parse_manifest = static_site.parse_manifest def write_site(root: Path) -> None: (root / "assets").mkdir() (root / "index.html").write_text("

Hello

") (root / "assets" / "app.js").write_text("console.log('hello')") class StaticSiteTests(unittest.TestCase): def test_collect_files_requires_index(self) -> None: with tempfile.TemporaryDirectory() as directory: with self.assertRaisesRegex(DeployError, "index.html"): collect_files(Path(directory)) def test_collect_files_rejects_symlinks(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) write_site(root) (root / "linked").symlink_to(root / "index.html") with self.assertRaisesRegex(DeployError, "symbolic links"): collect_files(root) def test_manifest_round_trip(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) write_site(root) files = collect_files(root) manifest = manifest_for(files) parsed = parse_manifest(json.dumps(manifest).encode()) self.assertEqual( parsed["index.html"]["sha256"], files["index.html"].sha256 ) def test_manifest_rejects_unsafe_remote_path(self) -> None: raw = b'{"version": 1, "files": {"../other-site/index.html": {}}}' with self.assertRaisesRegex(DeployError, "unsafe path"): parse_manifest(raw) def test_bunny_plan_uploads_assets_before_html_and_removes_stale(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) write_site(root) files = collect_files(root) remote = { "index.html": {"sha256": "old", "size": 1}, "assets/app.js": {"sha256": "old", "size": 1}, "old.css": {"sha256": "old", "size": 1}, } upload, delete = bunny_plan(files, remote) self.assertEqual(upload, ["assets/app.js", "index.html"]) self.assertEqual(delete, ["old.css"]) def test_bunny_plan_skips_unchanged_files(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) write_site(root) files = collect_files(root) remote = manifest_for(files)["files"] self.assertEqual(bunny_plan(files, remote), ([], [])) if __name__ == "__main__": unittest.main()