feat(n8n): add rotating provider access credential

This commit is contained in:
Codex
2026-07-19 10:11:56 +03:00
parent 3c5d8f6cef
commit 0611a88971
10 changed files with 408 additions and 64 deletions
@@ -13,19 +13,30 @@ const engineRoot = resolve(platformRoot, "../NODEDC_ENGINE_INFRA");
const artifactRoot = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || join(here, "../deploy-artifacts"));
const stageArtifact = resolve(
process.env.NODEDC_N8N_EXTENSION_STAGE_ARTIFACT
|| join(artifactRoot, "nodedc-n8n-private-extension-n8n-nodes-ndc-history-read-20260717-004.tgz"),
|| join(artifactRoot, "nodedc-n8n-private-extension-n8n-nodes-ndc-provider-rotating-access-20260718-006.tgz"),
);
const predecessorArtifact = resolve(
process.env.NODEDC_N8N_EXTENSION_PREDECESSOR_ARTIFACT
|| join(artifactRoot, "nodedc-engine-n8n-private-extension-20260717-005.tgz"),
);
const stageArtifactSha256 = "a78f8250704e4893eab727cdb862b69ce45a84310fbb73ecd3dfd968b72d12ee";
const releaseId = "0.1.3-3354149b5245e39a";
const packageVersion = "0.1.3";
const packageSha256 = "3354149b5245e39a10f6f03a4b43796602d7e57ea1f91dcc6cb6774ead97501d";
const stageArtifactSha256 = "2699e53c482f2d2bec6d13709a7e9b9a0bb3791ad2018c142e9bc4e7bf371e4a";
const predecessorArtifactSha256 = "f0f687ddcc1bc006db56c75e73c8213e99298c31c2d881cde078e5ddc9600172";
const releaseId = "0.1.4-59dc9f7882721d6a";
const packageVersion = "0.1.4";
const packageSha256 = "59dc9f7882721d6a5e2ae84ffa6aa8cd9bc5432f5904d24e70e363573ed2757f";
const n8nVersion = "2.3.2";
const baseImage = "docker.n8n.io/n8nio/n8n:2.3.2";
const architecture = "amd64";
const generatedAt = "2026-07-17T21:00:00.000Z";
const predecessorGitRevision = "96ad46e3c62943f818233481957c62e5088ae35c";
const previouslyIssuedTransitionIds = new Set(["20260715-002", "20260716-003", "20260717-004"]);
const generatedAt = "2026-07-18T21:00:00.000Z";
const previouslyIssuedTransitionIds = new Set([
"20260715-002",
"20260716-003",
"20260717-004",
"20260717-005",
"20260718-006",
"20260718-007",
]);
const transitionId = readTransitionId(process.argv.slice(2), process.env.NODEDC_N8N_TRANSITION_ID);
const activationId = `engine-n8n-private-extension-${transitionId}`;
const rollbackId = `engine-n8n-private-extension-rollback-${transitionId}`;
@@ -51,7 +62,9 @@ const expectedCredentialTypes = [
"ndcDataProductWriterApi",
"ndcDataProductReaderApi",
"ndcFoundryBindingApi",
"ndcProviderRotatingAccessApi",
];
const predecessorCredentialTypes = expectedCredentialTypes.slice(0, 3);
const nodeModules = [
["dist/nodes/NdcDataProductPublish/NdcDataProductPublish.node.js", "NdcDataProductPublish"],
["dist/nodes/NdcDataProductRead/NdcDataProductRead.node.js", "NdcDataProductRead"],
@@ -61,12 +74,14 @@ const credentialModules = [
["dist/credentials/NdcDataProductWriterApi.credentials.js", "NdcDataProductWriterApi"],
["dist/credentials/NdcDataProductReaderApi.credentials.js", "NdcDataProductReaderApi"],
["dist/credentials/NdcFoundryBindingApi.credentials.js", "NdcFoundryBindingApi"],
["dist/credentials/NdcProviderRotatingAccessApi.credentials.js", "NdcProviderRotatingAccessApi"],
];
await mkdir(artifactRoot, { recursive: true });
await assertArtifactTargetFresh(join(artifactRoot, `nodedc-${activationId}.tgz`));
await assertArtifactTargetFresh(join(artifactRoot, `nodedc-${rollbackId}.tgz`));
assertSha(await readFile(stageArtifact), stageArtifactSha256, "staging artifact");
assertSha(await readFile(predecessorArtifact), predecessorArtifactSha256, "predecessor artifact");
assertEngineBaseline(await readFile(join(engineRoot, "docker-compose.yml"), "utf8"));
const work = await mkdtemp(join(tmpdir(), "nodedc-engine-n8n-sealed-"));
@@ -111,10 +126,10 @@ try {
assertExact(privateNodes.map((item) => item.name), expectedNodeTypes, "node types");
assertExact(privateCredentials.map((item) => item.name), expectedCredentialTypes, "credential types");
const predecessorDescriptor = JSON.parse(gitFile(descriptorRel));
const predecessorNodes = JSON.parse(gitFile(nodesCatalogRel));
const predecessorCredentials = JSON.parse(gitFile(credentialsCatalogRel));
const predecessorMeta = JSON.parse(gitFile(metaRel));
const predecessorDescriptor = JSON.parse(predecessorArtifactFile(descriptorRel));
const predecessorNodes = JSON.parse(predecessorArtifactFile(nodesCatalogRel));
const predecessorCredentials = JSON.parse(predecessorArtifactFile(credentialsCatalogRel));
const predecessorMeta = JSON.parse(predecessorArtifactFile(metaRel));
assertPredecessorCatalogs(
predecessorDescriptor,
predecessorNodes,
@@ -148,8 +163,18 @@ try {
packageVersion: predecessorDescriptor.packageVersion,
packageSha256: predecessorDescriptor.packageSha256,
};
const activationDescriptor = descriptor(target, predecessor.releaseId, predecessor.releaseId);
const rollbackDescriptor = descriptor(predecessor, releaseId, releaseId);
const activationDescriptor = descriptor(
target,
predecessor.releaseId,
predecessor.releaseId,
expectedCredentialTypes,
);
const rollbackDescriptor = descriptor(
predecessor,
releaseId,
releaseId,
predecessorCredentialTypes,
);
const override = composeOverride(target);
const rollbackOverride = composeOverride(predecessor);
@@ -204,7 +229,7 @@ try {
await rm(work, { recursive: true, force: true });
}
function descriptor(target, expectedCurrent, rollbackBaseline) {
function descriptor(target, expectedCurrent, rollbackBaseline, credentialTypes) {
return {
schemaVersion: "nodedc.engine-n8n-private-extension-transition/v1",
action: "activate",
@@ -221,7 +246,7 @@ function descriptor(target, expectedCurrent, rollbackBaseline) {
topologyServices: ["n8n"],
expectedCurrent,
expectedNodeTypes,
expectedCredentialTypes,
expectedCredentialTypes: credentialTypes,
rollbackBaseline,
};
}
@@ -327,15 +352,15 @@ function assertPackage(value) {
function assertPredecessorCatalogs(descriptorValue, nodes, credentials, meta) {
if (descriptorValue?.action !== "activate"
|| descriptorValue?.releaseId !== "0.1.2-05e4b38b14b4a019"
|| descriptorValue?.packageVersion !== "0.1.2"
|| descriptorValue?.packageSha256 !== "05e4b38b14b4a019ce1f6eee27b9e320094cb3560903bd68074966b3a1267af5") {
|| descriptorValue?.releaseId !== "0.1.3-3354149b5245e39a"
|| descriptorValue?.packageVersion !== "0.1.3"
|| descriptorValue?.packageSha256 !== "3354149b5245e39a10f6f03a4b43796602d7e57ea1f91dcc6cb6774ead97501d") {
throw new Error("predecessor_descriptor_mismatch");
}
if (!Array.isArray(nodes) || nodes.length !== 437
|| !Array.isArray(credentials) || credentials.length !== 388
|| meta?.n8nVersion !== n8nVersion
|| meta?.source !== "n8n-core+n8n-nodes-ndc@0.1.2"
|| meta?.source !== "n8n-core+n8n-nodes-ndc@0.1.3"
|| meta?.nodeCount !== 437
|| meta?.credentialCount !== 388) {
throw new Error("predecessor_catalog_mismatch");
@@ -346,8 +371,8 @@ function assertPredecessorCatalogs(descriptorValue, nodes, credentials, meta) {
"predecessor node types",
);
assertExact(
credentials.filter((item) => expectedCredentialTypes.includes(String(item?.name || ""))).map((item) => item.name),
expectedCredentialTypes,
credentials.filter((item) => predecessorCredentialTypes.includes(String(item?.name || ""))).map((item) => item.name),
predecessorCredentialTypes,
"predecessor credential types",
);
}
@@ -381,8 +406,17 @@ function assertSha(bytes, expected, label) {
if (actual !== expected) throw new Error(`${label.replaceAll(" ", "_")}_sha256_mismatch:${actual}`);
}
function gitFile(rel) {
return run("git", ["show", `${predecessorGitRevision}:${rel}`], engineRoot).stdout;
function predecessorArtifactFile(rel) {
const script = [
"import pathlib,sys,tarfile",
"archive=pathlib.Path(sys.argv[1])",
"member='payload/'+sys.argv[2]",
"with tarfile.open(archive,'r:gz') as source:",
" extracted=source.extractfile(member)",
" if extracted is None: raise SystemExit('predecessor member missing')",
" sys.stdout.buffer.write(extracted.read())",
].join("\n");
return run("python3", ["-c", script, predecessorArtifact, rel]).stdout;
}
async function writeJson(path, value) {
@@ -12,8 +12,8 @@ const platformRoot = resolve(scriptDir, "../..");
const packageRoot = resolve(platformRoot, "packages/n8n-nodes-ndc");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const requireModule = createRequire(import.meta.url);
const expectedPackageVersion = "0.1.3";
const [patchId = "n8n-nodes-ndc-history-read-20260717-004", ...extra] = process.argv.slice(2);
const expectedPackageVersion = "0.1.4";
const [patchId = "n8n-nodes-ndc-provider-rotating-access-20260718-006", ...extra] = process.argv.slice(2);
const expectedRuntimeNodes = [
{
file: "dist/nodes/NdcDataProductPublish/NdcDataProductPublish.node.js",
@@ -37,11 +37,13 @@ const expectedN8nCredentials = [
"dist/credentials/NdcDataProductWriterApi.credentials.js",
"dist/credentials/NdcDataProductReaderApi.credentials.js",
"dist/credentials/NdcFoundryBindingApi.credentials.js",
"dist/credentials/NdcProviderRotatingAccessApi.credentials.js",
];
const expectedCredentialTypes = [
"ndcDataProductWriterApi",
"ndcDataProductReaderApi",
"ndcFoundryBindingApi",
"ndcProviderRotatingAccessApi",
];
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
+38 -5
View File
@@ -208,6 +208,11 @@ ENGINE_N8N_RELEASE_CATALOG_JSON_SHA256 = {
"credentials": "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d",
"meta": "b8aa60c0d919573626fa0694a1e4ae9ede015325a5d312e3e05d5ac293084aa7",
},
"0.1.4-59dc9f7882721d6a": {
"nodes": "b0a5215699a6e691b8cfc505ab457d5632ef6d83adb3537520d0b60760ba16b2",
"credentials": "af8ada839070a4c24b9b10981a751c2b2db651dced1392333d7a4ef13de4564e",
"meta": "8fadbc594c5d14b1d53ab69b677f00823949410b9a37f4e43ecc321e89d68983",
},
}
ENGINE_N8N_INACTIVE_NODES_CATALOG_JSON_SHA256 = "b70d9d8130d498c55de46a5d0758c844f1242b70803457b2291ab3a9de8056f2"
ENGINE_N8N_INACTIVE_CREDENTIALS_CATALOG_JSON_SHA256 = "680e9f52aac791efbd38e3bd99bd51ef5cded9756d867897c6c2755850e87b50"
@@ -236,6 +241,7 @@ N8N_PRIVATE_EXTENSION_CREDENTIALS = (
"dist/credentials/NdcDataProductWriterApi.credentials.js",
"dist/credentials/NdcDataProductReaderApi.credentials.js",
"dist/credentials/NdcFoundryBindingApi.credentials.js",
"dist/credentials/NdcProviderRotatingAccessApi.credentials.js",
)
ENGINE_N8N_NODE_TYPES = (
"n8n-nodes-ndc.ndcDataProductPublish",
@@ -246,7 +252,13 @@ ENGINE_N8N_CREDENTIAL_TYPES = (
"ndcDataProductWriterApi",
"ndcDataProductReaderApi",
"ndcFoundryBindingApi",
"ndcProviderRotatingAccessApi",
)
ENGINE_N8N_CREDENTIAL_TYPES_BY_RELEASE = {
"0.1.2-05e4b38b14b4a019": ENGINE_N8N_CREDENTIAL_TYPES[:3],
"0.1.3-3354149b5245e39a": ENGINE_N8N_CREDENTIAL_TYPES[:3],
"0.1.4-59dc9f7882721d6a": ENGINE_N8N_CREDENTIAL_TYPES,
}
ENGINE_CREDENTIAL_SINK_ARTIFACT_ENTRIES = (
"nodedc-source/server/credentialPolicies/ndcPrivateNode.js",
"nodedc-source/server/credentialSink",
@@ -2191,6 +2203,13 @@ def is_engine_n8n_transition(component, entries):
return component == "engine" and entries is not None and ENGINE_N8N_TRANSITION_DESCRIPTOR_REL in entries
def engine_n8n_credential_types_for_release(release_id):
expected = ENGINE_N8N_CREDENTIAL_TYPES_BY_RELEASE.get(release_id)
if expected is None:
die("Engine n8n credential catalog release is not registered")
return expected
def read_engine_n8n_transition_descriptor(path, label="Engine n8n transition descriptor"):
descriptor = read_strict_json(path, label)
require_exact_json_keys(
@@ -2259,7 +2278,9 @@ def read_engine_n8n_transition_descriptor(path, label="Engine n8n transition des
die("Engine n8n activation rollback baseline mismatch")
if descriptor.get("expectedNodeTypes") != list(ENGINE_N8N_NODE_TYPES):
die("Engine n8n activation node type set mismatch")
if descriptor.get("expectedCredentialTypes") != list(ENGINE_N8N_CREDENTIAL_TYPES):
if descriptor.get("expectedCredentialTypes") != list(
engine_n8n_credential_types_for_release(release_id)
):
die("Engine n8n activation credential type set mismatch")
else:
if descriptor.get("expectedCurrent") != release_id:
@@ -2332,7 +2353,11 @@ def validate_engine_n8n_catalog_payload(payload_dir, descriptor):
if isinstance(item, dict) and str(item.get("name") or "") in ENGINE_N8N_CREDENTIAL_TYPES
]
expected_node_types = list(ENGINE_N8N_NODE_TYPES) if action == "activate" else []
expected_credential_types = list(ENGINE_N8N_CREDENTIAL_TYPES) if action == "activate" else []
expected_credential_types = (
list(engine_n8n_credential_types_for_release(descriptor["releaseId"]))
if action == "activate"
else []
)
if [item.get("name") for item in private_nodes] != expected_node_types:
die("Engine n8n pinned node catalog exact set mismatch")
if [item.get("name") for item in private_credentials] != expected_credential_types:
@@ -2340,7 +2365,7 @@ def validate_engine_n8n_catalog_payload(payload_dir, descriptor):
if any("usableAsTool" in item for item in private_nodes):
die("Engine n8n pinned node catalog would generate tool variants")
if action == "activate":
if len(nodes) != 437 or len(credentials) != 388:
if len(nodes) != 437 or len(credentials) != 385 + len(expected_credential_types):
die("Engine n8n activation catalog count mismatch")
if any(not str(item.get("displayName") or "").startswith("NDC ") for item in private_nodes):
die("Engine n8n private node visible name mismatch")
@@ -5694,7 +5719,11 @@ def preflight_engine_n8n_transition(descriptor, enforce_expected_current):
current_types = current_catalog["node_types"]
current_credentials = current_catalog["credential_types"]
expected_current_types = list(ENGINE_N8N_NODE_TYPES) if current_state != "verified_inactive" else []
expected_current_credentials = list(ENGINE_N8N_CREDENTIAL_TYPES) if current_state != "verified_inactive" else []
expected_current_credentials = (
list(engine_n8n_credential_types_for_release(current_state))
if current_state != "verified_inactive"
else []
)
if current_types != expected_current_types:
die("Engine n8n current live private-node set does not match its transition state")
if current_credentials != expected_current_credentials:
@@ -5929,7 +5958,11 @@ def accept_engine_n8n_runtime(descriptor):
die("Engine n8n private extension loader log acceptance failed")
expected_types = list(ENGINE_N8N_NODE_TYPES) if descriptor["action"] == "activate" else []
expected_credentials = list(ENGINE_N8N_CREDENTIAL_TYPES) if descriptor["action"] == "activate" else []
expected_credentials = (
list(engine_n8n_credential_types_for_release(descriptor["releaseId"]))
if descriptor["action"] == "activate"
else []
)
loader_catalog = engine_n8n_private_loader_catalog(container_id)
if loader_catalog["node_types"] != expected_types:
die("Engine n8n live package-loader node catalog acceptance failed")
@@ -16,17 +16,24 @@ 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"
RUNNER_PATH = Path(
os.environ.get("NODEDC_DEPLOY_RUNNER_UNDER_TEST", SCRIPT_DIR / "nodedc-deploy")
).resolve()
BUILDER_PATH = SCRIPT_DIR / "build-engine-n8n-private-extension-artifact.mjs"
STAGE_ARTIFACT = (
PLATFORM_ROOT
/ "infra/deploy-artifacts"
/ "nodedc-n8n-private-extension-n8n-nodes-ndc-history-read-20260717-004.tgz"
/ "nodedc-n8n-private-extension-n8n-nodes-ndc-provider-rotating-access-20260718-006.tgz"
)
TRANSITION_ID = "20260717-005"
SEALED_RELEASE_ID = "0.1.3-3354149b5245e39a"
SEALED_PACKAGE_SHA256 = "3354149b5245e39a10f6f03a4b43796602d7e57ea1f91dcc6cb6774ead97501d"
PREDECESSOR_RELEASE_ID = "0.1.2-05e4b38b14b4a019"
PREDECESSOR_ARTIFACT = (
PLATFORM_ROOT
/ "infra/deploy-artifacts"
/ "nodedc-engine-n8n-private-extension-20260717-005.tgz"
)
TRANSITION_ID = "20260718-008"
SEALED_RELEASE_ID = "0.1.4-59dc9f7882721d6a"
SEALED_PACKAGE_SHA256 = "59dc9f7882721d6a5e2ae84ffa6aa8cd9bc5432f5904d24e70e363573ed2757f"
PREDECESSOR_RELEASE_ID = "0.1.3-3354149b5245e39a"
BUILDER_ENGINE_PATHS = (
"nodedc-source/server/assets/n8n/schema/v2.3.2/nodes.catalog.json",
"nodedc-source/server/assets/n8n/schema/v2.3.2/credentials.catalog.json",
@@ -45,14 +52,16 @@ EXPECTED_CREDENTIALS = [
"ndcDataProductWriterApi",
"ndcDataProductReaderApi",
"ndcFoundryBindingApi",
"ndcProviderRotatingAccessApi",
]
PREDECESSOR_CREDENTIALS = EXPECTED_CREDENTIALS[:3]
PRIVATE_EXTENSION_CANON_FUNCTION_SHA256 = {
"expected_engine_n8n_compose_override": "8ee93f3e7c407f5557c711119236449a440d73a7fcac1bbfa50a09e6a13c1cff",
"engine_n8n_package_loader_probe_script": "2b3b3c96fad6e5e48ebea74426b21bc7eea6b6fe0e786b537da3366d18aedd9b",
"engine_n8n_private_loader_catalog": "a2ae389b4ef215900cf967b863d01056bd2a8eec55554566d0f6005e4e0bcbac",
"validate_engine_n8n_base_compose_source": "adacc5b20e52684cbc427362ddba5a6d2e13ef2091a89859e2b8f7bbf2e20458",
"preflight_engine_n8n_transition": "3d574acb081fb752e45cc0c3286d3ae4596dcee83a85e2c3fee81ac65ec796e0",
"accept_engine_n8n_runtime": "1a2614a465161e3833e84aca402817682da1a538bdb2aecc4af66b665497338d",
"preflight_engine_n8n_transition": "25bf33b3be0916a9dd82f064a710060eaf6bd7d87b3614ee0065af07ddbb446e",
"accept_engine_n8n_runtime": "bc49275adb2d5ab4fd98abd07b9059a197d023e03b40145b6fc910b70b2f738b",
}
@@ -107,6 +116,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
env.pop("NODEDC_N8N_TRANSITION_ID", None)
env["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(output)
env["NODEDC_N8N_EXTENSION_STAGE_ARTIFACT"] = str(STAGE_ARTIFACT)
env["NODEDC_N8N_EXTENSION_PREDECESSOR_ARTIFACT"] = str(PREDECESSOR_ARTIFACT)
command = ["node", str(BUILDER_PATH)]
if index == 0:
command.append(TRANSITION_ID)
@@ -165,8 +175,8 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
def test_transition_id_rejects_missing_invalid_and_previously_issued_values(self):
cases = (
([], "transition_id_required"),
(["20260717-004"], "transition_id_already_issued"),
(["engine-n8n-private-extension-20260717-005"], "transition_id_invalid"),
(["20260718-007"], "transition_id_already_issued"),
(["engine-n8n-private-extension-20260718-008"], "transition_id_invalid"),
(["20260230-004"], "transition_id_invalid"),
(["20260717-000"], "transition_id_invalid"),
([TRANSITION_ID, "unexpected-second-id"], "transition_id_argument_count_invalid"),
@@ -191,6 +201,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
env.pop("NODEDC_N8N_TRANSITION_ID", None)
env["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(self.artifact(0, "activation").parent)
env["NODEDC_N8N_EXTENSION_STAGE_ARTIFACT"] = str(STAGE_ARTIFACT)
env["NODEDC_N8N_EXTENSION_PREDECESSOR_ARTIFACT"] = str(PREDECESSOR_ARTIFACT)
result = subprocess.run(
["node", str(BUILDER_PATH), TRANSITION_ID],
cwd=PLATFORM_ROOT,
@@ -233,7 +244,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
self.assertEqual([item["name"] for item in private_nodes], EXPECTED_NODES)
self.assertEqual([item["name"] for item in private_credentials], EXPECTED_CREDENTIALS)
self.assertTrue(all("usableAsTool" not in item for item in private_nodes))
self.assertEqual((len(nodes), len(credentials)), (437, 388))
self.assertEqual((len(nodes), len(credentials)), (437, 389))
def test_rollback_catalog_restores_verified_predecessor_release(self):
with tempfile.TemporaryDirectory() as directory:
@@ -253,8 +264,8 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
EXPECTED_NODES,
)
self.assertEqual(
[item.get("name") for item in credentials if item.get("name") in EXPECTED_CREDENTIALS],
EXPECTED_CREDENTIALS,
[item.get("name") for item in credentials if item.get("name") in PREDECESSOR_CREDENTIALS],
PREDECESSOR_CREDENTIALS,
)
self.assertEqual((len(nodes), len(credentials)), (437, 388))
@@ -271,7 +282,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
self.assertEqual(override, RUNNER.expected_engine_n8n_compose_override(descriptor))
self.assertEqual(
hashlib.sha256(override.encode("utf-8")).hexdigest(),
"256142c14ae295bf9be4d74fdc8dcd6e134e4ded8a264f15e66b38e85503b589",
"d6d1249e93b5bed5e697715b6b773c7efa4ac234f12eab57d868407e6bb5390e",
)
self.assertIn("pull_policy: never", override)
self.assertIn("N8N_USER_FOLDER: /home/node", override)
@@ -563,7 +574,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
def test_sealed_release_exact_set_includes_implicit_directories(self):
release_relative = Path(
"payload/releases/n8n-nodes-ndc/0.1.3-3354149b5245e39a"
"payload/releases/n8n-nodes-ndc/0.1.4-59dc9f7882721d6a"
)
with tempfile.TemporaryDirectory() as directory:
work = Path(directory)
@@ -15,9 +15,11 @@ from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
PLATFORM_ROOT = SCRIPT_DIR.parent.parent
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
RUNNER_PATH = Path(
os.environ.get("NODEDC_DEPLOY_RUNNER_UNDER_TEST", SCRIPT_DIR / "nodedc-deploy")
).resolve()
BUILDER_PATH = SCRIPT_DIR / "build-n8n-private-extension-artifact.mjs"
PATCH_ID = "n8n-nodes-ndc-history-read-20260717-004"
PATCH_ID = "n8n-nodes-ndc-provider-rotating-access-20260718-006"
EXPECTED_NODES = [
"dist/nodes/NdcDataProductPublish/NdcDataProductPublish.node.js",
"dist/nodes/NdcDataProductRead/NdcDataProductRead.node.js",
@@ -32,11 +34,13 @@ EXPECTED_CREDENTIALS = [
"dist/credentials/NdcDataProductWriterApi.credentials.js",
"dist/credentials/NdcDataProductReaderApi.credentials.js",
"dist/credentials/NdcFoundryBindingApi.credentials.js",
"dist/credentials/NdcProviderRotatingAccessApi.credentials.js",
]
EXPECTED_CREDENTIAL_TYPES = [
"ndcDataProductWriterApi",
"ndcDataProductReaderApi",
"ndcFoundryBindingApi",
"ndcProviderRotatingAccessApi",
]
@@ -106,7 +110,7 @@ class N8nPrivateExtensionPolicyTest(unittest.TestCase):
manifest, entries, _payload = RUNNER.load_artifact(self.artifacts[0], Path(directory))
self.assertEqual(manifest["component"], "n8n-private-extension")
self.assertEqual(len(entries), 1)
self.assertRegex(entries[0], r"^releases/n8n-nodes-ndc/0\.1\.3-[a-f0-9]{16}$")
self.assertRegex(entries[0], r"^releases/n8n-nodes-ndc/0\.1\.4-[a-f0-9]{16}$")
with tarfile.open(self.artifacts[0], "r:gz") as archive:
names = archive.getnames()
@@ -118,7 +122,7 @@ class N8nPrivateExtensionPolicyTest(unittest.TestCase):
with tarfile.open(fileobj=io.BytesIO(package), mode="r:gz") as archive:
package_json_member = archive.getmember("package/package.json")
package_json = json.loads(archive.extractfile(package_json_member).read())
self.assertEqual(package_json["version"], "0.1.3")
self.assertEqual(package_json["version"], "0.1.4")
self.assertEqual(package_json["n8n"]["nodes"], EXPECTED_NODES)
self.assertEqual(package_json["n8n"]["credentials"], EXPECTED_CREDENTIALS)
for node_path in EXPECTED_NODES:
@@ -154,7 +158,7 @@ class N8nPrivateExtensionPolicyTest(unittest.TestCase):
"requiresPreActivationVerification": True,
}
self.assertEqual(release["schemaVersion"], "nodedc.n8n-private-extension-release/v2")
self.assertEqual(release["package"]["version"], "0.1.3")
self.assertEqual(release["package"]["version"], "0.1.4")
self.assertEqual(release["activation"]["rollbackBaselinePolicy"], expected_policy)
self.assertEqual(rollback["schemaVersion"], "nodedc.n8n-private-extension-rollback/v2")
self.assertEqual(rollback["baselinePolicy"], expected_policy)