Files
pikasTech-HWLAB/scripts/refresh-artifact-catalog.test.mjs
T
2026-07-21 17:18:47 +02:00

651 lines
23 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";
import { writeStructuredFile } from "./src/structured-config.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-gateway",
"hwlab-edge-proxy",
"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")}`;
}
function envInputHashFor(index) {
return String(index + 1).padStart(64, "a");
}
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: "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: "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.yaml 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: "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.publishedCount, 1);
assert.equal(payload.reusedCount, SERVICE_IDS.length - 1);
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 current runtime service full image tags", async () => {
const commitId = await git(["rev-parse", "HEAD"]);
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-runtime-services-"));
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: "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: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
imageTag: commitId
}))
},
services: V02_SERVICE_IDS.map((serviceId, index) => ({
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)}`
}))
}
}, 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);
assert.equal(payload.status, "published");
assert.equal(payload.publishedCount, V02_SERVICE_IDS.length);
assert.equal(payload.reusedCount, 0);
assert.deepEqual(payload.services.map((service) => service.serviceId), V02_SERVICE_IDS);
for (const service of payload.services) {
assert.equal(service.imageTag, commitId);
}
});
test("v03 refresh accepts runtime lane config and env-reuse image tags", async () => {
const commitId = await git(["rev-parse", "HEAD"]);
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v03-runtime-services-"));
const deployPath = path.join(tempDir, "deploy.yaml");
const catalogPath = path.join(tempDir, "artifact-catalog.v03.json");
const reportPath = path.join(tempDir, "v03-artifacts.json");
const serviceIds = [...V02_SERVICE_IDS, "hwlab-tasktree-api", "hwlab-tasktree-worker"];
await writeStructuredFile(tempDir, deployPath, {
environment: "dev",
namespace: "hwlab-dev",
endpoint: "http://74.48.78.17:16667",
profiles: { dev: { enabled: true }, prod: { enabled: false } },
lanes: {
v03: {
namespace: "hwlab-v03",
endpoint: "https://hwlab-v03.74-48-78-17.nip.io",
envReuseServices: serviceIds,
serviceDeclarations: Object.fromEntries(serviceIds.map((serviceId) => [serviceId, {
runtimeKind: "node-command",
entrypoint: `cmd/${serviceId}/main.ts`,
componentPaths: [`cmd/${serviceId}/`]
}])),
bootScripts: Object.fromEntries(serviceIds.map((serviceId) => [serviceId, `deploy/runtime/boot/${serviceId}.sh`]))
}
},
services: SERVICE_IDS.map((serviceId) => ({ serviceId }))
});
await writeFile(catalogPath, `${JSON.stringify({
catalogVersion: "v1",
kind: "hwlab-artifact-catalog",
environment: "v03",
profile: "v03",
namespace: "hwlab-v03",
endpoint: "https://hwlab-v03.74-48-78-17.nip.io",
commitId,
artifactState: "published",
publish: { ciPublished: true, registryVerified: true, provenance: "previous-run" },
allowedProfiles: ["v03"],
forbiddenProfiles: ["dev", "prod"],
services: V02_SERVICE_IDS.map((serviceId, index) => ({
serviceId,
commitId,
sourceCommitId: commitId,
image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
imageTag: commitId,
digest: digestFor(index),
profile: "v03",
namespace: "hwlab-v03"
}))
}, null, 2)}\n`);
await writeFile(reportPath, `${JSON.stringify({
reportVersion: "v1",
taskId: "v03-artifact-publish",
commitId,
artifactPublish: {
status: "published",
mode: "publish",
sourceCommitId: commitId,
registryPrefix: "127.0.0.1:5000/hwlab",
serviceCount: serviceIds.length,
requiredServiceCount: serviceIds.length,
disabledServiceCount: 0,
publishedCount: serviceIds.length,
reusedCount: 0,
serviceInventory: {
version: "v2",
serviceCount: serviceIds.length,
requiredServiceCount: serviceIds.length,
disabledServiceCount: 0,
requiredServiceIds: serviceIds,
disabledServiceIds: [],
services: serviceIds.map((serviceId) => ({
serviceId,
publishEnabled: true,
artifactRequired: true,
artifactScope: "required",
runtimeKind: "node-command",
implementationState: "repo-entrypoint",
sourceState: "source-present",
entrypoint: `cmd/${serviceId}/main.ts`,
disabledReason: null
}))
},
publishPlan: {
version: "v2",
services: serviceIds.map((serviceId, index) => {
const envTag = `env-${envInputHashFor(index).slice(0, 12)}`;
return {
serviceId,
required: true,
digest: digestFor(index),
image: `127.0.0.1:5000/hwlab/${serviceId}-env:${envTag}`,
imageTag: envTag
};
})
},
services: serviceIds.map((serviceId, index) => {
const environmentInputHash = envInputHashFor(index);
const envTag = `env-${environmentInputHash.slice(0, 12)}`;
const image = `127.0.0.1:5000/hwlab/${serviceId}-env:${envTag}`;
return {
serviceId,
status: "published",
artifactRequired: true,
runtimeMode: "env-reuse-gitea-checkout",
envReuse: true,
commitId,
sourceCommitId: commitId,
image,
imageTag: envTag,
digest: digestFor(index),
repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}-env@${digestFor(index)}`,
environmentImage: image,
environmentDigest: digestFor(index),
environmentInputHash,
codeInputHash: envInputHashFor(index + serviceIds.length),
bootRepo: "git@github.com:pikasTech/HWLAB.git",
bootCommit: commitId,
bootSh: `deploy/runtime/boot/${serviceId}.sh`
};
})
}
}, null, 2)}\n`);
const result = await execFileAsync(process.execPath, [
"scripts/refresh-artifact-catalog.mjs",
"--lane",
"v03",
"--image-tag-mode",
"full",
"--target-ref",
"HEAD",
"--deploy-config",
deployPath,
"--catalog-path",
catalogPath,
"--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.endpoint, "https://hwlab-v03.74-48-78-17.nip.io");
assert.equal(payload.requiredServiceCount, serviceIds.length);
assert.deepEqual(payload.services.map((service) => service.serviceId), serviceIds);
assert.equal(payload.services.every((service) => service.runtimeMode === "env-reuse-gitea-checkout"), true);
assert.equal(payload.services.every((service) => service.imageTag.startsWith("env-")), true);
});
test("v02 refresh follows the current deploy endpoint instead of the retired 19667 freeze", async () => {
const commitId = await git(["rev-parse", "HEAD"]);
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-https-endpoint-"));
const deployPath = path.join(tempDir, "deploy.yaml");
const catalogPath = path.join(tempDir, "artifact-catalog.v02.json");
await writeStructuredFile(tempDir, deployPath, {
environment: "dev",
namespace: "hwlab-dev",
endpoint: "http://74.48.78.17:16667",
profiles: { dev: { enabled: true }, prod: { enabled: false } },
lanes: {
v02: {
namespace: "hwlab-v02",
endpoint: "https://hwlab.74-48-78-17.nip.io",
envReuseServices: V02_SERVICE_IDS,
serviceDeclarations: Object.fromEntries(V02_SERVICE_IDS.map((serviceId) => [serviceId, {
runtimeKind: "node-command",
entrypoint: `cmd/${serviceId}/main.ts`,
componentPaths: [`cmd/${serviceId}/`]
}])),
bootScripts: Object.fromEntries(V02_SERVICE_IDS.map((serviceId) => [serviceId, `deploy/runtime/boot/${serviceId}.sh`]))
}
},
services: SERVICE_IDS.map((serviceId) => ({ serviceId }))
});
await writeFile(catalogPath, `${JSON.stringify({
catalogVersion: "v1",
kind: "hwlab-artifact-catalog",
environment: "v02",
profile: "v02",
namespace: "hwlab-v02",
endpoint: "http://74.48.78.17:19667",
commitId,
artifactState: "contract-skeleton",
publish: { registryPrefix: "127.0.0.1:5000/hwlab" },
allowedProfiles: ["v02"],
forbiddenProfiles: ["dev", "prod"],
services: V02_SERVICE_IDS.map((serviceId) => ({
serviceId,
profile: "v02",
namespace: "hwlab-v02",
commitId,
sourceCommitId: commitId,
image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
imageTag: commitId,
digest: "not_published"
}))
}, null, 2)}\n`);
const result = await execFileAsync(process.execPath, [
"scripts/refresh-artifact-catalog.mjs",
"--lane",
"v02",
"--image-tag-mode",
"full",
"--target-ref",
"HEAD",
"--deploy-config",
deployPath,
"--catalog-path",
catalogPath,
"--blocked",
"--no-write"
], {
cwd: repoRoot,
timeout: 15000,
maxBuffer: 10 * 1024 * 1024
});
const payload = JSON.parse(result.stdout);
assert.equal(payload.status, "blocked");
assert.equal(payload.endpoint, "https://hwlab.74-48-78-17.nip.io");
});
test("v02 refresh reports current runtime records as reused", async () => {
const commitId = await git(["rev-parse", "HEAD"]);
const previousCommitId = "1234567890abcdef1234567890abcdef12345678";
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-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: 0,
reusedCount: V02_SERVICE_IDS.length,
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: "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,
status: "reused",
digest: digestFor(index),
image: `127.0.0.1:5000/hwlab/${serviceId}:${previousCommitId}`,
imageTag: previousCommitId
}))
},
services: V02_SERVICE_IDS.map((serviceId, index) => ({
serviceId,
status: "reused",
artifactRequired: true,
commitId: previousCommitId,
sourceCommitId: previousCommitId,
image: `127.0.0.1:5000/hwlab/${serviceId}:${previousCommitId}`,
imageTag: previousCommitId,
digest: digestFor(index),
repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`,
buildBackend: "reused-catalog",
reusedFrom: `catalog:${previousCommitId}`
}))
}
}, 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);
assert.equal(payload.status, "published");
assert.equal(payload.publishedCount, 0);
assert.equal(payload.reusedCount, V02_SERVICE_IDS.length);
assert.deepEqual(payload.services.map((service) => service.serviceId), V02_SERVICE_IDS);
for (const service of payload.services) {
assert.equal(service.publishState, "reused");
assert.equal(service.buildBackend, "reused-catalog");
assert.equal(service.sourceCommitId, previousCommitId);
assert.equal(service.imageTag, previousCommitId);
}
});