107 lines
4 KiB
Python
107 lines
4 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_revision": COMMIT,
|
||
|
|
"source_digest": SOURCE_DIGEST,
|
||
|
|
}
|
||
|
|
],
|
||
|
|
}
|
||
|
|
(build / "publication-manifest.json").write_text(
|
||
|
|
json.dumps(manifest, sort_keys=True) + "\n", encoding="utf-8"
|
||
|
|
)
|
||
|
|
return build
|
||
|
|
|
||
|
|
|
||
|
|
class ReleaseVerificationTest(unittest.TestCase):
|
||
|
|
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"])
|
||
|
|
|
||
|
|
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()
|