fix: keep live build metadata fresh

This commit is contained in:
Code Queue Review
2026-05-24 00:26:58 +00:00
parent 48dfbf9e7f
commit 0c481c6dc5
3 changed files with 381 additions and 58 deletions
+213 -39
View File
@@ -421,7 +421,7 @@ async function buildLiveBuildsPayload(options = {}) {
desiredStateSource: metadata.deployManifest.status === "ok" ? LIVE_BUILD_METADATA_PATHS.deployManifest : "unavailable",
artifactCatalogSource: metadata.artifactCatalog.status === "ok" ? LIVE_BUILD_METADATA_PATHS.artifactCatalog : "unavailable",
artifactReportSource: metadata.artifactReport.status === "ok" ? LIVE_BUILD_METADATA_PATHS.artifactReport : "unavailable",
note: "Each row prefers current live service health build metadata and falls back only to repo-owned deploy/artifact metadata; observedAt is not used as build time."
note: "Each row prefers current live service health build metadata and falls back to repo-owned deploy/artifact metadata only when the live tag/revision matches; observedAt is not used as build time."
},
latest,
counts: {
@@ -445,7 +445,8 @@ async function observeLiveBuildService(service, options) {
});
}
const metadataRecord = liveBuildRecordFromMetadata(service);
const metadataRecords = liveBuildRecordsFromMetadata(service);
const metadataRecord = metadataRecords[0] ?? null;
if (service.serviceId === CLOUD_API_SERVICE_ID) {
const health = await buildHealthPayload(options);
return liveBuildRecordFromHealth(service, {
@@ -453,22 +454,29 @@ async function observeLiveBuildService(service, options) {
status: 200,
url: "/health/live",
payload: health
}, metadataRecord);
}, metadataRecords);
}
const baseUrl = String(options.env?.[service.urlEnv] || service.defaultUrl || "").replace(/\/+$/u, "");
if (!baseUrl) {
return metadataRecord ?? unavailableLiveBuildRecord(service, {
reasonCode: "service_url_unavailable",
reason: `${service.serviceId} 没有配置 live health URL`
});
return metadataRecord
? metadataRecordWithHealthGap(metadataRecord, {
service,
healthUrl: null,
httpStatus: null,
reason: `${service.serviceId} 没有配置 live health URL`
})
: unavailableLiveBuildRecord(service, {
reasonCode: "service_url_unavailable",
reason: `${service.serviceId} 没有配置 live health URL`
});
}
const healthPath = service.healthPath ?? "/health/live";
return liveBuildRecordFromHealth(
service,
await fetchServiceHealth(`${baseUrl}${healthPath}`, options.fetchImpl ?? fetch),
metadataRecord
metadataRecords
);
}
@@ -515,11 +523,14 @@ async function fetchServiceHealth(url, fetchImpl) {
}
}
function liveBuildRecordFromHealth(service, result, metadataRecord = null) {
function liveBuildRecordFromHealth(service, result, metadataRecords = []) {
const candidates = Array.isArray(metadataRecords) ? metadataRecords : [metadataRecords].filter(Boolean);
const metadataRecord = candidates[0] ?? null;
if (!result.ok) {
const reason = healthUnavailableReason(service, result.reason ?? `${service.serviceId} health unavailable`);
return metadataRecord
? metadataRecordWithHealthGap(metadataRecord, {
service,
healthUrl: result.url,
httpStatus: result.status,
reason
@@ -550,9 +561,24 @@ function liveBuildRecordFromHealth(service, result, metadataRecord = null) {
const imageReference = payload.image?.reference ?? payload.image ?? "unknown";
const commitId = payload.commit?.id ?? payload.revision ?? "unknown";
const liveIdentity = liveBuildIdentityFromHealth({ payload, imageReference, commitId });
const createdAt = normalizeHealthBuildTime(payload);
if (!createdAt && metadataRecord?.build?.createdAt) {
return mergeLiveHealthOntoMetadataRecord(metadataRecord, {
if (createdAt) {
return liveBuildHealthPayloadRecord(service, result, {
payload,
imageReference,
commitId,
createdAt,
metadataSource: payload.build?.metadataSource ?? "health:build.createdAt",
unavailableReason: null
});
}
const matched = selectMatchingMetadataRecord(liveIdentity, candidates);
if (matched.record?.build?.createdAt) {
return mergeLiveHealthOntoMetadataRecord(matched.record, {
match: matched.match,
liveIdentity,
service,
result,
payload,
@@ -560,6 +586,33 @@ function liveBuildRecordFromHealth(service, result, metadataRecord = null) {
commitId
});
}
const mismatch = matched.match ?? liveBuildMetadataMatch(liveIdentity, metadataRecord);
const unavailableReason = matched.record
? `构建时间不可用:当前 live tag/revision 已匹配 repo-owned metadata,但该 metadata 没有构建时间(live tag ${liveIdentity.imageTag}live revision ${liveIdentity.revision}metadata tag ${matched.record.image.tag}metadata revision ${matched.record.revision},来源 ${matched.record.build.metadataSource}`
: metadataRecord
? `构建时间不可用:当前 live tag/revision 与 repo-owned artifact metadata 不匹配,未使用旧构建时间(live tag ${liveIdentity.imageTag}live revision ${liveIdentity.revision}metadata tag ${metadataRecord.image.tag}metadata revision ${metadataRecord.revision}`
: "构建时间不可用:health payload 缺少 build.createdAt / image.createdAt / buildCreatedAt,且 repo-owned artifact/catalog/deployment env 未提供可匹配的构建时间";
return liveBuildHealthPayloadRecord(service, result, {
payload,
imageReference,
commitId,
createdAt: null,
metadataSource: matched.record ? "live-health:matched-metadata-without-build-time" : metadataRecord ? "live-health:metadata-mismatch" : "unavailable",
unavailableReason,
metadataMismatch: mismatch
});
}
function liveBuildHealthPayloadRecord(service, result, {
payload,
imageReference,
commitId,
createdAt,
metadataSource,
unavailableReason,
metadataMismatch = null
}) {
return {
serviceId: service.serviceId,
name: service.serviceName,
@@ -571,8 +624,9 @@ function liveBuildRecordFromHealth(service, result, metadataRecord = null) {
build: {
createdAt,
source: payload.build?.source ?? payload.commit?.source ?? "health-payload",
metadataSource: payload.build?.metadataSource ?? (createdAt ? "health:build.createdAt" : "unavailable"),
unavailableReason: createdAt ? null : "构建时间不可用:health payload 缺少 build.createdAt / image.createdAt / buildCreatedAt"
metadataSource,
unavailableReason,
metadataMismatch
},
image: {
reference: imageReference,
@@ -587,19 +641,24 @@ function liveBuildRecordFromHealth(service, result, metadataRecord = null) {
};
}
function metadataRecordWithHealthGap(metadataRecord, { healthUrl, httpStatus, reason }) {
function metadataRecordWithHealthGap(metadataRecord, { service, healthUrl, httpStatus, reason }) {
const metadataTag = metadataRecord.image?.tag ?? "unknown";
const metadataRevision = metadataRecord.revision ?? metadataRecord.commit?.id ?? "unknown";
return {
...metadataRecord,
healthUrl,
httpStatus,
status: "build_time_unavailable",
build: {
...metadataRecord.build,
liveHealthMissingReason: `live health 不可用,已使用 repo-owned artifact metadata${reason}`
createdAt: null,
metadataSource: "live-health-unavailable",
unavailableReason: `构建时间不可用:当前 live health 不可用,无法确认 ${service.serviceId} live tag/revision 是否匹配 repo-owned artifact metadata,未使用可能过期的构建时间(metadata tag ${metadataTag}metadata revision ${metadataRevision});${reason}`
}
};
}
function mergeLiveHealthOntoMetadataRecord(metadataRecord, { service, result, payload, imageReference, commitId }) {
function mergeLiveHealthOntoMetadataRecord(metadataRecord, { match, liveIdentity, service, result, payload, imageReference, commitId }) {
return {
...metadataRecord,
serviceId: service.serviceId,
@@ -612,7 +671,8 @@ function mergeLiveHealthOntoMetadataRecord(metadataRecord, { service, result, pa
...metadataRecord.build,
metadataSource: metadataRecord.build.metadataSource,
unavailableReason: null,
liveHealthMissingReason: "health payload 缺少 build.createdAt / image.createdAt / buildCreatedAt,已使用 repo-owned artifact metadata"
liveMetadataMatch: match,
liveHealthMissingReason: `health payload 缺少 build.createdAt / image.createdAt / buildCreatedAt,已使用与 live tag/revision 匹配的 repo-owned artifact metadatalive tag ${liveIdentity.imageTag}`
},
image: {
reference: imageReference !== "unknown" ? imageReference : metadataRecord.image.reference,
@@ -627,43 +687,157 @@ function mergeLiveHealthOntoMetadataRecord(metadataRecord, { service, result, pa
};
}
function liveBuildRecordFromMetadata(service) {
const createdAt = normalizeIsoTimestamp(
service.artifact?.buildCreatedAt ??
service.report?.buildCreatedAt ??
service.deploy?.env?.HWLAB_BUILD_CREATED_AT ??
null
);
if (!createdAt) return null;
function liveBuildIdentityFromHealth({ payload, imageReference, commitId }) {
return {
imageReference,
imageTag: payload.image?.tag ?? imageTagFromReference(imageReference) ?? "unknown",
commitId,
revision: payload.revision ?? commitId,
digest: payload.image?.digest ?? "unknown"
};
}
const imageReference = service.artifact?.image ?? service.deploy?.image ?? "unknown";
const commitId = service.artifact?.commitId ?? service.report?.sourceCommitId ?? service.deploy?.env?.HWLAB_COMMIT_ID ?? imageTagFromReference(imageReference) ?? "unknown";
const digest = service.artifact?.digest ?? service.report?.digest ?? service.deploy?.env?.HWLAB_IMAGE_DIGEST ?? "unknown";
const source = service.artifact?.buildSource ?? service.report?.buildSource ?? service.deploy?.env?.HWLAB_BUILD_SOURCE ?? "repo-owned-artifact-metadata";
function selectMatchingMetadataRecord(liveIdentity, metadataRecords) {
let firstMatched = null;
for (const record of metadataRecords) {
const match = liveBuildMetadataMatch(liveIdentity, record);
if (!match.matched) continue;
if (record.build?.createdAt) return { record, match };
firstMatched ??= { record, match };
}
if (firstMatched) return firstMatched;
return {
record: null,
match: metadataRecords.length ? liveBuildMetadataMatch(liveIdentity, metadataRecords[0]) : null
};
}
function liveBuildMetadataMatch(liveIdentity, metadataRecord) {
if (!metadataRecord) {
return {
matched: false,
matchedValues: [],
live: liveBuildIdentitySummary(liveIdentity),
metadata: null
};
}
const liveValues = uniqueStrings([
liveIdentity.imageTag,
imageTagFromReference(liveIdentity.imageReference),
liveIdentity.revision,
liveIdentity.commitId,
liveIdentity.digest
]).filter((value) => value !== "unknown");
const metadataValues = uniqueStrings([
metadataRecord.image?.tag,
imageTagFromReference(metadataRecord.image?.reference),
metadataRecord.revision,
metadataRecord.commit?.id,
metadataRecord.image?.digest
]).filter((value) => value !== "unknown" && value !== "not_published");
const matchedValues = liveValues.filter((liveValue) =>
metadataValues.some((metadataValue) => liveBuildIdentityEquivalent(liveValue, metadataValue))
);
return {
matched: matchedValues.length > 0,
matchedValues,
live: liveBuildIdentitySummary(liveIdentity),
metadata: {
imageTag: metadataRecord.image?.tag ?? "unknown",
revision: metadataRecord.revision ?? "unknown",
commitId: metadataRecord.commit?.id ?? "unknown",
digest: metadataRecord.image?.digest ?? "unknown"
}
};
}
function liveBuildIdentitySummary(identity) {
return {
imageTag: identity?.imageTag ?? "unknown",
revision: identity?.revision ?? "unknown",
commitId: identity?.commitId ?? "unknown",
digest: identity?.digest ?? "unknown"
};
}
function liveBuildIdentityEquivalent(left, right) {
const a = String(left ?? "").trim();
const b = String(right ?? "").trim();
if (!a || !b || a === "unknown" || b === "unknown") return false;
if (a === b) return true;
if (a.startsWith("sha256:") || b.startsWith("sha256:")) return false;
return a.length >= 7 && b.length >= 7 && (a.startsWith(b) || b.startsWith(a));
}
function liveBuildRecordsFromMetadata(service) {
return [
liveBuildRecordFromMetadataSource(service, "artifact"),
liveBuildRecordFromMetadataSource(service, "report"),
liveBuildRecordFromMetadataSource(service, "deploy")
].filter(Boolean);
}
function liveBuildRecordFromMetadata(service) {
return liveBuildRecordsFromMetadata(service)[0] ?? null;
}
function liveBuildRecordFromMetadataSource(service, sourceKind) {
const source = sourceKind === "artifact"
? service.artifact
: sourceKind === "report"
? service.report
: service.deploy;
if (!source) return null;
const createdAt = normalizeIsoTimestamp(
sourceKind === "deploy" ? source.env?.HWLAB_BUILD_CREATED_AT : source.buildCreatedAt
);
const imageReference = sourceKind === "deploy"
? source.env?.HWLAB_IMAGE ?? source.image ?? "unknown"
: source.image ?? service.deploy?.image ?? "unknown";
const commitId = sourceKind === "artifact"
? source.commitId ?? imageTagFromReference(imageReference) ?? "unknown"
: sourceKind === "report"
? source.sourceCommitId ?? source.commitId ?? imageTagFromReference(imageReference) ?? "unknown"
: source.env?.HWLAB_COMMIT_ID ?? imageTagFromReference(imageReference) ?? "unknown";
const digest = sourceKind === "deploy"
? source.env?.HWLAB_IMAGE_DIGEST ?? "unknown"
: source.digest ?? "unknown";
const buildSource = sourceKind === "deploy"
? source.env?.HWLAB_BUILD_SOURCE ?? "repo-owned-deployment-env"
: source.buildSource ?? "repo-owned-artifact-metadata";
const imageTag = sourceKind === "deploy"
? source.env?.HWLAB_IMAGE_TAG ?? imageTagFromReference(imageReference) ?? "unknown"
: source.imageTag ?? imageTagFromReference(imageReference) ?? "unknown";
const revision = sourceKind === "deploy"
? source.env?.HWLAB_REVISION ?? commitId
: commitId;
return {
serviceId: service.serviceId,
name: service.serviceName,
kind: "hwlab",
status: "ok",
status: createdAt ? "ok" : "build_time_unavailable",
healthUrl: null,
httpStatus: null,
desiredState: desiredStateSummary(service),
build: {
createdAt,
source,
metadataSource: artifactMetadataSource(service),
unavailableReason: null
source: buildSource,
metadataSource: artifactMetadataSource(sourceKind),
unavailableReason: createdAt
? null
: `构建时间不可用:repo-owned ${artifactMetadataSource(sourceKind)} 缺少构建时间`
},
image: {
reference: imageReference,
tag: service.artifact?.imageTag ?? service.deploy?.env?.HWLAB_IMAGE_TAG ?? imageTagFromReference(imageReference) ?? "unknown",
tag: imageTag,
digest
},
commit: {
id: commitId,
source: service.artifact ? "artifact-catalog" : service.report ? "artifact-report" : "deploy-desired-state"
source: sourceKind === "artifact" ? "artifact-catalog" : sourceKind === "report" ? "artifact-report" : "deploy-desired-state"
},
revision: service.deploy?.env?.HWLAB_REVISION ?? commitId
revision
};
}
@@ -677,9 +851,9 @@ function desiredStateSummary(service) {
};
}
function artifactMetadataSource(service) {
if (service.artifact?.buildCreatedAt) return "artifact-catalog:buildCreatedAt";
if (service.report?.buildCreatedAt) return "artifact-report:buildCreatedAt";
function artifactMetadataSource(sourceKind) {
if (sourceKind === "artifact") return "artifact-catalog:buildCreatedAt";
if (sourceKind === "report") return "artifact-report:buildCreatedAt";
return "deploy-env:HWLAB_BUILD_CREATED_AT";
}
+162 -19
View File
@@ -196,14 +196,25 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
["/hwlab-agent-mgr/health/live", {
serviceId: "hwlab-agent-mgr",
status: "ok",
revision: "mgrabcdef123456",
commit: { id: "mgrabcdef123456", source: "test" },
revision: "48dfbf9",
commit: { id: "48dfbf9", source: "test" },
image: {
reference: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:mgrabcd",
tag: "mgrabcd",
reference: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9",
tag: "48dfbf9",
digest: "sha256:" + "2".repeat(64)
}
}],
["/hwlab-agent-skills/health/live", {
serviceId: "hwlab-agent-skills",
status: "ok",
revision: "skillsabcdef123456",
commit: { id: "skillsabcdef123456", source: "test" },
image: {
reference: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
tag: "skillsabcd",
digest: "sha256:" + "5".repeat(64)
}
}],
["/external/health", {
serviceId: "postgres",
status: "ok",
@@ -226,12 +237,27 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
},
{
serviceId: "hwlab-agent-mgr",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:mgrcat",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe",
namespace: "hwlab-dev",
profile: "dev",
healthPath: "/health/live",
replicas: 1
},
{
serviceId: "hwlab-agent-skills",
image: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
namespace: "hwlab-dev",
profile: "dev",
healthPath: "/health/live",
replicas: 1,
env: {
HWLAB_COMMIT_ID: "skillsabcdef123456",
HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
HWLAB_IMAGE_TAG: "skillsabcd",
HWLAB_BUILD_CREATED_AT: "2026-05-23T05:30:00.000Z",
HWLAB_BUILD_SOURCE: "deploy-env-test"
}
},
{
serviceId: "hwlab-agent-worker",
image: "127.0.0.1:5000/hwlab/hwlab-agent-worker:workcat",
@@ -262,9 +288,9 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
services: [
{
serviceId: "hwlab-agent-mgr",
commitId: "mgrcatabcdef123456",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:mgrcat",
imageTag: "mgrcat",
commitId: "3df89fe",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe",
imageTag: "3df89fe",
digest: "sha256:" + "3".repeat(64),
namespace: "hwlab-dev",
profile: "dev",
@@ -272,6 +298,18 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
buildCreatedAt: "2026-05-23T04:20:00.000Z",
buildSource: "artifact-catalog-test"
},
{
serviceId: "hwlab-agent-skills",
commitId: "skillsabcdef123456",
image: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
imageTag: "skillsabcd",
digest: "sha256:" + "5".repeat(64),
namespace: "hwlab-dev",
profile: "dev",
healthPath: "/health/live",
buildCreatedAt: null,
buildSource: null
},
{
serviceId: "hwlab-extra-lab",
commitId: "extracatabcdef123456",
@@ -311,7 +349,7 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
HWLAB_TUNNEL_CLIENT_URL: "http://live.test/missing-tunnel-client",
HWLAB_EDGE_PROXY_URL: "http://live.test/external",
HWLAB_CLI_URL: "http://live.test/missing-cli",
HWLAB_AGENT_SKILLS_URL: "http://live.test/missing-agent-skills",
HWLAB_AGENT_SKILLS_URL: "http://live.test/hwlab-agent-skills",
HWLAB_EXTRA_LAB_URL: "http://live.test/missing-extra-lab"
},
liveBuildMetadata,
@@ -344,19 +382,31 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.contractVersion, "live-builds-v1");
assert.equal(payload.latest.serviceId, "hwlab-agent-mgr");
assert.equal(payload.latest.build.createdAt, "2026-05-23T04:20:00.000Z");
assert.equal(payload.latest.image.tag, "mgrabcd");
assert.equal(payload.latest.commit.id, "mgrabcdef123456");
assert.equal(payload.latest.build.metadataSource, "artifact-catalog:buildCreatedAt");
assert.match(payload.latest.build.liveHealthMissingReason, /health payload 缺少 build\.createdAt/u);
assert.equal(payload.latest.serviceId, "hwlab-agent-skills");
assert.equal(payload.latest.build.createdAt, "2026-05-23T05:30:00.000Z");
assert.equal(payload.latest.image.tag, "skillsabcd");
assert.equal(payload.latest.commit.id, "skillsabcdef123456");
assert.equal(payload.latest.build.metadataSource, "deploy-env:HWLAB_BUILD_CREATED_AT");
assert.equal(payload.latest.build.liveMetadataMatch.metadata.imageTag, "skillsabcd");
assert.match(payload.latest.build.liveHealthMissingReason, /匹配的 repo-owned artifact metadata/u);
assert.equal(payload.counts.total, 15);
assert.equal(payload.counts.external, 2);
assert.equal(payload.counts.withBuildTime, 4);
assert.equal(payload.counts.withBuildTime, 3);
const manager = payload.services.find((service) => service.serviceId === "hwlab-agent-mgr");
assert.equal(manager.build.createdAt, null);
assert.equal(manager.image.tag, "48dfbf9");
assert.equal(manager.revision, "48dfbf9");
assert.equal(manager.build.metadataSource, "live-health:metadata-mismatch");
assert.match(manager.build.unavailableReason, /构建时间不可用:当前 live tag\/revision 与 repo-owned artifact metadata 不匹配/u);
assert.match(manager.build.unavailableReason, /live tag 48dfbf9/u);
assert.match(manager.build.unavailableReason, /metadata tag 3df89fe/u);
const extra = payload.services.find((service) => service.serviceId === "hwlab-extra-lab");
assert.equal(extra.build.createdAt, "2026-05-23T03:00:00.000Z");
assert.match(extra.build.liveHealthMissingReason, /live health 不可用/u);
assert.ok(payload.services.some((service) => service.serviceId === "hwlab-agent-worker" && /构建时间不可用:hwlab-agent-worker 当前 hwlab-dev desired replicas=0/u.test(service.build.unavailableReason)));
assert.equal(extra.build.createdAt, null);
assert.match(extra.build.unavailableReason, /当前 live health 不可用/u);
const worker = payload.services.find((service) => service.serviceId === "hwlab-agent-worker");
assert.equal(worker.build.createdAt, null);
assert.match(worker.build.unavailableReason, /构建时间不可用:当前 live health 不可用/u);
assert.match(worker.build.unavailableReason, /hwlab-agent-worker 当前 hwlab-dev desired replicas=0/u);
assert.ok(payload.services.some((service) => service.kind === "external" && service.serviceId === "hwlab-edge-proxy" && service.healthUrl.endsWith("/health")));
assert.ok(payload.services.some((service) => service.kind === "external" && service.serviceId === "hwlab-frpc" && service.image.tag === "v0.68.1"));
} finally {
@@ -366,6 +416,99 @@ test("cloud api aggregates live HWLAB build times from health and repo-owned art
}
});
test("cloud api uses matching report metadata when catalog has the live tag but no build time", async () => {
const liveBuildMetadata = {
deployManifest: {
services: [
{
serviceId: "hwlab-agent-mgr",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9",
namespace: "hwlab-dev",
profile: "dev",
healthPath: "/health/live",
replicas: 1
}
]
},
artifactCatalog: {
services: [
{
serviceId: "hwlab-agent-mgr",
commitId: "48dfbf9",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9",
imageTag: "48dfbf9",
digest: "not_published",
namespace: "hwlab-dev",
profile: "dev",
healthPath: "/health/live",
buildCreatedAt: null,
buildSource: null
}
]
},
artifactReport: {
artifactPublish: {
services: [
{
serviceId: "hwlab-agent-mgr",
sourceCommitId: "48dfbf9abcdef",
image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9",
imageTag: "48dfbf9",
digest: "sha256:" + "6".repeat(64),
namespace: "hwlab-dev",
profile: "dev",
healthPath: "/health/live",
buildCreatedAt: "2026-05-24T06:00:00.000Z",
buildSource: "artifact-report-test"
}
]
}
}
};
const server = createCloudApiServer({
env: {
PATH: "",
HWLAB_AGENT_MGR_URL: "http://live.test/hwlab-agent-mgr"
},
liveBuildMetadata,
fetchImpl: async () => ({
ok: true,
status: 200,
async text() {
return JSON.stringify({
serviceId: "hwlab-agent-mgr",
status: "ok",
revision: "48dfbf9abcdef",
commit: { id: "48dfbf9abcdef", source: "test" },
image: {
reference: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9",
tag: "48dfbf9",
digest: "sha256:" + "6".repeat(64)
}
});
}
})
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const response = await fetch(`http://127.0.0.1:${port}/v1/live-builds`);
assert.equal(response.status, 200);
const payload = await response.json();
const manager = payload.services.find((service) => service.serviceId === "hwlab-agent-mgr");
assert.equal(manager.build.createdAt, "2026-05-24T06:00:00.000Z");
assert.equal(manager.build.metadataSource, "artifact-report:buildCreatedAt");
assert.equal(manager.build.liveMetadataMatch.metadata.imageTag, "48dfbf9");
assert.equal(payload.latest.serviceId, "hwlab-agent-mgr");
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api health reports DB env presence and live connection classification without leaking values", async () => {
const originalUrl = process.env.HWLAB_CLOUD_DB_URL;
const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE;
+6
View File
@@ -915,9 +915,15 @@ assert.match(app, /formatBeijingTime/);
assert.match(app, /Asia\/Shanghai/);
assert.match(app, /最新镜像构建时间/);
assert.match(app, /构建时间不可用/);
assert.match(app, /revision \$\{shortToken\(latest\.revision\)\}/);
assert.match(app, /revision \$\{shortToken\(service\.revision\)\}/);
assert.match(app, /liveBuildSourceLabel/);
assert.match(app, /liveBuildDesiredStateText/);
assert.match(app, /外部镜像或非 HWLAB 构建产物/);
assert.match(styles, /\.live-build-summary-label\s*{[^}]*min-width:\s*0;[^}]*max-width:\s*100%;[^}]*overflow-wrap:\s*anywhere;/s);
assert.match(styles, /\.live-build-list\s*{[^}]*max-height:\s*min\(280px,\s*42dvh\);[^}]*min-width:\s*0;[^}]*overflow:\s*auto;/s);
assert.match(styles, /\.live-build-row-head\s*{[^}]*grid-template-columns:\s*minmax\(0,\s*0\.8fr\)\s*minmax\(0,\s*1\.2fr\);/s);
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.live-build-row-head\s*{[\s\S]*?grid-template-columns:\s*1fr;/);
assert.match(styles, /\.live-build-meta span\s*{[^}]*overflow-wrap:\s*anywhere;/s);
assert.match(app, /fetchJson\("\/health\/live"\)/);
assert.match(app, /callRpc\("system\.health"\)/);