314 lines
8.3 KiB
JavaScript
314 lines
8.3 KiB
JavaScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import { classifyWorkbenchLiveStatus } from "../live-status.mjs";
|
||
|
||
const okApi = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
readiness: {
|
||
status: "ok",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true
|
||
}
|
||
},
|
||
runtime: {
|
||
status: "ready",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true
|
||
}
|
||
}
|
||
});
|
||
|
||
const okRest = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "available",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "available"
|
||
}
|
||
}
|
||
});
|
||
|
||
const okM3Control = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "available",
|
||
readiness: {
|
||
status: "ready",
|
||
controlReady: true,
|
||
sourceKind: "DEV-LIVE",
|
||
evidenceLevel: "DEV-LIVE"
|
||
}
|
||
}
|
||
});
|
||
|
||
const okM3Status = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "live",
|
||
sourceKind: "DEV-LIVE",
|
||
summary: "M3 硬件状态与可信持久化均为 DEV-LIVE。"
|
||
}
|
||
});
|
||
|
||
test("classifies fully ready workbench runtime as API 正常", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "pass");
|
||
assert.equal(status.label, "API 正常");
|
||
assert.match(status.detail, /hwlab-cloud-api \/health\/live/u);
|
||
assert.equal(status.internalRawStatuses.includes("degraded"), false);
|
||
});
|
||
|
||
test("classifies concrete API failure with service path and HTTP status", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: false,
|
||
path: "/health/live",
|
||
status: 503,
|
||
error: "/health/live HTTP 503:runtime unavailable"
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.label, "API 错误");
|
||
assert.equal(status.serviceId, "hwlab-cloud-api");
|
||
assert.equal(status.apiPath, "/health/live");
|
||
assert.equal(status.reasonCode, "HTTP 503");
|
||
assert.match(status.detail, /错误:hwlab-cloud-api \/health\/live \/ HTTP 503/u);
|
||
});
|
||
|
||
test("classifies durable runtime degraded as explicit read-only mode", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
runtime: {
|
||
durable: true,
|
||
ready: false,
|
||
liveRuntimeEvidence: false,
|
||
status: "blocked",
|
||
blocker: "runtime_durable_adapter_schema_blocked",
|
||
connection: {
|
||
queryResult: "schema_blocked"
|
||
}
|
||
},
|
||
readiness: {
|
||
status: "degraded",
|
||
ready: false,
|
||
durability: {
|
||
status: "blocked",
|
||
ready: false,
|
||
blocker: "runtime_durable_adapter_schema_blocked",
|
||
blockedLayer: "schema",
|
||
queryResult: "schema_blocked"
|
||
}
|
||
},
|
||
blockerCodes: ["runtime_durable_adapter_schema_blocked"]
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.label, "只读模式");
|
||
assert.equal(status.reasonCode, "runtime_durable_adapter_schema_blocked");
|
||
assert.match(status.detail, /只读:hwlab-cloud-api \/health\/live/u);
|
||
assert.match(status.detail, /schema_blocked/u);
|
||
assert.deepEqual(status.internalRawStatuses.filter((item) => item === "degraded"), ["degraded"]);
|
||
});
|
||
|
||
test("classifies missing probes as unverified instead of degraded", () => {
|
||
const status = classifyWorkbenchLiveStatus({});
|
||
|
||
assert.equal(status.kind, "unverified");
|
||
assert.equal(status.label, "等待验证");
|
||
assert.match(status.detail, /等待验证/u);
|
||
assert.doesNotMatch(`${status.label} ${status.detail}`, /降级/u);
|
||
});
|
||
|
||
test("classifies memory or non-durable runtime as read-only with explicit reason", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: okRest,
|
||
health: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
runtime: {
|
||
adapter: "memory",
|
||
durable: false
|
||
}
|
||
}
|
||
},
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.label, "只读模式");
|
||
assert.equal(status.reasonCode, "runtime_durable_false");
|
||
assert.match(status.detail, /runtime durable=false/u);
|
||
});
|
||
|
||
test("classifies raw degraded without durable/read-only reason as API error", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.label, "API 错误");
|
||
assert.equal(status.reasonCode, "ready_false_without_blocker");
|
||
assert.match(status.detail, /ready=false without a concrete blocker/u);
|
||
assert.equal(status.internalRawStatuses.includes("degraded"), true);
|
||
assert.doesNotMatch(`${status.label} ${status.detail}`, /API 降级/u);
|
||
});
|
||
|
||
test("classifies Code Agent provider timeout or fallback with concrete attribution", () => {
|
||
const providerTimeout = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "blocked",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
error: {
|
||
providerStatus: 504
|
||
},
|
||
summary: "/v1/agent/chat timeout"
|
||
}
|
||
}
|
||
},
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
assert.equal(providerTimeout.kind, "error");
|
||
assert.equal(providerTimeout.apiPath, "POST /v1/agent/chat");
|
||
assert.equal(providerTimeout.reasonCode, "provider_http_504");
|
||
assert.match(providerTimeout.detail, /hwlab-cloud-api POST \/v1\/agent\/chat/u);
|
||
|
||
const fallback = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "available",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "text-chat-only"
|
||
}
|
||
}
|
||
},
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
assert.equal(fallback.kind, "readonly");
|
||
assert.equal(fallback.label, "只读模式");
|
||
assert.equal(fallback.reasonCode, "fallback_text_chat_only");
|
||
});
|
||
|
||
test("classifies M3 control blockers by cloud-api/gateway/box/patch-panel layer", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "blocked",
|
||
readiness: {
|
||
status: "blocked",
|
||
controlReady: false,
|
||
blocker: {
|
||
code: "m3_wiring_missing",
|
||
layer: "hwlab-patch-panel",
|
||
zh: "hwlab-patch-panel route unavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.serviceId, "hwlab-patch-panel");
|
||
assert.equal(status.apiPath, "/v1/m3/io");
|
||
assert.equal(status.reasonCode, "m3_wiring_missing");
|
||
assert.match(status.detail, /hwlab-patch-panel \/v1\/m3\/io/u);
|
||
});
|