#!/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 from unittest import mock SCRIPT_DIR = Path(__file__).resolve().parent PLATFORM_ROOT = SCRIPT_DIR.parent.parent RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy" def load_runner(): loader = importlib.machinery.SourceFileLoader( "nodedc_device_manager_deploy_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 DeviceManagerControlPlaneArtifactsTest(unittest.TestCase): def build(self, script, patch_id, artifact_dir): environment = os.environ.copy() environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir) completed = subprocess.run( ["node", str(SCRIPT_DIR / script), patch_id], cwd=PLATFORM_ROOT, env=environment, check=True, capture_output=True, text=True, ) return json.loads(completed.stdout) def assert_deterministic_artifact(self, script, patch_id, expected_entries): with tempfile.TemporaryDirectory(prefix="nodedc-device-manager-artifact-") as directory: root = Path(directory) first = self.build(script, patch_id, root / "first") second = self.build(script, patch_id, root / "second") first_artifact = Path(first["artifact"]) second_artifact = Path(second["artifact"]) self.assertEqual(first_artifact.read_bytes(), second_artifact.read_bytes()) self.assertEqual( first["sha256"], hashlib.sha256(first_artifact.read_bytes()).hexdigest(), ) self.assertEqual(tuple(first["entries"]), tuple(expected_entries)) extracted = root / "extracted" extracted.mkdir() manifest, entries, payload = RUNNER.load_artifact(first_artifact, extracted) self.assertEqual(tuple(entries), tuple(expected_entries)) with tarfile.open(first_artifact, "r:gz") as archive: members = archive.getmembers() names = [member.name for member in members] bytes_joined = b"\n".join( archive.extractfile(member).read() for member in members if member.isfile() ) self.assertFalse(any(Path(name).name.startswith("._") for name in names)) self.assertFalse(any("/node_modules/" in name or "/.git/" in name for name in names)) self.assertNotIn(b"-----BEGIN PRIVATE KEY-----", bytes_joined) return manifest, entries, names, first def test_platform_hub_trust_artifact_is_exact_and_build_free(self): manifest, entries, _names, result = self.assert_deterministic_artifact( "build-platform-device-core-hub-trust-artifact.mjs", "platform-device-core-hub-trust-unit-001", RUNNER.PLATFORM_DEVICE_CORE_HUB_TRUST_ENTRIES, ) self.assertEqual(manifest["component"], "platform") self.assertEqual(RUNNER.component_services("platform", entries), ("launcher",)) self.assertEqual(RUNNER.component_builds("platform", entries), ()) self.assertEqual(result["services"], ["launcher"]) def test_launcher_session_artifact_is_exact(self): manifest, entries, _names, result = self.assert_deterministic_artifact( "build-launcher-device-core-artifact.mjs", "launcher-device-core-session-unit-001", RUNNER.LAUNCHER_DEVICE_CORE_SESSION_ENTRIES, ) self.assertEqual(manifest["component"], "launcher") self.assertEqual(RUNNER.component_services("launcher", entries), ("launcher",)) self.assertEqual(len(RUNNER.component_builds("launcher", entries)), 1) self.assertEqual(result["services"], ["launcher"]) def test_device_manager_artifact_selects_only_core_and_manager(self): manifest, entries, names, result = self.assert_deterministic_artifact( "build-device-manager-control-plane-artifact.mjs", "device-manager-control-plane-unit-001", RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES, ) self.assertEqual(manifest["component"], "device-plane") self.assertEqual( RUNNER.component_services("device-plane", entries), ("device-control-core", "device-manager"), ) builds = RUNNER.component_builds("device-plane", entries) self.assertEqual(len(builds), 2) self.assertIn(RUNNER.DEVICE_PLANE_CONTROL_CORE_IMAGE, builds[0][1]) self.assertIn(RUNNER.DEVICE_PLANE_MANAGER_IMAGE, builds[1][1]) self.assertIn("payload/services/device-manager/dist/index.html", names) self.assertIn( "payload/services/device-manager/server/device-manager-server.mjs", names, ) self.assertFalse(any(name.endswith(".test.mjs") for name in names)) self.assertEqual(result["services"], ["device-control-core", "device-manager"]) self.assertNotIn("device-postgres", result["services"]) self.assertIn("docker-compose.device-manager.yml", entries) self.assertNotIn("docker-compose.device-plane.yml", entries) checks = RUNNER.component_healthchecks("device-plane", entries, tuple(result["services"])) self.assertEqual(checks[0]["expected_json"]["managementApi"], "enabled") self.assertEqual(checks[0]["expected_json"]["discoveryIngest"], "enabled") def test_public_route_artifact_is_last_and_proxy_only(self): manifest, entries, _names, result = self.assert_deterministic_artifact( "build-platform-device-manager-route-artifact.mjs", "platform-device-manager-route-unit-001", RUNNER.PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_ENTRIES, ) self.assertEqual(manifest["component"], "platform") self.assertEqual(RUNNER.component_services("platform", entries), ("reverse-proxy",)) self.assertEqual(RUNNER.component_builds("platform", entries), ()) self.assertEqual(result["services"], ["reverse-proxy"]) def test_runner_creates_only_file_backed_runtime_secrets(self): with mock.patch.object(RUNNER, "ensure_platform_runtime_secret") as ensure: RUNNER.prepare_component_runtime( "platform", RUNNER.PLATFORM_DEVICE_CORE_HUB_TRUST_ENTRIES, ) self.assertEqual( [call.args[0] for call in ensure.call_args_list], [RUNNER.PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE], ) with mock.patch.object(RUNNER, "ensure_platform_runtime_secret") as ensure: RUNNER.prepare_component_runtime( "device-plane", RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES, ) self.assertEqual( [call.args[0] for call in ensure.call_args_list], [ RUNNER.DEVICE_PLANE_POSTGRES_PASSWORD_FILE, RUNNER.DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE, RUNNER.DEVICE_PLANE_IDENTIFIER_PEPPER_FILE, RUNNER.DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE, RUNNER.PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE, ], ) if __name__ == "__main__": unittest.main(verbosity=2)