from __future__ import annotations import importlib.util import json from pathlib import Path import sys import tempfile import unittest import zipfile SCRIPT = ( Path(__file__).resolve().parents[1] / "tools" / "release" / "prepare-runtime-context.py" ) SPEC = importlib.util.spec_from_file_location("prepare_runtime_context", SCRIPT) assert SPEC is not None and SPEC.loader is not None MODULE = importlib.util.module_from_spec(SPEC) sys.modules[SPEC.name] = MODULE SPEC.loader.exec_module(MODULE) class RuntimeImageContextTests(unittest.TestCase): def test_builds_deterministic_network_free_context(self) -> None: with tempfile.TemporaryDirectory(prefix="govoplan-runtime-context-") as value: root = Path(value) wheelhouse = root / "input-wheels" web = root / "web" wheelhouse.mkdir() web.mkdir() self._wheel( wheelhouse / "govoplan_core-1.2.3-py3-none-any.whl", package="govoplan-core", version="1.2.3", module_ids=(), ) self._wheel( wheelhouse / "govoplan_files-1.2.3-py3-none-any.whl", package="govoplan-files", version="1.2.3", module_ids=("files",), ) self._wheel( wheelhouse / "sqlalchemy-2.0.0-py3-none-any.whl", package="SQLAlchemy", version="2.0.0", module_ids=(), ) (web / "index.html").write_text("
GovOPlaN
\n", encoding="utf-8") composition = MODULE.prepare_context( wheelhouse=wheelhouse, web_dist=web, output=root / "context", required_modules=("files",), source_date_epoch=1_700_000_000, ) self.assertEqual(["files"], composition["python"]["module_ids"]) self.assertEqual(2, composition["python"]["wheel_count"]) requirements = (root / "context" / "requirements-runtime.txt").read_text() self.assertEqual( "govoplan-core[server]==1.2.3\ngovoplan-files==1.2.3\n", requirements, ) published = json.loads( ( root / "context" / "web-dist" / ".well-known" / "govoplan-composition.json" ).read_text() ) self.assertEqual(composition, published) def test_rejects_missing_required_module(self) -> None: with tempfile.TemporaryDirectory(prefix="govoplan-runtime-context-") as value: root = Path(value) wheelhouse = root / "wheels" web = root / "web" wheelhouse.mkdir() web.mkdir() self._wheel( wheelhouse / "govoplan_core-1.0.0-py3-none-any.whl", package="govoplan-core", version="1.0.0", module_ids=(), ) (web / "index.html").write_text("ok", encoding="utf-8") with self.assertRaisesRegex(MODULE.ContextError, "missing required"): MODULE.prepare_context( wheelhouse=wheelhouse, web_dist=web, output=root / "context", required_modules=("mail",), ) def test_rejects_symlinked_web_payload(self) -> None: with tempfile.TemporaryDirectory(prefix="govoplan-runtime-context-") as value: root = Path(value) wheelhouse = root / "wheels" web = root / "web" wheelhouse.mkdir() web.mkdir() self._wheel( wheelhouse / "govoplan_core-1.0.0-py3-none-any.whl", package="govoplan-core", version="1.0.0", module_ids=(), ) outside = root / "outside" outside.write_text("not part of dist", encoding="utf-8") (web / "index.html").symlink_to(outside) with self.assertRaisesRegex(MODULE.ContextError, "symlink"): MODULE.prepare_context( wheelhouse=wheelhouse, web_dist=web, output=root / "context", ) @staticmethod def _wheel( path: Path, *, package: str, version: str, module_ids: tuple[str, ...], ) -> None: dist_info = package.replace("-", "_") + f"-{version}.dist-info" with zipfile.ZipFile(path, "w") as archive: archive.writestr( f"{dist_info}/METADATA", f"Metadata-Version: 2.1\nName: {package}\nVersion: {version}\n", ) if module_ids: rows = "\n".join( f"{module_id} = example.module:manifest" for module_id in module_ids ) archive.writestr( f"{dist_info}/entry_points.txt", f"[govoplan.modules]\n{rows}\n", ) archive.writestr(f"{package.replace('-', '_')}/__init__.py", "") if __name__ == "__main__": unittest.main()