feat: centralize candidate promotion pipelines
Platform CI tests / QuickStack deploy action tests (push) Successful in 27s
Nuvisphere/Platform-CI: Immutable QuickStack OCI deployment / Build once and deploy exact digest (push) Successful in 25s

This commit is contained in:
2026-08-20 11:40:37 +02:00
parent 8f41d273d7
commit a4247d2697
7 changed files with 752 additions and 3 deletions
@@ -3,11 +3,17 @@ import test from "node:test";
import {
deployExactImage,
expandTokens,
mergeEnvironment,
orderApplications,
resolveDeploymentBranch,
selectDeployment,
selectPipeline,
toSavePayload,
validateArtifact,
validateApplication,
validateTarget,
validateVersion2Pipeline,
verifyEndpoint,
} from "./deploy.mjs";
@@ -65,6 +71,63 @@ test("validates OCI paths and repository-local build paths", () => {
);
});
test("version 2 reuses one artifact across ordered applications", () => {
const config = {
version: 2,
artifacts: [
{ name: "webapp", image: "InnoMieter/Webapp", requiredFiles: ["/app/server.js"] },
{ name: "thumbnail", image: "innomieter/thumbnail", dockerfile: "Dockerfile.thumbnail" },
],
pipelines: [
{
branch: "staging",
strategy: "candidate",
applications: [
{ name: "worker", artifact: "webapp", appId: "app-worker", dependsOn: ["web"] },
{ name: "web", artifact: "webapp", appId: "app-web" },
],
},
],
};
assert.equal(selectPipeline(config, "staging")?.strategy, "candidate");
const pipeline = validateVersion2Pipeline(config, "staging");
assert.deepEqual(pipeline.artifacts.map((artifact) => artifact.name), ["webapp", "thumbnail"]);
assert.deepEqual(pipeline.applications.map((application) => application.name), ["web", "worker"]);
assert.equal(pipeline.applications[1].artifact, "webapp");
});
test("version 2 validates promotion contracts and token expansion", () => {
const sha = "1234567890abcdef1234567890abcdef12345678";
assert.equal(expandTokens("build-$sha12-$version", { sha, version: "v1.2.3" }), "build-1234567890ab-v1.2.3");
assert.throws(
() => validateVersion2Pipeline({
version: 2,
artifacts: [{ name: "web", image: "owner/web" }],
pipelines: [{ branch: "prod", strategy: "promote", applications: [{ name: "web", artifact: "web", appId: "app" }] }],
}, "prod"),
/requires release configuration/,
);
assert.throws(() => validateArtifact({ name: "web", image: "owner/web", requiredFiles: ["../secret"] }), /safe absolute path/);
assert.throws(
() => validateApplication({ name: "web", artifact: "missing", appId: "app" }, new Set(["known"])),
/unknown artifact/,
);
});
test("application dependencies reject missing nodes and cycles", () => {
assert.throws(
() => orderApplications([{ name: "web", dependsOn: ["missing"] }]),
/unknown application/,
);
assert.throws(
() => orderApplications([
{ name: "web", dependsOn: ["worker"] },
{ name: "worker", dependsOn: ["web"] },
]),
/cycle/,
);
});
test("preserves response-only fields and existing environment secrets safely", () => {
const payload = toSavePayload(app());
assert.equal(payload.createdAt, undefined);