Files
pikasTech-HWLAB/scripts/src/dev-deploy-apply.test.mjs
T
2026-05-23 21:15:05 +00:00

460 lines
15 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
buildKubectlCommandPrefix,
compareRuntimeIdentityEnv,
decideDevLegacySimulatorDeploymentCleanup,
decideDevTemplateJobReplacement,
formatKubeconfigAccessFailure,
inspectCodeAgentProviderDesiredState,
inspectCodeAgentProviderLiveDeployment,
parseArgs,
redactSensitiveText,
resolveApplySourceCommit,
resolveDevKubeconfigSelection
} from "./dev-deploy-apply.mjs";
import {
devCdTransactionGuardFailure,
hasDevCdTransactionEnv,
requireDevCdTransactionForSideEffect
} from "./dev-cd-transaction-guard.mjs";
const codeAgentBaseUrl = "http://172.26.26.227:17680/v1/responses";
function codeAgentDeploy(env = {}) {
return {
services: [
{
serviceId: "hwlab-cloud-api",
env: {
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-5.5",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: codeAgentBaseUrl,
OPENAI_API_KEY: "secretRef:hwlab-code-agent-provider/openai-api-key",
...env
}
}
]
};
}
function codeAgentWorkloads(envOverrides = []) {
return {
kind: "List",
items: [
{
kind: "Deployment",
metadata: {
name: "hwlab-cloud-api",
namespace: "hwlab-dev",
labels: {
"hwlab.pikastech.local/service-id": "hwlab-cloud-api"
}
},
spec: {
template: {
spec: {
containers: [
{
name: "hwlab-cloud-api",
env: [
{ name: "HWLAB_CODE_AGENT_PROVIDER", value: "codex-stdio" },
{ name: "HWLAB_CODE_AGENT_MODEL", value: "gpt-5.5" },
{ name: "HWLAB_CODE_AGENT_OPENAI_BASE_URL", value: codeAgentBaseUrl },
{
name: "OPENAI_API_KEY",
valueFrom: {
secretKeyRef: {
name: "hwlab-code-agent-provider",
key: "openai-api-key"
}
}
},
...envOverrides
]
}
]
}
}
}
}
]
};
}
test("allowlisted suspended template Job image change plans replacement", () => {
const decision = decideDevTemplateJobReplacement({
jobName: "hwlab-agent-worker-template",
jobNamespace: "hwlab-dev",
desiredSuspended: true,
liveExists: true,
liveSuspended: true,
oldImages: [
{
name: "hwlab-agent-worker",
serviceId: "hwlab-agent-worker",
image: "127.0.0.1:5000/hwlab/hwlab-agent-worker:old1234"
}
],
newImages: [
{
name: "hwlab-agent-worker",
serviceId: "hwlab-agent-worker",
image: "127.0.0.1:5000/hwlab/hwlab-agent-worker:new5678"
}
]
});
assert.equal(decision.replace, true);
assert.equal(decision.result, "planned");
assert.equal(decision.namespace, "hwlab-dev");
assert.equal(decision.jobName, "hwlab-agent-worker-template");
assert.equal(decision.oldImage, "127.0.0.1:5000/hwlab/hwlab-agent-worker:old1234");
assert.equal(decision.newImage, "127.0.0.1:5000/hwlab/hwlab-agent-worker:new5678");
});
test("DEV CD transaction guard rejects direct legacy side-effect calls", () => {
assert.equal(hasDevCdTransactionEnv({}), false);
assert.equal(hasDevCdTransactionEnv({ HWLAB_CD_TRANSACTION_ID: "tx-123" }), true);
const failure = requireDevCdTransactionForSideEffect({
env: {},
script: "scripts/dev-deploy-apply.mjs",
mode: "--apply"
});
assert.deepEqual(failure, devCdTransactionGuardFailure({
script: "scripts/dev-deploy-apply.mjs",
mode: "--apply"
}));
assert.equal(failure.error, "cd-transaction-required");
assert.equal(failure.mutationAttempted, false);
assert.match(failure.entrypoint, /scripts\/dev-cd-apply\.mjs/u);
assert.equal(
requireDevCdTransactionForSideEffect({
env: { HWLAB_CD_TRANSACTION_ID: "tx-123" },
script: "scripts/dev-deploy-apply.mjs",
mode: "--apply"
}),
null
);
});
test("matching allowlisted suspended template Job image does not replace", () => {
const image = "127.0.0.1:5000/hwlab/hwlab-cli:73b379f";
const decision = decideDevTemplateJobReplacement({
jobName: "hwlab-cli-template",
jobNamespace: "hwlab-dev",
desiredSuspended: true,
liveExists: true,
liveSuspended: true,
oldImages: [{ name: "hwlab-cli", serviceId: "hwlab-cli", image }],
newImages: [{ name: "hwlab-cli", serviceId: "hwlab-cli", image }]
});
assert.equal(decision.replace, false);
assert.equal(decision.result, "not_needed");
});
test("replacement policy refuses non-allowlisted or active Jobs", () => {
const ignored = decideDevTemplateJobReplacement({
jobName: "some-other-job",
jobNamespace: "hwlab-dev",
desiredSuspended: true,
liveExists: true,
liveSuspended: true,
oldImages: [{ name: "worker", serviceId: "worker", image: "image:old" }],
newImages: [{ name: "worker", serviceId: "worker", image: "image:new" }]
});
assert.equal(ignored.replace, false);
assert.equal(ignored.result, "ignored");
const active = decideDevTemplateJobReplacement({
jobName: "hwlab-cli-template",
jobNamespace: "hwlab-dev",
desiredSuspended: true,
liveExists: true,
liveSuspended: false,
oldImages: [{ name: "hwlab-cli", serviceId: "hwlab-cli", image: "image:old" }],
newImages: [{ name: "hwlab-cli", serviceId: "hwlab-cli", image: "image:new" }]
});
assert.equal(active.replace, false);
assert.equal(active.result, "blocked");
});
test("allowlisted stale simulator Deployment plans cleanup only with StatefulSet replacement", () => {
const decision = decideDevLegacySimulatorDeploymentCleanup({
deploymentName: "hwlab-box-simu",
deploymentNamespace: "hwlab-dev",
desiredReplacementExists: true,
desiredReplacementKind: "StatefulSet",
liveExists: true,
oldImages: [
{
name: "hwlab-box-simu",
serviceId: "hwlab-box-simu",
image: "127.0.0.1:5000/hwlab/hwlab-box-simu:old1234"
}
],
newImages: [
{
name: "hwlab-box-simu",
serviceId: "hwlab-box-simu",
image: "127.0.0.1:5000/hwlab/hwlab-box-simu:new5678"
}
]
});
assert.equal(decision.cleanup, true);
assert.equal(decision.result, "planned");
assert.equal(decision.namespace, "hwlab-dev");
assert.equal(decision.deploymentName, "hwlab-box-simu");
assert.equal(decision.serviceId, "hwlab-box-simu");
assert.equal(decision.desiredReplacementKind, "StatefulSet");
assert.equal(decision.oldImage, "127.0.0.1:5000/hwlab/hwlab-box-simu:old1234");
assert.equal(decision.newImage, "127.0.0.1:5000/hwlab/hwlab-box-simu:new5678");
});
test("legacy simulator cleanup refuses non-DEV or missing replacement cleanup", () => {
const wrongNamespace = decideDevLegacySimulatorDeploymentCleanup({
deploymentName: "hwlab-gateway-simu",
deploymentNamespace: "hwlab-prod",
desiredReplacementExists: true,
desiredReplacementKind: "StatefulSet",
liveExists: true
});
assert.equal(wrongNamespace.cleanup, false);
assert.equal(wrongNamespace.result, "blocked");
const missingReplacement = decideDevLegacySimulatorDeploymentCleanup({
deploymentName: "hwlab-gateway-simu",
deploymentNamespace: "hwlab-dev",
desiredReplacementExists: false,
desiredReplacementKind: null,
liveExists: true
});
assert.equal(missingReplacement.cleanup, false);
assert.equal(missingReplacement.result, "blocked");
const ignored = decideDevLegacySimulatorDeploymentCleanup({
deploymentName: "hwlab-cloud-api",
deploymentNamespace: "hwlab-dev",
desiredReplacementExists: true,
desiredReplacementKind: "StatefulSet",
liveExists: true
});
assert.equal(ignored.cleanup, false);
assert.equal(ignored.result, "ignored");
});
test("apply source commit follows deploy/catalog artifact identity", () => {
assert.equal(
resolveApplySourceCommit({ commitId: "73b379f" }, { commitId: "73b379f" }, "cb35ada68606"),
"73b379f"
);
assert.equal(
resolveApplySourceCommit({ commitId: "73b379f" }, { commitId: "deadbee" }, "cb35ada68606"),
"cb35ada68606"
);
});
test("runtime identity env comparator accepts matching commit image and tag", () => {
const comparison = compareRuntimeIdentityEnv(
{
HWLAB_COMMIT_ID: "7de6edd",
HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
HWLAB_IMAGE_TAG: "7de6edd"
},
{
HWLAB_COMMIT_ID: "7de6edd2c41fb50dcb007353780338be8832b27e",
HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
HWLAB_IMAGE_TAG: "7de6edd"
}
);
assert.equal(comparison.status, "match");
assert.equal(comparison.matchesDesired, true);
assert.deepEqual(comparison.drift, []);
});
test("runtime identity env comparator detects stale cloud-web env drift", () => {
const comparison = compareRuntimeIdentityEnv(
{
HWLAB_COMMIT_ID: "c7de474",
HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474",
HWLAB_IMAGE_TAG: "c7de474"
},
{
HWLAB_COMMIT_ID: "7de6edd",
HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
HWLAB_IMAGE_TAG: "7de6edd"
}
);
assert.equal(comparison.status, "drift");
assert.equal(comparison.matchesDesired, false);
assert.deepEqual(
comparison.drift.map((field) => field.name),
["HWLAB_COMMIT_ID", "HWLAB_IMAGE", "HWLAB_IMAGE_TAG"]
);
});
test("Code Agent provider desired state preserves Secret ref and egress base URL", () => {
const inspection = inspectCodeAgentProviderDesiredState(codeAgentDeploy(), codeAgentWorkloads());
assert.equal(inspection.ready, true);
assert.equal(inspection.status, "pass");
assert.equal(inspection.provider, "codex-stdio");
assert.equal(inspection.runtimeProvider, "openai-responses");
assert.equal(inspection.backend, "hwlab-cloud-api/codex-mcp-stdio");
assert.equal(inspection.secretRef.present, true);
assert.equal(inspection.secretMaterialRead, false);
assert.equal(inspection.egress.deployMatchesDevProxy, true);
assert.equal(inspection.egress.workloadMatchesDevProxy, true);
});
test("Code Agent provider desired state blocks direct public OpenAI base URL", () => {
const inspection = inspectCodeAgentProviderDesiredState(
codeAgentDeploy({ HWLAB_CODE_AGENT_OPENAI_BASE_URL: "https://api.openai.com/v1/responses" }),
codeAgentWorkloads([
{ name: "HWLAB_CODE_AGENT_OPENAI_BASE_URL", value: "https://api.openai.com/v1/responses" }
])
);
assert.equal(inspection.ready, false);
assert.equal(inspection.status, "blocked");
assert.equal(inspection.egress.directPublicOpenAi, true);
assert.ok(inspection.missingEgressContract.some((entry) => entry.includes("api.openai.com")));
});
test("Code Agent live Deployment inspection reads only env metadata", () => {
const deployment = codeAgentWorkloads().items[0];
const inspection = inspectCodeAgentProviderLiveDeployment(deployment);
assert.equal(inspection.ready, true);
assert.equal(inspection.secretRef.present, true);
assert.equal(inspection.secretValuesRead, false);
assert.equal(inspection.kubernetesSecretDataRead, false);
assert.equal(inspection.validationScope, "deployment-env-and-secretKeyRef-metadata-only");
});
test("DEV kubeconfig selection prefers flag then HWLAB_DEV_KUBECONFIG then KUBECONFIG then D601 default", () => {
assert.deepEqual(
resolveDevKubeconfigSelection({
flagValue: "/tmp/from-flag",
env: {
HWLAB_DEV_KUBECONFIG: "/tmp/from-hwlab",
KUBECONFIG: "/tmp/from-kubeconfig"
}
}),
{
kubeconfig: "/tmp/from-flag",
source: "flag:--kubeconfig",
paths: ["/tmp/from-flag"]
}
);
assert.deepEqual(
resolveDevKubeconfigSelection({
env: {
HWLAB_DEV_KUBECONFIG: "/tmp/from-hwlab",
KUBECONFIG: "/tmp/from-kubeconfig"
}
}),
{
kubeconfig: "/tmp/from-hwlab",
source: "env:HWLAB_DEV_KUBECONFIG",
paths: ["/tmp/from-hwlab"]
}
);
assert.deepEqual(
resolveDevKubeconfigSelection({
env: {
KUBECONFIG: "/tmp/from-kubeconfig"
}
}),
{
kubeconfig: "/tmp/from-kubeconfig",
source: "env:KUBECONFIG",
paths: ["/tmp/from-kubeconfig"]
}
);
assert.deepEqual(
resolveDevKubeconfigSelection({ env: {} }),
{
kubeconfig: "/etc/rancher/k3s/k3s.yaml",
source: "default:d601-k3s",
paths: ["/etc/rancher/k3s/k3s.yaml"]
}
);
});
test("DEV kubeconfig selection keeps multi-path KUBECONFIG readable checks explicit", () => {
const delimiter = process.platform === "win32" ? ";" : ":";
const selection = resolveDevKubeconfigSelection({
env: {
KUBECONFIG: [`/tmp/one`, `/tmp/two`].join(delimiter)
}
});
assert.equal(selection.kubeconfig, [`/tmp/one`, `/tmp/two`].join(delimiter));
assert.equal(selection.source, "env:KUBECONFIG");
assert.deepEqual(selection.paths, ["/tmp/one", "/tmp/two"]);
});
test("kubectl command prefix quotes kubeconfig paths without exposing contents", () => {
assert.equal(
buildKubectlCommandPrefix("/var/lib/unidesk/code-queue/kubeconfig"),
"KUBECONFIG=/var/lib/unidesk/code-queue/kubeconfig kubectl"
);
assert.equal(
buildKubectlCommandPrefix("/tmp/path with space/kubeconfig"),
"KUBECONFIG='/tmp/path with space/kubeconfig' kubectl"
);
});
test("kubeconfig access failure names source and path without secret material", () => {
const message = formatKubeconfigAccessFailure(
{
source: "env:HWLAB_DEV_KUBECONFIG"
},
"/missing/kubeconfig"
);
assert.match(message, /env:HWLAB_DEV_KUBECONFIG/u);
assert.match(message, /\/missing\/kubeconfig/u);
assert.match(message, /Secret values are never printed/u);
assert.equal(message.includes("client-key-data:"), false);
});
test("parseArgs reports a missing kubeconfig path as a blocking error", () => {
const args = parseArgs(["--apply", "--kubeconfig"]);
assert.equal(args.apply, true);
assert.equal(args.kubeconfigSpecified, true);
assert.deepEqual(args.errors, ["--kubeconfig requires a non-empty path value"]);
});
test("redaction removes common token and password material from kubectl output", () => {
const output = redactSensitiveText(
[
"Authorization: Bearer abc.def-123",
"token: abcdef123456",
"password=plain-secret",
"client-key-data: LS0tPRIVATE",
"postgresql://hwlab:db-secret@example.invalid:5432/hwlab"
].join("\n")
);
assert.equal(output.includes("abc.def-123"), false);
assert.equal(output.includes("plain-secret"), false);
assert.equal(output.includes("LS0tPRIVATE"), false);
assert.equal(output.includes("db-secret"), false);
assert.match(output, /Bearer <redacted>/u);
assert.match(output, /password=<redacted>/u);
assert.match(output, /client-key-data: <redacted>/u);
assert.match(output, /postgresql:\/\/hwlab:<redacted>@example.invalid/u);
});