#!/usr/bin/env python3 import hashlib import importlib.machinery import importlib.util import json import shutil import tarfile import tempfile import unittest from io import BytesIO from pathlib import Path from unittest import mock SCRIPT_DIR = Path(__file__).resolve().parent PLATFORM_ROOT = SCRIPT_DIR.parent.parent ENGINE_ROOT = PLATFORM_ROOT.parent / "NODEDC_ENGINE_INFRA" RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy" def load_runner(): loader = importlib.machinery.SourceFileLoader( "nodedc_engine_node_intelligence_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() def sha256(path): return hashlib.sha256(path.read_bytes()).hexdigest() class EngineNodeIntelligenceTest(unittest.TestCase): def make_image_archive(self, root, runtime_env=None): runtime_env = list(runtime_env or ["PATH=/usr/local/bin"]) config = { "architecture": "amd64", "os": "linux", "config": { "Labels": { "org.opencontainers.image.revision": RUNNER.ENGINE_NODE_INTELLIGENCE_UPSTREAM_COMMIT, }, "Entrypoint": ["/usr/local/bin/docker-entrypoint.sh"], "Cmd": ["node", "dist/mcp/index.js"], "Env": runtime_env, }, } config_raw = json.dumps(config, separators=(",", ":")).encode() config_sha = hashlib.sha256(config_raw).hexdigest() layer_raw = b"nodedc-node-intelligence-test-layer\n" * 64 layer_sha = hashlib.sha256(layer_raw).hexdigest() manifest = [{ "Config": f"blobs/sha256/{config_sha}", "RepoTags": [RUNNER.ENGINE_NODE_INTELLIGENCE_IMAGE], "Layers": [f"blobs/sha256/{layer_sha}"], }] archive = root / "engine-node-intelligence.tar" with tarfile.open(archive, "w") as output: for name, value in ( ("manifest.json", json.dumps(manifest, separators=(",", ":")).encode()), (f"blobs/sha256/{config_sha}", config_raw), (f"blobs/sha256/{layer_sha}", layer_raw), ): member = tarfile.TarInfo(name) member.size = len(value) member.mode = 0o644 output.addfile(member, BytesIO(value)) return archive, config_sha def make_activation_payload(self, root, runtime_env=None): payload = root / "payload" source = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_SOURCE_REL service = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL gateway = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_GATEWAY_REL source.mkdir(parents=True) service.joinpath("image").mkdir(parents=True) gateway.parent.mkdir(parents=True) for name in ("catalog.js", "upstreamMcpClient.js", "upstreamProjection.js"): shutil.copy2( ENGINE_ROOT / RUNNER.ENGINE_NODE_INTELLIGENCE_SOURCE_REL / name, source / name, ) shutil.copy2( ENGINE_ROOT / RUNNER.ENGINE_NODE_INTELLIGENCE_GATEWAY_REL, gateway, ) readme = service / "README.md" readme.write_text("# exact node intelligence test\n", encoding="utf-8") override = service / "docker-compose.immutable-runtime.yml" override.write_text( RUNNER.expected_engine_node_intelligence_compose_override(), encoding="utf-8", ) archive, config_sha = self.make_image_archive(root, runtime_env) target_archive = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL shutil.copy2(archive, target_archive) descriptor = { "schemaVersion": "nodedc.engine-node-intelligence-transition/v1", "action": "activate", "releaseId": RUNNER.ENGINE_NODE_INTELLIGENCE_RELEASE_ID, "expectedCurrent": "inactive", "upstream": { "package": "n8n-mcp", "version": "2.33.2", "commit": RUNNER.ENGINE_NODE_INTELLIGENCE_UPSTREAM_COMMIT, }, "image": { "tag": RUNNER.ENGINE_NODE_INTELLIGENCE_IMAGE, "archiveRelativePath": RUNNER.ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL, "archiveSha256": sha256(target_archive), "configSha256": config_sha, "architecture": "amd64", "os": "linux", }, "source": { "gatewaySha256": sha256(gateway), "catalogSha256": sha256(source / "catalog.js"), "upstreamClientSha256": sha256(source / "upstreamMcpClient.js"), "upstreamProjectionSha256": sha256(source / "upstreamProjection.js"), "composeOverrideSha256": sha256(override), "readmeSha256": sha256(readme), }, "predecessor": { "gatewaySha256": RUNNER.ENGINE_NODE_INTELLIGENCE_PREDECESSOR_GATEWAY_SHA256, "composeSha256": RUNNER.ENGINE_NODE_INTELLIGENCE_PREDECESSOR_COMPOSE_SHA256, "backendRuntime": "verified-derived-retry", }, } (payload / RUNNER.ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL).write_text( json.dumps(descriptor, indent=2) + "\n", encoding="utf-8", ) return payload, descriptor def test_activation_payload_and_offline_image_are_exact(self): with tempfile.TemporaryDirectory() as directory: payload, descriptor = self.make_activation_payload(Path(directory)) actual = RUNNER.validate_engine_node_intelligence_transition( payload, RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) self.assertEqual(actual, descriptor) def test_gateway_accepts_registered_0_10_and_rejects_unknown_successors(self): with tempfile.TemporaryDirectory() as directory: payload, descriptor = self.make_activation_payload(Path(directory)) gateway = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_GATEWAY_REL gateway_text = gateway.read_text(encoding="utf-8") self.assertIn("const ENGINE_AGENT_MCP_VERSION = '0.10.0'", gateway_text) gateway.write_text( gateway_text.replace( "const ENGINE_AGENT_MCP_VERSION = '0.10.0'", "const ENGINE_AGENT_MCP_VERSION = '9.9.9'", ), encoding="utf-8", ) descriptor["source"]["gatewaySha256"] = sha256(gateway) ( payload / RUNNER.ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL ).write_text( json.dumps(descriptor, indent=2) + "\n", encoding="utf-8", ) with self.assertRaisesRegex( RUNNER.DeployError, "gateway MCP version is not registered", ): RUNNER.validate_engine_node_intelligence_transition( payload, RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) def test_archive_tamper_and_embedded_authority_fail_closed(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) payload, descriptor = self.make_activation_payload(root) archive = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL archive.write_bytes(archive.read_bytes() + b"tamper") with self.assertRaisesRegex(RUNNER.DeployError, "archive sha256 mismatch"): RUNNER.validate_engine_node_intelligence_transition( payload, RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) with tempfile.TemporaryDirectory() as directory: payload, _descriptor = self.make_activation_payload( Path(directory), runtime_env=["AUTH_TOKEN=forbidden"], ) with self.assertRaisesRegex(RUNNER.DeployError, "contains runtime authority"): RUNNER.validate_engine_node_intelligence_transition( payload, RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) def test_payload_extra_file_and_wrong_entry_order_are_rejected(self): with tempfile.TemporaryDirectory() as directory: payload, _descriptor = self.make_activation_payload(Path(directory)) service = payload / RUNNER.ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL (service / "unexpected.txt").write_text("no\n", encoding="utf-8") with self.assertRaisesRegex(RUNNER.DeployError, "service payload file set mismatch"): RUNNER.validate_engine_node_intelligence_transition( payload, RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) with tempfile.TemporaryDirectory() as directory: payload, _descriptor = self.make_activation_payload(Path(directory)) with self.assertRaisesRegex(RUNNER.DeployError, "files.txt exact set/order mismatch"): RUNNER.validate_engine_node_intelligence_transition( payload, tuple(reversed(RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES)), ) def test_predecessor_drift_is_rejected_before_apply(self): with tempfile.TemporaryDirectory() as payload_dir, tempfile.TemporaryDirectory() as live_dir: payload, _descriptor = self.make_activation_payload(Path(payload_dir)) live = Path(live_dir) (live / "nodedc-source/server/routes").mkdir(parents=True) (live / "docker-compose.yml").write_bytes( (ENGINE_ROOT / "docker-compose.yml").read_bytes() ) baseline = bytes.fromhex("00") gateway = live / RUNNER.ENGINE_NODE_INTELLIGENCE_GATEWAY_REL gateway.write_bytes(baseline) original_root = RUNNER.COMPONENTS["engine"]["payload_root"] RUNNER.COMPONENTS["engine"]["payload_root"] = live try: with mock.patch.object( RUNNER, "preflight_engine_credential_backend_runtime", return_value={"mode": "verified-derived-retry"}, ): with self.assertRaisesRegex(RUNNER.DeployError, "gateway drift detected"): RUNNER.preflight_engine_node_intelligence_predecessor(payload) finally: RUNNER.COMPONENTS["engine"]["payload_root"] = original_root def test_service_selection_and_compose_dispatch_are_narrow(self): self.assertEqual( RUNNER.component_services( "engine", RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ), (RUNNER.ENGINE_NODE_INTELLIGENCE_SERVICE, "nodedc-backend"), ) self.assertEqual( RUNNER.component_services( "engine", RUNNER.ENGINE_NODE_INTELLIGENCE_ROLLBACK_ENTRIES, ), ("nodedc-backend",), ) with ( mock.patch.object(RUNNER, "run_build") as build, mock.patch.object(RUNNER, "prepare_component_runtime") as prepare, mock.patch.object(RUNNER, "run_engine_node_intelligence_compose") as exact_compose, mock.patch.object(RUNNER, "run_compose") as legacy_compose, ): RUNNER.run_component_runtime( "engine", RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, (RUNNER.ENGINE_NODE_INTELLIGENCE_SERVICE, "nodedc-backend"), ) build.assert_called_once() prepare.assert_called_once() exact_compose.assert_called_once() legacy_compose.assert_not_called() def test_exact_compose_never_recreates_dependencies(self): services = (RUNNER.ENGINE_NODE_INTELLIGENCE_SERVICE, "nodedc-backend") with ( mock.patch.object( RUNNER, "compose_base_cmd", return_value=["docker", "compose", "-f", "exact.yml"], ), mock.patch.object( RUNNER, "component_compose_root", return_value=Path("/exact-engine-root"), ), mock.patch.object(RUNNER.subprocess, "run") as execute, ): RUNNER.run_engine_node_intelligence_compose( services, RUNNER.ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) command = execute.call_args_list[0].args[0] self.assertIn("--pull", command) self.assertIn("never", command) self.assertIn("--no-deps", command) self.assertEqual(command[-2:], list(services)) def test_backend_additive_mount_projects_into_legacy_guard(self): descriptor = {"action": "activate"} baseline_mounts = [ { "Type": "bind", "Source": "/baseline/app", "Destination": "/app", "RW": True, }, ] node_mount = { "Type": "bind", "Source": str(RUNNER.ENGINE_NODE_INTELLIGENCE_SECRET_FILE), "Destination": RUNNER.ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH, "RW": False, } container = {"Mounts": [*baseline_mounts, node_mount]} with ( mock.patch.object( RUNNER, "current_engine_node_intelligence_descriptor", return_value=descriptor, ), mock.patch.object( RUNNER, "validate_installed_engine_node_intelligence_source", ) as source_guard, mock.patch.object( RUNNER, "validate_engine_backend_mounts_for_installed_runtime", ) as legacy_guard, ): RUNNER.validate_engine_backend_node_intelligence_mounts_for_installed_runtime( container, node_modules_read_only=True, ) source_guard.assert_called_once_with(descriptor) projected = legacy_guard.call_args.args[0] self.assertEqual(projected["Mounts"], baseline_mounts) self.assertTrue(legacy_guard.call_args.args[1]) def test_backend_additive_mount_rejects_source_or_mode_drift(self): descriptor = {"action": "activate"} for source, read_write in (("/wrong/source", False), (str(RUNNER.ENGINE_NODE_INTELLIGENCE_SECRET_FILE), True)): container = { "Mounts": [{ "Type": "bind", "Source": source, "Destination": RUNNER.ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH, "RW": read_write, }], } with ( mock.patch.object( RUNNER, "current_engine_node_intelligence_descriptor", return_value=descriptor, ), mock.patch.object( RUNNER, "validate_installed_engine_node_intelligence_source", ), ): with self.assertRaisesRegex( RUNNER.DeployError, "node-intelligence mount barrier mismatch", ): RUNNER.validate_engine_backend_node_intelligence_mounts_for_installed_runtime( container, node_modules_read_only=True, ) if __name__ == "__main__": unittest.main()