#!/usr/bin/env python3 import hashlib 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-engine-data-product-publish-grant-artifact.mjs" EXPECTED_ENTRIES = [ "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/dataProductPublishGrant", "nodedc-source/server/engineAgents/store.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/routes/n8n.js", "nodedc-source/services/backend/data-product-publish-grant/docker-compose.immutable-runtime.yml", ] PATCH_ID = "engine-data-product-publish-grant-20990101-001" SUCCESSFUL_CREDENTIAL_SINK_INDEX_SHA256 = ( "b2b790b02839570d967a2ca68b00e2724485a99389ac9b3a589a1b22302a36b8" ) class EnginePublishGrantArtifactTest(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_secret_free_and_deterministic(self): with tempfile.TemporaryDirectory(prefix="nodedc-engine-publish-artifact-") as directory: root = Path(directory) first_dir = root / "first" second_dir = root / "second" first_dir.mkdir() second_dir.mkdir() first = self.build(first_dir, PATCH_ID) artifact = Path(first["artifact"]) first_bytes = artifact.read_bytes() second = self.build(second_dir, PATCH_ID) second_bytes = Path(second["artifact"]).read_bytes() self.assertEqual(first["entries"], EXPECTED_ENTRIES) 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: names = {member.name for member in archive.getmembers()} files = archive.extractfile("files.txt").read().decode("utf-8").splitlines() manifest = archive.extractfile("manifest.env").read().decode("utf-8") n8n_source = archive.extractfile( "payload/nodedc-source/server/routes/n8n.js" ).read().decode("utf-8") gateway_source = archive.extractfile( "payload/nodedc-source/server/routes/engineAgentGateway.js" ).read().decode("utf-8") runtime_override = archive.extractfile( "payload/nodedc-source/services/backend/data-product-publish-grant/" "docker-compose.immutable-runtime.yml" ).read().decode("utf-8") self.assertEqual(files, EXPECTED_ENTRIES) self.assertEqual( manifest, f"id={PATCH_ID}\ncomponent=engine\ntype=app-overlay\n", ) forbidden_roots = ( "payload/docker-compose.yml", "payload/nodedc-source/server/index.js", "payload/nodedc-source/server/credentialPolicies/ndcPrivateNode.js", "payload/nodedc-source/server/credentialSink/", "payload/nodedc-source/server/routes/engineCredentialSink.js", "payload/nodedc-source/server/middleware/demoAccess.js", "payload/nodedc-source/server/routes/ndcAgentMcp.js", "payload/nodedc-source/server/tests/", "payload/nodedc-source/dist/", "payload/nodedc-source/server/data/", ) self.assertFalse(any( name == root.rstrip("/") or name.startswith(root) for name in names for root in forbidden_roots )) self.assertNotIn("from '../credentialSink/", n8n_source) self.assertIn("engineCredentialSinkN8nAdapter", n8n_source) self.assertIn("engineDataProductPublishGrantN8nAdapter", n8n_source) for tool in ( "engine_plan_data_product_publish_grant", "engine_apply_data_product_publish_grant", "engine_accept_data_product_publish_grant", "engine_rollback_data_product_publish_grant", ): self.assertIn(tool, gateway_source) self.assertIn(" nodedc-backend:", runtime_override) self.assertNotIn(" n8n:", runtime_override) self.assertIn( "source: /volume2/nodedc-demo/nodedc-control-plane/publish-grants", runtime_override, ) self.assertNotIn( "source: /volume2/nodedc-demo/nodedc-control-plane\n", runtime_override, ) self.assertIn("read_only: true", runtime_override) self.assertEqual(runtime_override.count("create_host_path: false"), 2) def test_builder_requires_a_fresh_never_issued_patch_id(self): with tempfile.TemporaryDirectory(prefix="nodedc-engine-publish-id-") as directory: artifact_dir = Path(directory) environment = os.environ.copy() environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir) cases = ( ([], "fresh-patch-id"), (["engine-data-product-publish-grant-20260716-001"], "patch_id_already_issued"), (["engine-data-product-publish-grant-20260717-001"], "patch_id_already_issued"), (["engine-data-product-publish-grant-20260717-002"], "patch_id_already_issued"), (["engine-publish-grant-unit-001"], "fresh-patch-id"), ) for arguments, expected in cases: with self.subTest(arguments=arguments): result = subprocess.run( ["node", str(BUILDER), *arguments], check=False, capture_output=True, text=True, env=environment, ) self.assertNotEqual(result.returncode, 0) self.assertIn(expected, result.stderr) self.build(artifact_dir, PATCH_ID) duplicate = subprocess.run( ["node", str(BUILDER), PATCH_ID], check=False, capture_output=True, text=True, env=environment, ) self.assertNotEqual(duplicate.returncode, 0) self.assertIn("artifact_already_exists", duplicate.stderr) if __name__ == "__main__": unittest.main(verbosity=2)