229 lines
8.7 KiB
Python
229 lines
8.7 KiB
Python
from __future__ import annotations
|
|
|
|
from io import BytesIO
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
import tomllib
|
|
import unittest
|
|
import zipfile
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _load(name: str, path: Path):
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
PACKAGE_SET = _load(
|
|
"generate_release_package_set",
|
|
ROOT / "tools/release/generate-release-package-set.py",
|
|
)
|
|
ARTIFACTS = _load(
|
|
"resolve_package_artifacts",
|
|
ROOT / "tools/release/resolve-package-artifacts.py",
|
|
)
|
|
|
|
|
|
class PackageRegistryReleaseTests(unittest.TestCase):
|
|
def test_current_release_sources_form_a_hash_bound_package_set(self) -> None:
|
|
core_version = tomllib.loads(
|
|
(ROOT.parent / "govoplan-core/pyproject.toml").read_text(encoding="utf-8")
|
|
)["project"]["version"]
|
|
payload = PACKAGE_SET.generate_package_set(
|
|
core_version=core_version,
|
|
requirements=ROOT / "requirements-release.txt",
|
|
workspace=ROOT.parent,
|
|
)
|
|
|
|
self.assertEqual("1", payload["schema_version"])
|
|
self.assertEqual("base", payload["profile"])
|
|
self.assertEqual("govoplan-core", payload["python"][0]["name"])
|
|
self.assertIn(
|
|
"@govoplan/core-webui",
|
|
{item["name"] for item in payload["webui"]},
|
|
)
|
|
unsigned = dict(payload)
|
|
digest = unsigned.pop("package_set_sha256")
|
|
self.assertEqual(ARTIFACTS._canonical_sha256(unsigned), digest)
|
|
|
|
def test_full_profile_is_derived_from_the_developer_meta_package(self) -> None:
|
|
core_version = tomllib.loads(
|
|
(ROOT.parent / "govoplan-core/pyproject.toml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
)["project"]["version"]
|
|
selected = PACKAGE_SET.parse_meta_package(
|
|
ROOT / "packages/govoplan-meta/pyproject.toml",
|
|
core_version=core_version,
|
|
)
|
|
|
|
by_name = {item["name"]: item for item in selected}
|
|
self.assertIn("govoplan-core", by_name)
|
|
self.assertIn("govoplan-records", by_name)
|
|
self.assertEqual("0.1.21", by_name["govoplan-tasks"]["version"])
|
|
|
|
payload = PACKAGE_SET.generate_package_set(
|
|
core_version=core_version,
|
|
requirements=ROOT / "requirements-release.txt",
|
|
workspace=ROOT.parent,
|
|
profile="full",
|
|
meta_package=ROOT / "packages/govoplan-meta/pyproject.toml",
|
|
)
|
|
self.assertEqual("full", payload["profile"])
|
|
self.assertEqual(len(selected), len(payload["python"]))
|
|
self.assertIn(
|
|
"@govoplan/records-webui",
|
|
{item["name"] for item in payload["webui"]},
|
|
)
|
|
|
|
def test_python_registry_artifact_url_is_immutable_and_credential_free(self) -> None:
|
|
url = ARTIFACTS._python_artifact_url(
|
|
"https://git.add-ideas.de/api/packages/GovOPlaN/pypi/simple",
|
|
package={"name": "govoplan-files", "version": "0.1.18"},
|
|
filename="govoplan_files-0.1.18-py3-none-any.whl",
|
|
)
|
|
|
|
self.assertEqual(
|
|
"https://git.add-ideas.de/api/packages/GovOPlaN/pypi/files/govoplan-files/0.1.18/govoplan_files-0.1.18-py3-none-any.whl",
|
|
url,
|
|
)
|
|
|
|
def test_wheel_and_webui_artifacts_are_verified_by_embedded_identity(self) -> None:
|
|
with tempfile.TemporaryDirectory(prefix="govoplan-package-artifacts-") as value:
|
|
root = Path(value)
|
|
wheels = root / "wheels"
|
|
webui = root / "webui"
|
|
wheels.mkdir()
|
|
webui.mkdir()
|
|
wheel = wheels / "govoplan_demo-1.2.3-py3-none-any.whl"
|
|
with zipfile.ZipFile(wheel, "w") as archive:
|
|
archive.writestr(
|
|
"govoplan_demo-1.2.3.dist-info/METADATA",
|
|
"Metadata-Version: 2.1\nName: govoplan-demo\nVersion: 1.2.3\n",
|
|
)
|
|
package_json = json.dumps(
|
|
{"name": "@govoplan/demo-webui", "version": "1.2.3"}
|
|
).encode("utf-8")
|
|
npm = webui / "govoplan-demo-webui-1.2.3.tgz"
|
|
with tarfile.open(npm, "w:gz") as archive:
|
|
member = tarfile.TarInfo("package/package.json")
|
|
member.size = len(package_json)
|
|
archive.addfile(member, BytesIO(package_json))
|
|
source = {
|
|
"version": "1.2.3",
|
|
"repository": "govoplan-demo",
|
|
"tag": "v1.2.3",
|
|
"commit": "1" * 40,
|
|
}
|
|
|
|
python_rows = ARTIFACTS._verify_wheels(
|
|
({"name": "govoplan-demo", "extras": ["server"], **source},), wheels
|
|
)
|
|
webui_rows = ARTIFACTS._verify_webui(
|
|
({"name": "@govoplan/demo-webui", **source},), webui
|
|
)
|
|
|
|
self.assertEqual("govoplan-demo", python_rows[0]["name"])
|
|
self.assertEqual(["server"], python_rows[0]["extras"])
|
|
self.assertEqual("@govoplan/demo-webui", webui_rows[0]["name"])
|
|
self.assertTrue(str(webui_rows[0]["integrity"]).startswith("sha512-"))
|
|
|
|
def test_package_set_rejects_argument_shaped_package_names(self) -> None:
|
|
payload = {
|
|
"schema_version": "1",
|
|
"release_version": "1.2.3",
|
|
"registries": {
|
|
"python": "https://packages.example.test/pypi/simple",
|
|
"npm": "https://packages.example.test/npm/",
|
|
},
|
|
"python": [
|
|
{
|
|
"name": "--index-url",
|
|
"version": "1.2.3",
|
|
"repository": "govoplan-demo",
|
|
"extras": [],
|
|
"tag": "v1.2.3",
|
|
"commit": "1" * 40,
|
|
}
|
|
],
|
|
"webui": [
|
|
{
|
|
"name": "@govoplan/demo-webui",
|
|
"version": "1.2.3",
|
|
"repository": "govoplan-demo",
|
|
"tag": "v1.2.3",
|
|
"commit": "1" * 40,
|
|
}
|
|
],
|
|
}
|
|
payload["package_set_sha256"] = ARTIFACTS._canonical_sha256(payload)
|
|
with tempfile.TemporaryDirectory() as value:
|
|
path = Path(value) / "packages.json"
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
with self.assertRaisesRegex(
|
|
ARTIFACTS.PackageArtifactError, "invalid identity"
|
|
):
|
|
ARTIFACTS._load_package_set(path)
|
|
|
|
def test_runtime_workflow_consumes_registry_artifacts_and_publishes_lock(self) -> None:
|
|
workflow = (ROOT / ".gitea/workflows/runtime-distribution.yml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
self.assertIn("resolve-package-artifacts.py", workflow)
|
|
self.assertIn("--profile full", workflow)
|
|
self.assertIn('git show "v$VERSION:requirements-release.txt"', workflow)
|
|
self.assertIn('git show "v$VERSION:packages/govoplan-meta/pyproject.toml"', workflow)
|
|
self.assertIn("--meta-package runtime-output/govoplan-meta.source.toml", workflow)
|
|
self.assertIn("GOVOPLAN_WEBUI_INSTALL_ALL_PACKAGES=true", workflow)
|
|
self.assertIn("package-artifacts.lock.json", workflow)
|
|
self.assertIn(
|
|
"--package-lock runtime-output/package-artifacts.lock.json",
|
|
workflow,
|
|
)
|
|
self.assertIn('PYTHON="$PWD/.runtime-build/bin/python"', workflow)
|
|
self.assertNotIn(
|
|
"pip wheel --no-deps --wheel-dir runtime-output/local-wheels",
|
|
workflow,
|
|
)
|
|
|
|
def test_developer_meta_package_matches_workspace_versions(self) -> None:
|
|
script = _load(
|
|
"generate_developer_meta_package",
|
|
ROOT / "tools/release/generate-developer-meta-package.py",
|
|
)
|
|
expected = script.render(
|
|
workspace=ROOT.parent,
|
|
requirements=ROOT / "requirements-release.txt",
|
|
)
|
|
actual = (ROOT / "packages/govoplan-meta/pyproject.toml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
self.assertEqual(expected, actual)
|
|
|
|
def test_meta_package_workflow_supports_hash_safe_tag_retry(self) -> None:
|
|
workflow = (
|
|
ROOT / ".gitea/workflows/publish-developer-meta-package.yml"
|
|
).read_text(encoding="utf-8")
|
|
|
|
self.assertIn("workflow_dispatch:", workflow)
|
|
self.assertIn("TRIGGER_TAG: ${{ gitea.ref_name }}", workflow)
|
|
self.assertNotIn("GITEA_REF_NAME", workflow)
|
|
self.assertIn('refs/tags/{tag}^{{commit}}', workflow)
|
|
self.assertIn("already exists with a different SHA-256", workflow)
|
|
self.assertIn("PUBLISH_PYPI", workflow)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|