#!/usr/bin/env python3 import hashlib import importlib.machinery import importlib.util 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-device-plane-artifact.mjs" RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy" EXPECTED_ENTRIES = [ ".dockerignore", "package.json", "package-lock.json", "docker-compose.device-plane.yml", "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway", ] def load_runner(): loader = importlib.machinery.SourceFileLoader( "nodedc_device_plane_artifact_runner_under_test", 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 DevicePlaneArtifactTest(unittest.TestCase): def build(self, artifact_dir, patch_id): environment = os.environ.copy() environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir) result = subprocess.run( ["node", str(BUILDER), patch_id], check=True, capture_output=True, text=True, env=environment, ) return json.loads(result.stdout) def test_artifact_is_narrow_safe_and_deterministic(self): with tempfile.TemporaryDirectory( prefix="nodedc-device-plane-artifact-", ) as directory: artifact_dir = Path(directory) first = self.build( artifact_dir, "device-plane-foundation-unit-001", ) artifact = Path(first["artifact"]) first_bytes = artifact.read_bytes() second = self.build( artifact_dir, "device-plane-foundation-unit-001", ) second_bytes = Path(second["artifact"]).read_bytes() self.assertEqual(first["component"], "device-plane") self.assertEqual(first["entries"], EXPECTED_ENTRIES) self.assertEqual( first["services"], ["device-control-core", "device-gateway"], ) self.assertEqual( first["sha256"], hashlib.sha256(first_bytes).hexdigest(), ) self.assertEqual(first["sha256"], second["sha256"]) self.assertEqual(first_bytes, second_bytes) with tarfile.open(artifact, "r:gz") as archive: members = archive.getmembers() names = {member.name for member in members} files = ( archive.extractfile("files.txt") .read() .decode("utf-8") .splitlines() ) manifest = ( archive.extractfile("manifest.env") .read() .decode("utf-8") ) compose = ( archive.extractfile( "payload/docker-compose.device-plane.yml", ) .read() .decode("utf-8") ) regular_payloads = [ archive.extractfile(member).read() for member in members if member.isfile() ] self.assertEqual(files, EXPECTED_ENTRIES) self.assertEqual( manifest, "id=device-plane-foundation-unit-001\n" "component=device-plane\n" "type=app-overlay\n", ) self.assertIn( "payload/services/device-control-core/Dockerfile", names, ) self.assertIn( "payload/services/device-gateway/Dockerfile", names, ) self.assertFalse(any( "/test/" in name or "/node_modules/" in name or Path(name).name.startswith(".env") or name.startswith("payload/docs/") or name.startswith("payload/runtime/") or name.startswith("payload/secrets/") for name in names )) self.assertNotIn("9921:9921", compose) self.assertIn('DEVICE_GATEWAY_LISTEN_ENABLED: "false"', compose) self.assertIn('DEVICE_DISCOVERY_INGEST_ENABLED: "false"', compose) self.assertNotIn(b"-----BEGIN PRIVATE KEY-----", b"\n".join( regular_payloads, )) with tempfile.TemporaryDirectory( prefix="nodedc-device-plane-runner-load-", ) as work_directory: manifest_loaded, entries_loaded, payload_loaded = ( RUNNER.load_artifact(artifact, Path(work_directory)) ) self.assertEqual(manifest_loaded["component"], "device-plane") self.assertEqual(entries_loaded, EXPECTED_ENTRIES) self.assertEqual(payload_loaded.name, "payload") if __name__ == "__main__": unittest.main(verbosity=2)