354 lines
13 KiB
JavaScript
354 lines
13 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { promisify } from "node:util";
|
|
|
|
import { SERVICE_IDS } from "../internal/protocol/index.mjs";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
|
|
const V02_SERVICE_IDS = Object.freeze([
|
|
"hwlab-cloud-api",
|
|
"hwlab-cloud-web",
|
|
"hwlab-agent-mgr",
|
|
"hwlab-agent-worker",
|
|
"hwlab-device-pod",
|
|
"hwlab-gateway",
|
|
"hwlab-edge-proxy",
|
|
"hwlab-cli",
|
|
"hwlab-agent-skills"
|
|
]);
|
|
|
|
async function git(args) {
|
|
const result = await execFileAsync("git", args, {
|
|
cwd: repoRoot,
|
|
timeout: 5000,
|
|
maxBuffer: 1024 * 1024
|
|
});
|
|
return result.stdout.trim();
|
|
}
|
|
|
|
function digestFor(index) {
|
|
return `sha256:${String(index + 1).padStart(64, "0")}`;
|
|
}
|
|
|
|
test("refresh accepts an absolute publish report path", async () => {
|
|
const commitId = await git(["rev-parse", "HEAD"]);
|
|
const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]);
|
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-"));
|
|
const reportPath = path.join(tempDir, "dev-artifacts.json");
|
|
await writeFile(reportPath, `${JSON.stringify({
|
|
reportVersion: "v1",
|
|
taskId: "g14-artifact-publish",
|
|
commitId: shortCommitId,
|
|
artifactPublish: {
|
|
status: "published",
|
|
mode: "publish",
|
|
sourceCommitId: commitId,
|
|
registryPrefix: "127.0.0.1:5000/hwlab",
|
|
serviceCount: SERVICE_IDS.length,
|
|
requiredServiceCount: SERVICE_IDS.length,
|
|
disabledServiceCount: 0,
|
|
publishedCount: SERVICE_IDS.length,
|
|
publishPlan: {
|
|
version: "v2",
|
|
services: SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
required: true,
|
|
digest: digestFor(index),
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
|
|
imageTag: shortCommitId
|
|
}))
|
|
},
|
|
services: SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
status: "published",
|
|
artifactRequired: true,
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
|
|
imageTag: shortCommitId,
|
|
digest: digestFor(index),
|
|
repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`,
|
|
buildCreatedAt: "2026-05-24T00:00:00.000Z",
|
|
buildSource: `pikasTech/HWLAB@${commitId}`,
|
|
componentCommitId: commitId,
|
|
componentInputHash: `${String(index + 1).padStart(64, "a")}`,
|
|
dockerfileHash: `${String(index + 1).padStart(64, "b")}`,
|
|
buildArgsHash: `${String(index + 1).padStart(64, "c")}`,
|
|
ciAffected: index === 0,
|
|
ciReason: index === 0 ? ["component-path-changed"] : ["component-inputs-unchanged"]
|
|
}))
|
|
}
|
|
}, null, 2)}\n`);
|
|
|
|
const result = await execFileAsync(process.execPath, [
|
|
"scripts/refresh-artifact-catalog.mjs",
|
|
"--target-ref",
|
|
"HEAD",
|
|
"--publish-report",
|
|
reportPath,
|
|
"--no-write"
|
|
], {
|
|
cwd: repoRoot,
|
|
timeout: 15000,
|
|
maxBuffer: 10 * 1024 * 1024
|
|
});
|
|
const payload = JSON.parse(result.stdout);
|
|
assert.equal(payload.status, "published");
|
|
assert.equal(payload.publishedCount, SERVICE_IDS.length);
|
|
assert.deepEqual(payload.wrote, []);
|
|
assert.equal(payload.services[0].componentCommitId, commitId);
|
|
assert.equal(payload.services[0].ciAffected, true);
|
|
assert.equal(payload.services[0].componentInputHash, `${String(1).padStart(64, "a")}`);
|
|
});
|
|
|
|
test("refresh is read-only by default", async () => {
|
|
const commitId = await git(["rev-parse", "HEAD"]);
|
|
const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]);
|
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-default-readonly-"));
|
|
const reportPath = path.join(tempDir, "dev-artifacts.json");
|
|
await writeFile(reportPath, `${JSON.stringify({
|
|
reportVersion: "v1",
|
|
taskId: "g14-artifact-publish",
|
|
commitId: shortCommitId,
|
|
artifactPublish: {
|
|
status: "published",
|
|
mode: "publish",
|
|
sourceCommitId: commitId,
|
|
registryPrefix: "127.0.0.1:5000/hwlab",
|
|
serviceCount: SERVICE_IDS.length,
|
|
requiredServiceCount: SERVICE_IDS.length,
|
|
disabledServiceCount: 0,
|
|
publishedCount: SERVICE_IDS.length,
|
|
publishPlan: {
|
|
version: "v2",
|
|
services: SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
required: true,
|
|
digest: digestFor(index),
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
|
|
imageTag: shortCommitId
|
|
}))
|
|
},
|
|
services: SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
status: "published",
|
|
artifactRequired: true,
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
|
|
imageTag: shortCommitId,
|
|
digest: digestFor(index),
|
|
repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`
|
|
}))
|
|
}
|
|
}, null, 2)}\n`);
|
|
|
|
const result = await execFileAsync(process.execPath, [
|
|
"scripts/refresh-artifact-catalog.mjs",
|
|
"--target-ref",
|
|
"HEAD",
|
|
"--publish-report",
|
|
reportPath
|
|
], {
|
|
cwd: repoRoot,
|
|
timeout: 15000,
|
|
maxBuffer: 10 * 1024 * 1024
|
|
});
|
|
const payload = JSON.parse(result.stdout);
|
|
assert.equal(payload.status, "published");
|
|
assert.deepEqual(payload.wrote, []);
|
|
assert.equal(payload.deployTruthPolicy, "deploy.json is human-authored runtime config; artifact promotion must not rewrite it");
|
|
});
|
|
|
|
test("refresh keeps reused service image tags from the publish report", async () => {
|
|
const commitId = await git(["rev-parse", "HEAD"]);
|
|
const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]);
|
|
const reusedTag = "d42c77d";
|
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-reused-"));
|
|
const reportPath = path.join(tempDir, "dev-artifacts.json");
|
|
await writeFile(reportPath, `${JSON.stringify({
|
|
reportVersion: "v1",
|
|
taskId: "g14-artifact-publish",
|
|
commitId: shortCommitId,
|
|
artifactPublish: {
|
|
status: "published",
|
|
mode: "publish",
|
|
sourceCommitId: commitId,
|
|
registryPrefix: "127.0.0.1:5000/hwlab",
|
|
serviceCount: SERVICE_IDS.length,
|
|
requiredServiceCount: SERVICE_IDS.length,
|
|
disabledServiceCount: 0,
|
|
publishedCount: 1,
|
|
reusedCount: SERVICE_IDS.length - 1,
|
|
publishPlan: {
|
|
version: "v2",
|
|
services: SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
required: true,
|
|
status: index === 0 ? "published" : "reused",
|
|
digest: digestFor(index),
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${index === 0 ? shortCommitId : reusedTag}`,
|
|
imageTag: index === 0 ? shortCommitId : reusedTag
|
|
}))
|
|
},
|
|
services: SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
status: index === 0 ? "published" : "reused",
|
|
artifactRequired: true,
|
|
commitId: index === 0 ? shortCommitId : reusedTag,
|
|
sourceCommitId: index === 0 ? commitId : reusedTag,
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${index === 0 ? shortCommitId : reusedTag}`,
|
|
imageTag: index === 0 ? shortCommitId : reusedTag,
|
|
digest: digestFor(index),
|
|
repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`,
|
|
buildCreatedAt: "2026-05-24T00:00:00.000Z",
|
|
buildSource: index === 0 ? `pikasTech/HWLAB@${commitId}` : `pikasTech/HWLAB@${reusedTag}`,
|
|
componentCommitId: index === 0 ? commitId : reusedTag,
|
|
componentInputHash: `${String(index + 1).padStart(64, "d")}`,
|
|
dockerfileHash: `${String(index + 1).padStart(64, "e")}`,
|
|
buildArgsHash: `${String(index + 1).padStart(64, "f")}`,
|
|
ciAffected: index === 0,
|
|
ciReason: index === 0 ? ["component-path-changed"] : ["component-inputs-unchanged"],
|
|
reusedFrom: index === 0 ? null : `catalog:${reusedTag}`
|
|
}))
|
|
}
|
|
}, null, 2)}\n`);
|
|
|
|
const result = await execFileAsync(process.execPath, [
|
|
"scripts/refresh-artifact-catalog.mjs",
|
|
"--target-ref",
|
|
"HEAD",
|
|
"--publish-report",
|
|
reportPath,
|
|
"--no-write"
|
|
], {
|
|
cwd: repoRoot,
|
|
timeout: 15000,
|
|
maxBuffer: 10 * 1024 * 1024
|
|
});
|
|
const payload = JSON.parse(result.stdout);
|
|
assert.equal(payload.status, "published");
|
|
assert.equal(payload.services[0].imageTag, shortCommitId);
|
|
assert.equal(payload.services[1].imageTag, reusedTag);
|
|
assert.equal(payload.services[1].publishState, "reused");
|
|
assert.equal(payload.services[1].reusedFrom, `catalog:${reusedTag}`);
|
|
});
|
|
|
|
test("v02 refresh accepts env-reuse device-pod image tag from environment hash", async () => {
|
|
const commitId = await git(["rev-parse", "HEAD"]);
|
|
const environmentInputHash = "14847b0fe65d3d99a8bad2553c67c8660e24fba7e065eb122dba0de83e471823";
|
|
const envTag = `env-${environmentInputHash.slice(0, 12)}`;
|
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-env-reuse-"));
|
|
const reportPath = path.join(tempDir, "v02-artifacts.json");
|
|
await writeFile(reportPath, `${JSON.stringify({
|
|
reportVersion: "v1",
|
|
taskId: "v02-artifact-publish",
|
|
commitId,
|
|
artifactPublish: {
|
|
status: "published",
|
|
mode: "publish",
|
|
sourceCommitId: commitId,
|
|
registryPrefix: "127.0.0.1:5000/hwlab",
|
|
serviceCount: V02_SERVICE_IDS.length,
|
|
requiredServiceCount: V02_SERVICE_IDS.length,
|
|
disabledServiceCount: 0,
|
|
publishedCount: V02_SERVICE_IDS.length,
|
|
reusedCount: 0,
|
|
serviceInventory: {
|
|
version: "v2",
|
|
serviceCount: V02_SERVICE_IDS.length,
|
|
requiredServiceCount: V02_SERVICE_IDS.length,
|
|
disabledServiceCount: 0,
|
|
requiredServiceIds: V02_SERVICE_IDS,
|
|
disabledServiceIds: [],
|
|
services: V02_SERVICE_IDS.map((serviceId) => ({
|
|
serviceId,
|
|
publishEnabled: true,
|
|
artifactRequired: true,
|
|
artifactScope: "required",
|
|
runtimeKind: serviceId === "hwlab-device-pod" ? "env-reuse" : "node-command",
|
|
implementationState: "repo-entrypoint",
|
|
sourceState: "source-present",
|
|
entrypoint: `cmd/${serviceId}/main.ts`,
|
|
disabledReason: null
|
|
}))
|
|
},
|
|
publishPlan: {
|
|
version: "v2",
|
|
services: V02_SERVICE_IDS.map((serviceId, index) => ({
|
|
serviceId,
|
|
required: true,
|
|
digest: digestFor(index),
|
|
image: serviceId === "hwlab-device-pod"
|
|
? `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`
|
|
: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
|
|
imageTag: serviceId === "hwlab-device-pod" ? envTag : commitId
|
|
}))
|
|
},
|
|
services: V02_SERVICE_IDS.map((serviceId, index) => {
|
|
if (serviceId !== "hwlab-device-pod") {
|
|
return {
|
|
serviceId,
|
|
status: "published",
|
|
artifactRequired: true,
|
|
commitId,
|
|
sourceCommitId: commitId,
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
|
|
imageTag: commitId,
|
|
digest: digestFor(index),
|
|
repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`
|
|
};
|
|
}
|
|
return {
|
|
serviceId,
|
|
status: "published",
|
|
artifactRequired: true,
|
|
commitId,
|
|
sourceCommitId: commitId,
|
|
image: `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`,
|
|
imageTag: envTag,
|
|
digest: digestFor(index),
|
|
repositoryDigest: `127.0.0.1:5000/hwlab/hwlab-device-pod-env@${digestFor(index)}`,
|
|
runtimeMode: "env-reuse-git-mirror-checkout",
|
|
envReuse: true,
|
|
environmentImage: `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`,
|
|
environmentDigest: digestFor(index),
|
|
environmentInputHash,
|
|
codeInputHash: "96cd701e01ab9fab8d4daafd30b2206f65f5e72d23adb72dc4ad23fd9310f97e",
|
|
bootRepo: "git@github.com:pikasTech/HWLAB.git",
|
|
bootCommit: commitId,
|
|
bootSh: "deploy/runtime/boot/hwlab-device-pod.sh"
|
|
};
|
|
})
|
|
}
|
|
}, null, 2)}\n`);
|
|
|
|
const result = await execFileAsync(process.execPath, [
|
|
"scripts/refresh-artifact-catalog.mjs",
|
|
"--lane",
|
|
"v02",
|
|
"--image-tag-mode",
|
|
"full",
|
|
"--target-ref",
|
|
"HEAD",
|
|
"--catalog-path",
|
|
path.join(tempDir, "artifact-catalog.v02.json"),
|
|
"--publish-report",
|
|
reportPath,
|
|
"--no-write"
|
|
], {
|
|
cwd: repoRoot,
|
|
timeout: 15000,
|
|
maxBuffer: 10 * 1024 * 1024
|
|
});
|
|
const payload = JSON.parse(result.stdout);
|
|
const devicePod = payload.services.find((service) => service.serviceId === "hwlab-device-pod");
|
|
assert.equal(payload.status, "published");
|
|
assert.equal(devicePod.imageTag, envTag);
|
|
assert.equal(devicePod.runtimeMode, "env-reuse-git-mirror-checkout");
|
|
assert.equal(devicePod.environmentInputHash, environmentInputHash);
|
|
assert.equal(devicePod.bootCommit, commitId);
|
|
assert.equal(devicePod.bootSh, "deploy/runtime/boot/hwlab-device-pod.sh");
|
|
});
|