feat(n8n): add rotating provider access credential
This commit is contained in:
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user