policy-nexus/tests/test_release.py
tegwick 03a4fab9e0
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 2s
Automate policy source freshness and inventory
2026-08-18 13:25:49 +02:00

137 lines
5.3 KiB
Python

from __future__ import annotations
import hashlib
import json
from pathlib import Path
import sys
import tempfile
import unittest
ROOT = Path(__file__).parents[1]
sys.path.insert(0, str(ROOT / "tools"))
from verify_release import verify
COMMIT = "1" * 40
SOURCE_DIGEST = "2" * 64
def _page(revision: str = COMMIT, digest: str = SOURCE_DIGEST) -> str:
return (
'<!doctype html><meta name="policy-source-revision" '
f'content="{revision}"><meta name="policy-source-digest" content="{digest}">'
)
def _release(root: Path) -> Path:
build = root / "build"
canonical = build / "standards/example/v1/index.html"
revision = build / "standards/example/v1/revisions/draft-1/index.html"
canonical.parent.mkdir(parents=True)
revision.parent.mkdir(parents=True)
canonical.write_text(_page(), encoding="utf-8")
revision.write_text(_page(), encoding="utf-8")
(build / "index.html").write_text("<!doctype html>", encoding="utf-8")
manifest = {
"schema_version": 1,
"generated_as_of": "2026-08-18",
"documents": [
{
"id": "example",
"title": "Example Standard",
"status": "proposed",
"revision": "draft-1",
"owner": "example-owner",
"last_reviewed": "2026-08-18",
"review_due": "2027-02-18",
"canonical_path": "standards/example/v1/index.html",
"revision_path": "standards/example/v1/revisions/draft-1/index.html",
"source_repo": "canon",
"source_path": "canon/standards/example.md",
"source_revision": COMMIT,
"source_digest": SOURCE_DIGEST,
}
],
}
(build / "publication-manifest.json").write_text(
json.dumps(manifest, sort_keys=True) + "\n", encoding="utf-8"
)
(build / "source-inventory.json").write_text(
json.dumps(
{
"schema_version": "policy-nexus-source-inventory/v1",
"source_set_digest": "3" * 64,
"repositories": [
{"name": "canon", "revision": COMMIT, "source_count": 1}
],
"sources": [
{
"source_repo": "canon",
"source_path": "canon/standards/example.md",
"disposition": "published",
"reason": "test",
}
],
},
sort_keys=True,
)
+ "\n",
encoding="utf-8",
)
return build
class ReleaseVerificationTest(unittest.TestCase):
def test_release_pipeline_does_not_delete_immutable_revision_tree(self) -> None:
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
containerfile = (ROOT / "Containerfile").read_text(encoding="utf-8")
self.assertIn("release-build: build release-check", makefile)
self.assertNotIn("RUN rm -rf build", containerfile)
def test_accepts_clean_provenance_and_returns_manifest_digest(self) -> None:
with tempfile.TemporaryDirectory() as directory:
build = _release(Path(directory))
evidence = verify(build)
expected = hashlib.sha256(
(build / "publication-manifest.json").read_bytes()
).hexdigest()
self.assertEqual(expected, evidence["publication_manifest_digest"])
self.assertEqual(["example"], evidence["documents"])
self.assertEqual("3" * 64, evidence["source_set_digest"])
def test_rejects_working_tree_source_revision(self) -> None:
with tempfile.TemporaryDirectory() as directory:
build = _release(Path(directory))
manifest_path = build / "publication-manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
dirty = COMMIT + "+working-tree." + SOURCE_DIGEST[:12]
manifest["documents"][0]["source_revision"] = dirty
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
canonical = build / manifest["documents"][0]["canonical_path"]
canonical.write_text(_page(revision=dirty), encoding="utf-8")
with self.assertRaisesRegex(ValueError, "clean 40-hex Git commit"):
verify(build)
def test_rejects_immutable_revision_built_from_dirty_source(self) -> None:
with tempfile.TemporaryDirectory() as directory:
build = _release(Path(directory))
revision = build / "standards/example/v1/revisions/draft-1/index.html"
revision.write_text(
_page(revision=COMMIT + "+working-tree." + SOURCE_DIGEST[:12]),
encoding="utf-8",
)
with self.assertRaisesRegex(ValueError, "immutable revision page"):
verify(build)
def test_rejects_page_digest_mismatch(self) -> None:
with tempfile.TemporaryDirectory() as directory:
build = _release(Path(directory))
canonical = build / "standards/example/v1/index.html"
canonical.write_text(_page(digest="3" * 64), encoding="utf-8")
with self.assertRaisesRegex(ValueError, "provenance differs"):
verify(build)
if __name__ == "__main__":
unittest.main()