Files
pikasTech-unidesk/scripts/src/hwlab-node/web-probe-observe-actions.test.ts
T

182 lines
6.3 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "bun:test";
import { hwlabRuntimeLaneSpecForNode } from "../hwlab-node-lanes";
import { buildWebObserveCommandVisibility, nodeWebProbeRealtimeFanoutProfiles, nodeWebProbeWorkbenchKafkaDebugReplay, nodeWebProbeWorkbenchTraceReadability, resolveWebObserveActionJson } from "./web-probe-observe-actions";
test("realtime fanout runner contract derives topics, groups, and independent capabilities from owning YAML", () => {
const profiles = nodeWebProbeRealtimeFanoutProfiles(hwlabRuntimeLaneSpecForNode("v03", "NC01"));
const expectedKafka = profiles["pure-kafka-live"]?.expectedKafka as Record<string, any>;
assert.deepEqual(expectedKafka.topics, {
stdio: "codex-stdio.raw.v1",
agentrun: "agentrun.event.v1",
hwlab: "hwlab.event.v1",
});
assert.deepEqual(expectedKafka.groups, {
directPublish: "hwlab-v03-agentrun-event-direct-publish",
transactionalProjector: "hwlab-v03-agentrun-event-projector",
liveSse: "hwlab-v03-workbench-live-sse",
});
assert.deepEqual(expectedKafka.capabilities, {
directPublish: true,
liveKafkaSse: true,
kafkaRefreshReplay: true,
transactionalProjector: false,
projectionOutboxRelay: false,
projectionRealtime: false,
});
});
test("Workbench Kafka debug replay runner contract is derived from owning YAML", () => {
assert.deepEqual(nodeWebProbeWorkbenchKafkaDebugReplay(hwlabRuntimeLaneSpecForNode("v03", "NC01")), {
enabled: true,
topic: "hwlab.event.debug.v1",
groupPrefix: "hwlab-v03-workbench-isolated-debug",
completionTimeoutMs: 30_000,
productEventsPath: "/v1/workbench/events",
debugEventsPath: "/v1/workbench/debug/kafka-sse/events",
requestObservationQuietMs: 3_000,
valuesRedacted: true,
});
});
test("Workbench 产品 Trace 可读性 runner 合同完全来自独立 owning YAML", () => {
assert.deepEqual(nodeWebProbeWorkbenchTraceReadability(hwlabRuntimeLaneSpecForNode("v03", "NC01")), {
enabled: true,
runningCardTimeoutMs: 15_000,
disclosureActionTimeoutMs: 15_000,
disclosureOpenTimeoutMs: 10_000,
runningReadableTimeoutMs: 480_000,
terminalTimeoutMs: 480_000,
domStableQuietMs: 2_000,
domStableTimeoutMs: 6_000,
pollIntervalMs: 100,
valuesRedacted: true,
});
});
test("web observe command visibility does not equate control completion with async turn completion", () => {
const visibility = buildWebObserveCommandVisibility({
commandType: "sendPrompt",
exitCode: 0,
commandResult: {
ok: true,
completedAt: "2026-07-10T01:00:01.000Z",
result: {
chatSubmit: {
status: 202,
traceId: "trc_fixture",
otelTraceId: "otel_fixture",
},
},
},
});
assert.equal(visibility.status, "control-completed");
assert.equal(visibility.control.executionStatus, "completed");
assert.equal(visibility.control.businessTurnTerminalImplied, false);
assert.equal(visibility.asyncTurn.submissionStatus, "accepted");
assert.equal(visibility.asyncTurn.terminalStatus, null);
assert.equal(visibility.asyncTurn.terminalObserved, false);
assert.equal(visibility.asyncTurn.terminalStatusSource, "not-evaluated-by-control-command");
});
test("web observe action JSON recovery accepts concrete start contract", () => {
const resolved = resolveWebObserveActionJson({
stdout: JSON.stringify({
ok: true,
command: "web-probe-observe start",
jobId: "webobs-fixture",
stateDir: ".state/web-observe/JD01/v03/fixture",
valuesRedacted: true,
}),
stderr: "",
exitCode: 0,
timedOut: false,
}, "start");
assert.equal(resolved.source, "stdout");
assert.equal(resolved.parsed?.jobId, "webobs-fixture");
assert.equal(resolved.diagnostics.stdoutContractAccepted, true);
});
test("web observe action JSON recovery reads dump wrapper for status contract", async () => {
const dir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-status-dump-"));
const dumpPath = join(dir, "status.json");
await writeFile(dumpPath, JSON.stringify({
ok: true,
status: "running",
jobId: "webobs-fixture",
heartbeat: { status: "running" },
diagnostics: { heartbeatStale: false },
}) + "\n");
const resolved = resolveWebObserveActionJson({
stdout: JSON.stringify({
ok: true,
data: {
outputTruncated: true,
reason: "stdout-json-bytes-exceeded-threshold",
dump: { path: dumpPath },
},
}),
stderr: "",
exitCode: 0,
timedOut: false,
}, "status");
assert.equal(resolved.source, "dump");
assert.equal(resolved.parsed?.jobId, "webobs-fixture");
assert.equal(resolved.diagnostics.stdoutKind, "dump-wrapper");
assert.equal(resolved.diagnostics.dumpPath, dumpPath);
});
test("web observe action JSON recovery reads SSH truncation summary dump for command contract", async () => {
const dir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-command-dump-"));
const dumpPath = join(dir, "command.json");
await writeFile(dumpPath, JSON.stringify({
ok: true,
queued: false,
waitTimedOut: false,
commandId: "cmd-fixture",
}) + "\n");
const summary = {
code: "ssh-truncation-summary",
stdout: {
stream: "stdout",
dumpPath,
forwardedBytes: 10240,
thresholdBytes: 10240,
},
};
const resolved = resolveWebObserveActionJson({
stdout: `UNIDESK_SSH_TRUNCATION_SUMMARY ${JSON.stringify(summary)}\n`,
stderr: "",
exitCode: 0,
timedOut: false,
}, "command");
assert.equal(resolved.source, "dump");
assert.equal(resolved.parsed?.commandId, "cmd-fixture");
assert.equal(resolved.diagnostics.stdoutKind, "ssh-truncation-summary");
assert.equal(resolved.diagnostics.dumpPath, dumpPath);
});
test("web observe action JSON recovery rejects ok-only action contract", () => {
const resolved = resolveWebObserveActionJson({
stdout: JSON.stringify({ ok: true }),
stderr: "",
exitCode: 0,
timedOut: false,
}, "start");
assert.equal(resolved.parsed, null);
assert.equal(resolved.source, null);
assert.equal(resolved.diagnostics.stdoutKind, "json");
assert.equal(resolved.diagnostics.fallbackReason, "stdout-json-contract-invalid");
assert.equal(resolved.diagnostics.stdoutContractAccepted, false);
});