102 lines
5.0 KiB
Python
102 lines
5.0 KiB
Python
"""Offline artifact/provenance tests independent of generated repository output."""
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
|
|
SPEC = importlib.util.spec_from_file_location("package_release", Path(__file__).with_name("package_release.py"))
|
|
PACKER = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(PACKER)
|
|
|
|
|
|
class PackagingTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.tag = "v28.0.0"
|
|
self.commit = "a" * 40
|
|
theme = json.dumps({"name": "demo", "mode": "shared"}).encode()
|
|
css = b"a { color:blue; }"
|
|
self.write("themes/demo/theme.json", theme)
|
|
self.write("themes/demo/theme.css", css)
|
|
files = {"mail/base/head.tmpl": b"<title>test</title>", "mail/base/footer.tmpl": b"<p>footer</p>"}
|
|
meta = {"theme": "demo", "mode": "shared", "gitea_tag": self.tag, "gitea_commit": self.commit, "sources": {"theme.json": PACKER.digest(theme), "theme.css": PACKER.digest(css)}, "files": {p: PACKER.digest(data) for p, data in files.items()}}
|
|
for path, data in files.items():
|
|
self.write("build/themes/demo/" + path, data)
|
|
self.write_json("build/themes/demo/build.json", meta)
|
|
self.write_json("gitea.lock.json", {"tag": self.tag, "commit": self.commit, "files": {"LICENSE": PACKER.digest(b"MIT"), "options/locale/locale_en-US.json": PACKER.digest(b"{}")}})
|
|
self.write("build/upstream/LICENSE", b"MIT")
|
|
manifest = {"upstream": self.tag, "upstream_commit": self.commit, "themes": ["demo"], "languages": ["en-US"], "registry": {"activate": {}}}
|
|
self.write("preview/rendered.js", ("window.__MAIL_PREVIEW__ = " + json.dumps(manifest) + ";").encode())
|
|
self.write("preview/rendered/en-US.js", ('window.__MAIL_PREVIEW_DATA__["en-US"] = ' + json.dumps({"demo": {"activate": "<html></html>"}}) + ";").encode())
|
|
for name in ["LICENSE", "AGENTS.md", "README.md", "CONTRIBUTING.md", "COMPATIBILITY.md", "THIRD_PARTY_NOTICES.md", "docs/README.zh-CN.md", "docs/CONTRIBUTING.zh-CN.md", "preview/index.html", "docs/images/README.md"]:
|
|
self.write(name, b"test")
|
|
|
|
def write(self, name, data):
|
|
path = self.root / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_bytes(data)
|
|
|
|
def write_json(self, name, data):
|
|
self.write(name, json.dumps(data).encode())
|
|
|
|
def test_packages_declared_outputs_only_and_never_overwrites(self):
|
|
self.write("build/themes/stale/mail/stale.tmpl", b"stale")
|
|
out = self.root / "artifacts"
|
|
PACKER.package(self.root, self.tag, out)
|
|
with zipfile.ZipFile(next(out.glob("*.zip"))) as archive:
|
|
names = archive.namelist()
|
|
self.assertTrue(any(name.endswith("themes/demo/mail/base/head.tmpl") for name in names))
|
|
self.assertFalse(any("stale" in name or "/build/" in name for name in names))
|
|
with self.assertRaises(ValueError):
|
|
PACKER.package(self.root, self.tag, out)
|
|
|
|
def test_refuses_stale_source_tampered_output_and_wrong_version(self):
|
|
with self.assertRaises(ValueError):
|
|
PACKER.collect(self.root, "v28.1.0")
|
|
self.write("themes/demo/theme.css", b"changed")
|
|
with self.assertRaisesRegex(ValueError, "Source changed"):
|
|
PACKER.collect(self.root, self.tag)
|
|
|
|
def test_refuses_missing_language_bundle(self):
|
|
(self.root / "preview/rendered/en-US.js").unlink()
|
|
with self.assertRaises(ValueError):
|
|
PACKER.collect(self.root, self.tag)
|
|
|
|
def test_refuses_modified_generated_files(self):
|
|
self.write("build/themes/demo/mail/base/head.tmpl", b"modified")
|
|
with self.assertRaisesRegex(ValueError, "checksum mismatch"):
|
|
PACKER.collect(self.root, self.tag)
|
|
|
|
def test_includes_nested_documentation_and_release_note_sources(self):
|
|
documents = {
|
|
"docs/INDEX.zh-CN.md": "中文索引".encode(),
|
|
"docs/images/README.zh-CN.md": "截图指南".encode(),
|
|
"docs/release-notes/v28.0.0.zh-CN.md": "发布说明".encode(),
|
|
".github/release-notes/v28.0.0.md": b"English release notes",
|
|
}
|
|
for name, data in documents.items():
|
|
self.write(name, data)
|
|
self.write("docs/private.tmp", b"not documentation")
|
|
files = PACKER.collect(self.root, self.tag)
|
|
for name, data in documents.items():
|
|
self.assertEqual(files[name], data)
|
|
self.assertIn("AGENTS.md", files)
|
|
self.assertNotIn("docs/private.tmp", files)
|
|
|
|
def test_packages_without_removed_optional_root_documents(self):
|
|
for name in ["AGENTS.md", "THIRD_PARTY_NOTICES.md"]:
|
|
(self.root / name).unlink()
|
|
files = PACKER.collect(self.root, self.tag)
|
|
self.assertNotIn("AGENTS.md", files)
|
|
self.assertNotIn("THIRD_PARTY_NOTICES.md", files)
|
|
self.assertIn("GITEA-LICENSE", files)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|