#!/usr/bin/env python3 import hashlib import json import os import subprocess import tarfile import tempfile import unittest from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent BUILDER = SCRIPT_DIR / "build-bim-tasker-cad-ops-artifacts.mjs" EXPECTED = { "bim-viewer": { "files": [ "converter/worker.py", "frontend/dcViewer.js", "server/cad-formats.js", "server/index.js", "server/tasker-gateway-auth.js", ], "manifest": ( "id=bim-viewer-cad-ops-20990101-001\n" "component=bim-viewer\n" "type=app-overlay\n" ), }, "tasker": { "files": [ "plane-src/apps/api/plane/app/urls/issue.py", "plane-src/apps/api/plane/app/views/__init__.py", "plane-src/apps/api/plane/app/views/issue/attachment.py", "plane-src/apps/api/plane/app/views/issue/bim_attachment.py", "plane-src/apps/api/plane/utils/nodedc_bim_gateway.py", "plane-src/apps/web/core/components/issues/attachment/attachment-list-item.tsx", "plane-src/apps/web/core/services/issue/issue_attachment.service.ts", "plane-src/apps/web/helpers/beam-viewer-config.ts", "plane-src/apps/web/helpers/beam-viewer.ts", ], "manifest": ( "id=tasker-cad-ops-20990101-001\n" "component=tasker\n" "type=app-overlay\n" ), }, } class BimTaskerCadOpsArtifactsTest(unittest.TestCase): def build(self, artifact_dir: Path) -> dict: environment = os.environ.copy() environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir) result = subprocess.run( ["node", str(BUILDER), "20990101-001"], check=True, capture_output=True, text=True, env=environment, ) return json.loads(result.stdout) def test_artifacts_are_exact_deterministic_and_runtime_only(self) -> None: with ( tempfile.TemporaryDirectory(prefix="nodedc-cad-ops-first-") as first_dir, tempfile.TemporaryDirectory(prefix="nodedc-cad-ops-second-") as second_dir, ): first = self.build(Path(first_dir)) second = self.build(Path(second_dir)) first_by_component = {item["component"]: item for item in first["artifacts"]} second_by_component = {item["component"]: item for item in second["artifacts"]} self.assertEqual(set(first_by_component), set(EXPECTED)) self.assertEqual(set(second_by_component), set(EXPECTED)) for component, contract in EXPECTED.items(): first_item = first_by_component[component] second_item = second_by_component[component] first_bytes = Path(first_item["artifact"]).read_bytes() second_bytes = Path(second_item["artifact"]).read_bytes() self.assertEqual(first_bytes, second_bytes) self.assertEqual(first_item["sha256"], hashlib.sha256(first_bytes).hexdigest()) self.assertEqual(first_item["files"], contract["files"]) self.assertRegex(first_item["sourceCommit"], r"^[0-9a-f]{40}$") with tarfile.open(first_item["artifact"], "r:gz") as archive: members = archive.getmembers() names = [member.name for member in members] self.assertEqual( archive.extractfile("manifest.env").read().decode("utf-8"), contract["manifest"], ) self.assertEqual( archive.extractfile("files.txt").read().decode("utf-8").splitlines(), contract["files"], ) self.assertEqual(names[:3], ["manifest.env", "files.txt", "payload"]) self.assertTrue(all(member.isfile() or member.isdir() for member in members)) self.assertFalse(any(member.issym() or member.islnk() for member in members)) self.assertFalse(any("test" in name.lower() for name in names)) self.assertFalse( any( name.startswith("payload/") and ("/.env" in name or name.endswith(".env")) for name in names ) ) self.assertFalse(any(name.startswith("._") or "/._" in name for name in names)) if __name__ == "__main__": unittest.main()