#!/usr/bin/env python3 import hashlib import importlib.machinery import importlib.util import json import os import subprocess import tempfile import unittest from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent PLATFORM_ROOT = SCRIPT_DIR.parent.parent BUILDER_PATH = SCRIPT_DIR / "build-tasker-ui-recovery-artifact.mjs" RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy" RELEASE = "20260808-001" SOURCE_COMMIT = "ca21a8f5bc48ce6f20b79cd99eb79aed79edc627" EXPECTED_FILES = { "plane-src/apps/web/ce/components/instance/maintenance-message.tsx", "plane-src/apps/web/core/components/dropdowns/cycle/cycle-options.tsx", "plane-src/apps/web/core/components/dropdowns/estimate.tsx", "plane-src/apps/web/core/components/dropdowns/module/module-options.tsx", "plane-src/apps/web/core/components/instance/maintenance-view.tsx", "plane-src/apps/web/core/components/issues/issue-layouts/properties/label-dropdown.tsx", "plane-src/apps/web/core/lib/wrappers/instance-wrapper.tsx", } def load_runner(): loader = importlib.machinery.SourceFileLoader("nodedc_tasker_ui_recovery_runner", str(RUNNER_PATH)) spec = importlib.util.spec_from_loader(loader.name, loader) module = importlib.util.module_from_spec(spec) loader.exec_module(module) return module RUNNER = load_runner() class TaskerUiRecoveryArtifactTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.temporary = tempfile.TemporaryDirectory(prefix="nodedc-tasker-ui-recovery-") cls.root = Path(cls.temporary.name) cls.builds = [] for index in range(2): output = cls.root / f"build-{index}" output.mkdir() env = os.environ.copy() env["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(output) result = subprocess.run( ["node", str(BUILDER_PATH), RELEASE], cwd=PLATFORM_ROOT, env=env, check=True, capture_output=True, text=True, ) cls.builds.append(json.loads(result.stdout)) @classmethod def tearDownClass(cls): cls.temporary.cleanup() def test_artifact_is_reproducible_and_runner_accepted(self): first = self.builds[0] second = self.builds[1] first_path = Path(first["artifact"]) second_path = Path(second["artifact"]) first_bytes = first_path.read_bytes() self.assertEqual(first_bytes, second_path.read_bytes()) self.assertEqual(first_bytes[4:8], bytes(4)) self.assertEqual(first["sha256"], hashlib.sha256(first_bytes).hexdigest()) self.assertEqual(first["sourceCommit"], SOURCE_COMMIT) self.assertEqual(first["patchId"], f"tasker-ui-recovery-{RELEASE}") self.assertEqual(set(first["files"]), EXPECTED_FILES) with tempfile.TemporaryDirectory() as directory: manifest, entries, payload = RUNNER.load_artifact(first_path, Path(directory)) self.assertEqual(manifest["component"], "tasker") self.assertEqual(manifest["id"], first["patchId"]) self.assertEqual(entries, first["files"]) for relative_path in entries: self.assertTrue((payload / relative_path).is_file()) def test_builder_rejects_an_unexpected_release(self): env = os.environ.copy() env["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(self.root / "unexpected-release") result = subprocess.run( ["node", str(BUILDER_PATH), "20260808-002"], cwd=PLATFORM_ROOT, env=env, capture_output=True, text=True, ) self.assertNotEqual(result.returncode, 0) self.assertIn("usage: build-tasker-ui-recovery-artifact.mjs", result.stderr) if __name__ == "__main__": unittest.main(verbosity=2)