NODEDC_PLATFORM/infra/deploy-runner/test_engine_mcp_gelios_item...

320 lines
12 KiB
Python

import hashlib
import importlib.machinery
import importlib.util
import io
import json
import os
from pathlib import Path
import subprocess
import tarfile
import tempfile
import unittest
from contextlib import redirect_stdout
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
BUILDER_PATH = (
SCRIPT_DIR / "build-engine-mcp-gelios-items-envelope-artifact.mjs"
)
ENGINE_ROOT = SCRIPT_DIR.parent.parent.parent / "NODEDC_ENGINE_INFRA"
PATCH_ID = "engine-mcp-gelios-items-envelope-20991231-999"
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_engine_mcp_gelios_items_envelope",
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 EngineMcpGeliosItemsEnvelopeTest(unittest.TestCase):
def require_current_target_source(self):
for relative_path, expected in (
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256.items()
):
path = ENGINE_ROOT / relative_path
if (
not path.is_file()
or hashlib.sha256(path.read_bytes()).hexdigest() != expected
):
self.skipTest("Gelios items envelope source advanced")
def build(self, artifact_dir, engine_root=ENGINE_ROOT):
environment = os.environ.copy()
environment["NODEDC_ENGINE_SOURCE_ROOT"] = str(engine_root)
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
completed = subprocess.run(
["node", str(BUILDER_PATH), PATCH_ID],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(completed.stdout)
def test_builder_is_deterministic_exact_and_backend_only(self):
self.require_current_target_source()
with tempfile.TemporaryDirectory(
prefix="nodedc-engine-mcp-gelios-items-"
) as directory:
root = Path(directory)
first = self.build(root / "first")
second = self.build(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["services"], ["nodedc-backend"])
self.assertEqual(
first["providerPackageTransition"],
"gelios.provider.v11-to-v12",
)
self.assertEqual(first["responseCollectionPath"], "items")
self.assertTrue(first["historicalExecutionPackagePreserved"])
self.assertFalse(first["engineCompilerChanged"])
self.assertFalse(first["rawProviderValuesIncluded"])
extract = root / "extract"
with tarfile.open(first_artifact, "r:gz") as archive:
archive.extractall(extract, filter="data")
entries = tuple(
(extract / "files.txt").read_text(encoding="utf-8").splitlines()
)
payload = extract / "payload"
self.assertEqual(
entries,
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES,
)
self.assertEqual(
RUNNER.component_services("engine", entries),
("nodedc-backend",),
)
RUNNER.validate_engine_mcp_gelios_items_envelope_slice(
payload,
entries,
)
def test_catalogs_preserve_history_and_have_one_active_authority(self):
self.require_current_target_source()
execution = json.loads(
(
ENGINE_ROOT
/ "nodedc-source/server/assets/execution-plans/v1/catalog.json"
).read_text(encoding="utf-8")
)
security = json.loads(
(
ENGINE_ROOT
/ "nodedc-source/server/assets/provider-packages/v1/catalog.json"
).read_text(encoding="utf-8")
)
execution_ids = [item["id"] for item in execution["packages"]]
security_ids = [item["id"] for item in security["packages"]]
self.assertIn("gelios.provider.v11", execution_ids)
self.assertIn("gelios.provider.v12", execution_ids)
self.assertNotIn("gelios.provider.v11", security_ids)
self.assertEqual(security_ids.count("gelios.provider.v12"), 1)
def test_preflight_requires_045_foundation_and_absent_descriptor(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-engine-mcp-gelios-items-preflight-"
) as directory:
root = Path(directory)
hashes = {}
for mapping in (
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_PREDECESSOR_SHA256,
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256,
):
for relative_path, expected in mapping.items():
path = root / relative_path
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("installed\n", encoding="utf-8")
hashes[path] = expected
with (
mock.patch.object(RUNNER, "component_root", return_value=root),
mock.patch.object(
RUNNER,
"sha256_file",
side_effect=lambda path: hashes[Path(path)],
),
mock.patch.object(
RUNNER,
"preflight_engine_credential_backend_runtime",
return_value={"mode": "verified-derived-retry"},
),
):
result = (
RUNNER
.preflight_engine_mcp_gelios_items_envelope_predecessor()
)
self.assertEqual(
result["mode"],
"gelios-provider-v11-to-v12-items-envelope",
)
self.assertIn(
RUNNER.ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL,
result["foundation_sha256"],
)
def test_plan_renders_exact_untouched_boundaries(self):
self.require_current_target_source()
with tempfile.TemporaryDirectory(
prefix="nodedc-engine-mcp-gelios-items-plan-"
) as directory:
root = Path(directory)
artifact = Path(self.build(root / "artifacts")["artifact"])
live_root = root / "live"
live_root.mkdir()
preflight = {
"mode": "gelios-provider-v11-to-v12-items-envelope",
"predecessor_sha256": dict(
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_PREDECESSOR_SHA256
),
"foundation_sha256": dict(
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256
),
"target_sha256": dict(
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256
),
"new_paths": tuple(
RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_NEW_PATHS
),
"backend_mode": "verified-derived-retry",
}
output = io.StringIO()
with (
mock.patch.object(RUNNER, "validate_artifact_location"),
mock.patch.object(RUNNER, "ensure_layout"),
mock.patch.object(RUNNER, "TMP_DIR", root),
mock.patch.object(RUNNER, "component_root", return_value=live_root),
mock.patch.object(
RUNNER,
"component_compose_root",
return_value=live_root,
),
mock.patch.object(
RUNNER,
"preflight_engine_mcp_gelios_items_envelope_predecessor",
return_value=preflight,
),
mock.patch.object(
RUNNER,
"preflight_engine_credential_backend_runtime",
return_value={"mode": "verified-derived-retry"},
),
mock.patch.object(RUNNER, "state_has_sha", return_value=False),
mock.patch.object(
RUNNER,
"state_has_patch_id",
return_value=False,
),
redirect_stdout(output),
):
RUNNER.plan_artifact(artifact)
plan = output.getvalue()
self.assertIn("services=nodedc-backend", plan)
self.assertIn(
"engine_mcp_execution_history_v11=preserved",
plan,
)
self.assertIn("engine_mcp_security_authority_v12=exclusive", plan)
self.assertIn("engine_mcp_compiler_changed=no", plan)
self.assertIn("l2_graph=untouched", plan)
self.assertIn("n8n_core=untouched", plan)
self.assertIn("credentials=preserved", plan)
def test_healthchecks_dispatch_runtime_acceptance(self):
entries = RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES
with tempfile.TemporaryDirectory(
prefix="nodedc-engine-mcp-gelios-items-health-"
) as directory:
root = Path(directory)
all_hashes = {
**RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256,
**RUNNER.ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256,
}
for relative_path in all_hashes:
path = root / relative_path
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("target\n", encoding="utf-8")
def target_hash(path):
relative_path = Path(path).relative_to(root).as_posix()
return all_hashes[relative_path]
with (
mock.patch.object(RUNNER, "component_root", return_value=root),
mock.patch.object(
RUNNER,
"sha256_file",
side_effect=target_hash,
),
mock.patch.object(RUNNER, "healthcheck_compose_service"),
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=(),
),
mock.patch.object(
RUNNER,
"preflight_engine_credential_backend_runtime",
return_value={"mode": "verified-derived-retry"},
),
mock.patch.object(
RUNNER,
"accept_engine_mcp_gelios_items_envelope_runtime",
return_value={"live": "accepted"},
) as acceptance,
mock.patch.object(RUNNER, "healthcheck_container"),
):
RUNNER.run_healthchecks(
"engine",
entries,
("nodedc-backend",),
)
acceptance.assert_called_once_with()
def test_live_acceptance_proves_catalog_authority_and_generic_compiler(self):
expected_live = (
"engine-mcp-gelios-items-envelope:"
"0.11.0:v11-history:v12-authority:items"
)
with (
mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT),
mock.patch.object(
RUNNER,
"validate_engine_mcp_gelios_items_envelope_slice",
),
mock.patch.object(
RUNNER,
"engine_backend_container_id",
return_value="backend",
),
mock.patch.object(
RUNNER,
"run_engine_backend_probe",
return_value=expected_live,
) as backend_probe,
):
result = RUNNER.accept_engine_mcp_gelios_items_envelope_runtime()
self.assertEqual(result["live"], expected_live)
probe_source = backend_probe.call_args.args[0][-1]
self.assertIn("gelios.provider.v11", probe_source)
self.assertIn("authorities.length!==1", probe_source)
self.assertIn("/gelios|robot2b/i.test(compiler)", probe_source)
if __name__ == "__main__":
unittest.main(verbosity=2)