158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
|
|
META_ROOT = Path(__file__).resolve().parents[1]
|
|
RELEASE_TOOLS_ROOT = META_ROOT / "tools" / "release"
|
|
if str(RELEASE_TOOLS_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(RELEASE_TOOLS_ROOT))
|
|
|
|
from govoplan_release.module_directory import ( # noqa: E402
|
|
module_directory_payloads,
|
|
safe_path_part,
|
|
write_module_directory,
|
|
)
|
|
|
|
|
|
class ReleaseModuleDirectoryTests(unittest.TestCase):
|
|
def test_signed_catalog_timestamp_makes_derived_files_reproducible(self) -> None:
|
|
catalog = {
|
|
"generated_at": "2026-07-22T12:00:00Z",
|
|
"sequence": 7,
|
|
"modules": [],
|
|
}
|
|
first = module_directory_payloads(
|
|
catalog_payload=catalog,
|
|
keyring_payload={},
|
|
channel="stable",
|
|
)
|
|
second = module_directory_payloads(
|
|
catalog_payload=catalog,
|
|
keyring_payload={},
|
|
channel="stable",
|
|
)
|
|
|
|
self.assertEqual(first, second)
|
|
self.assertEqual(
|
|
"2026-07-22T12:00:00Z",
|
|
first[-1][1]["generated_at"],
|
|
)
|
|
|
|
def test_dot_segments_and_noncanonical_ids_never_become_paths(self) -> None:
|
|
for value in (".", "..", "../escape", "/absolute", "module/child"):
|
|
with self.subTest(value=value), self.assertRaises(ValueError):
|
|
safe_path_part(value)
|
|
|
|
for module_id in ("..", "../escape", "UPPER", "bad.id"):
|
|
with self.subTest(module_id=module_id), mock.patch(
|
|
"govoplan_release.module_directory.module_rows",
|
|
return_value=[
|
|
{
|
|
"module_id": module_id,
|
|
"version": "1.2.3",
|
|
}
|
|
],
|
|
):
|
|
with self.assertRaises(ValueError):
|
|
module_directory_payloads(
|
|
catalog_payload={},
|
|
keyring_payload={},
|
|
channel="stable",
|
|
)
|
|
|
|
def test_noncanonical_version_and_channel_fail_before_paths(self) -> None:
|
|
with mock.patch(
|
|
"govoplan_release.module_directory.module_rows",
|
|
return_value=[{"module_id": "demo", "version": "../1.2.3"}],
|
|
):
|
|
with self.assertRaises(ValueError):
|
|
module_directory_payloads(
|
|
catalog_payload={}, keyring_payload={}, channel="stable"
|
|
)
|
|
with self.assertRaises(ValueError):
|
|
module_directory_payloads(
|
|
catalog_payload={}, keyring_payload={}, channel="../stable"
|
|
)
|
|
|
|
def test_prune_removes_stale_json_but_keeps_unrelated_assets(self) -> None:
|
|
catalog = {
|
|
"generated_at": "2026-08-06T12:00:00Z",
|
|
"sequence": 8,
|
|
"modules": [
|
|
{
|
|
"module_id": "files",
|
|
"name": "Files",
|
|
"version": "1.2.3",
|
|
"python_package": "govoplan-files",
|
|
"source": {
|
|
"repository": "govoplan-files",
|
|
"tag": "v1.2.3",
|
|
"commit": "a" * 40,
|
|
},
|
|
"artifact_integrity": {
|
|
"python": {"sha256": "b" * 64},
|
|
},
|
|
}
|
|
],
|
|
}
|
|
with tempfile.TemporaryDirectory() as value:
|
|
output_root = Path(value)
|
|
stale = output_root / "modules" / "legacy" / "0.1.0" / "manifest.json"
|
|
stale.parent.mkdir(parents=True)
|
|
stale.write_text("{}\n", encoding="utf-8")
|
|
unrelated = output_root / "modules" / "README.txt"
|
|
unrelated.write_text("keep\n", encoding="utf-8")
|
|
|
|
written = write_module_directory(
|
|
catalog_payload=catalog,
|
|
keyring_payload={},
|
|
output_root=output_root,
|
|
channel="stable",
|
|
prune=True,
|
|
)
|
|
|
|
self.assertFalse(stale.exists())
|
|
self.assertTrue(unrelated.exists())
|
|
self.assertEqual(3, len(written))
|
|
manifest = json.loads(
|
|
(output_root / "modules" / "files" / "1.2.3" / "manifest.json").read_text()
|
|
)
|
|
self.assertEqual("govoplan-files", manifest["module"]["repo"])
|
|
self.assertEqual("v1.2.3", manifest["module"]["python_tag"])
|
|
self.assertEqual("b" * 64, manifest["module"]["artifact_integrity"]["python"]["sha256"])
|
|
|
|
def test_writer_refuses_nested_symlink_targets(self) -> None:
|
|
catalog = {
|
|
"modules": [{"module_id": "files", "version": "1.2.3"}],
|
|
}
|
|
with tempfile.TemporaryDirectory() as value:
|
|
root = Path(value)
|
|
output_root = root / "public"
|
|
external = root / "external"
|
|
(output_root / "modules").mkdir(parents=True)
|
|
external.mkdir()
|
|
(output_root / "modules" / "files").symlink_to(
|
|
external,
|
|
target_is_directory=True,
|
|
)
|
|
|
|
with self.assertRaisesRegex(ValueError, "symlinks"):
|
|
write_module_directory(
|
|
catalog_payload=catalog,
|
|
keyring_payload={},
|
|
output_root=output_root,
|
|
channel="stable",
|
|
)
|
|
|
|
self.assertEqual([], list(external.iterdir()))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|