Files
browser/platform_web/contracts/operationsConsole.test.ts
T
2026-07-20 16:42:33 +08:00

121 lines
4.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { JobResponse, RunEndpointResponse, ServerInstanceResponse } from "../api/types";
import type { OperationRecord } from "./workspace";
import { jobBuckets, projectOperationForTray, summarizeEndpointOperations, summarizeServerOperations } from "./operationsConsole";
function job(id: string, state: JobResponse["state"], updatedAt: string, serverInstanceId = "server-1"): JobResponse {
return {
id,
serverInstanceId,
runEndpointId: "endpoint-1",
capability: "server.lifecycle.start",
idempotencyKey: `idem-${id}`,
state,
progress: { percent: state === "succeeded" ? 100 : 40 },
retryPolicy: { maxAttempts: 3, initialBackoffSeconds: 1, maxBackoffSeconds: 10 },
attempt: 1,
reconcileCount: 0,
createdAt: updatedAt,
updatedAt
};
}
function server(id: string, state: ServerInstanceResponse["state"], updatedAt: string): ServerInstanceResponse {
return {
id,
pluginId: "scum-server-plugin",
pluginVersion: "1.0.0",
runEndpointId: "endpoint-1",
name: id,
adminUserIds: [],
state,
configVersion: 1,
createdAt: updatedAt,
updatedAt
};
}
describe("operations console contracts", () => {
it("classifies active, retrying, failed, and terminal jobs using Platform state", () => {
const buckets = jobBuckets([
job("queued", "queued", "2026-07-18T10:00:00Z"),
job("retrying", "retrying", "2026-07-18T10:02:00Z"),
job("failed", "failed", "2026-07-18T10:03:00Z"),
job("succeeded", "succeeded", "2026-07-18T10:01:00Z")
]);
expect(buckets.active.map((item) => item.id)).toEqual(["retrying", "queued"]);
expect(buckets.retrying.map((item) => item.id)).toEqual(["retrying"]);
expect(buckets.failed.map((item) => item.id)).toEqual(["failed"]);
expect(buckets.succeeded.map((item) => item.id)).toEqual(["succeeded"]);
});
it("orders failed servers and failed jobs before active and healthy servers", () => {
const summaries = summarizeServerOperations(
[
server("healthy", "running", "2026-07-18T10:04:00Z"),
server("active", "ready", "2026-07-18T10:03:00Z"),
server("job-failed", "stopped", "2026-07-18T10:02:00Z"),
server("server-failed", "failed", "2026-07-18T10:01:00Z")
],
new Map(),
[job("active-job", "running", "2026-07-18T10:05:00Z", "active"), job("failed-job", "failed", "2026-07-18T10:06:00Z", "job-failed")]
);
expect(summaries.map((item) => item.instance.id)).toEqual(["server-failed", "job-failed", "active", "healthy"]);
expect(summaries.find((item) => item.instance.id === "job-failed")?.failedJobs).toBe(1);
});
it("summarizes only endpoint-safe capacity and status values", () => {
const endpoints: RunEndpointResponse[] = [
{
id: "endpoint-1",
displayName: "Primary",
version: "1.0.0",
status: "online",
capabilities: ["server.lifecycle.start"],
capacity: { maxJobs: 4, runningJobs: 2, queuedJobs: 1 },
lastHeartbeatAt: "2026-07-18T10:00:00Z"
},
{
id: "endpoint-2",
displayName: "Secondary",
version: "1.0.0",
status: "degraded",
capabilities: [],
capacity: { maxJobs: 2, runningJobs: 1, queuedJobs: 3 },
lastHeartbeatAt: "2026-07-18T10:00:00Z"
}
];
expect(summarizeEndpointOperations(endpoints)).toEqual({ total: 2, online: 1, degraded: 1, offline: 0, disabled: 0, activeJobs: 3, queuedJobs: 4 });
});
it("projects an allowlisted operation tray shape and protects unsafe target strings", () => {
const operation = {
id: "op-1",
intent: "更新配置",
targetKind: "config",
targetId: "/Users/operator/private.cfg",
requester: "Operator",
status: "failed",
errorReason: "平台拒绝请求:Bearer raw-token /Users/operator/private.cfg PID=4201 unix:///var/run/run.sock",
diagnosticId: "/private/tmp/diagnostic.json",
createdAt: "2026-07-18T10:00:00Z",
updatedAt: "2026-07-18T10:01:00Z",
rawToken: "run-token-should-not-project"
} as OperationRecord & { rawToken: string };
const projected = projectOperationForTray(operation);
expect(projected.targetLabel).toBe("配置 / 受保护目标");
expect(JSON.stringify(projected)).not.toContain("/Users/");
expect(JSON.stringify(projected)).not.toContain("run-token");
expect(JSON.stringify(projected)).not.toContain("raw-token");
expect(JSON.stringify(projected)).not.toContain("/var/run/");
expect(JSON.stringify(projected)).not.toContain("4201");
expect(projected.diagnosticId).toBe("受保护诊断");
expect(projected).not.toHaveProperty("requester");
});
});