214 lines
8.3 KiB
TypeScript
214 lines
8.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "bun:test";
|
|
|
|
import { nodeWebObserveRunnerSource } from "./hwlab-node-web-observe-runner-source";
|
|
import { nodeWebObserveRunnerTraceReadabilitySource } from "./hwlab-node-web-observe-runner-trace-readability-source";
|
|
|
|
test("Workbench 产品 Trace 可读性源码解析独立 YAML 时序并识别终态", () => {
|
|
const source = nodeWebObserveRunnerTraceReadabilitySource();
|
|
const build = new Function(`${source}\nreturn { parseWorkbenchTraceReadabilityConfig, workbenchTraceReadabilityTerminalStatus, workbenchTraceReadabilityFailures, workbenchTraceReadabilityRetention };`) as () => {
|
|
parseWorkbenchTraceReadabilityConfig: (raw: string) => Record<string, unknown>;
|
|
workbenchTraceReadabilityTerminalStatus: (value: string) => boolean;
|
|
workbenchTraceReadabilityFailures: (value: Record<string, unknown>) => Array<Record<string, unknown>>;
|
|
workbenchTraceReadabilityRetention: (running: Record<string, unknown>, terminal: Record<string, unknown>) => Record<string, unknown>;
|
|
};
|
|
const helpers = build();
|
|
assert.deepEqual(helpers.parseWorkbenchTraceReadabilityConfig(JSON.stringify({
|
|
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,
|
|
})), {
|
|
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,
|
|
});
|
|
assert.equal(helpers.workbenchTraceReadabilityTerminalStatus("completed"), true);
|
|
assert.equal(helpers.workbenchTraceReadabilityTerminalStatus("running"), false);
|
|
assert.deepEqual(helpers.workbenchTraceReadabilityFailures({
|
|
scope: { conversationCount: 1, debugPanelCountInsideConversation: 0 },
|
|
startedDuringRunning: true,
|
|
disclosure: { openBefore: true, autoReadable: true },
|
|
running: { observed: true, snapshot: { rowCount: 2, sourceAuthorityRowCount: 2, projectedAuthorityRowCount: 0 } },
|
|
terminalArrival: { observed: true },
|
|
terminal: {
|
|
stable: true,
|
|
snapshot: {
|
|
disclosure: { open: true },
|
|
rowCount: 3,
|
|
sourceAuthorityRowCount: 3,
|
|
readableSourceRowCount: 3,
|
|
projectedAuthorityRowCount: 0,
|
|
},
|
|
},
|
|
runningRetention: { passed: true },
|
|
retainedRunningRowCount: 2,
|
|
}), []);
|
|
assert.deepEqual(
|
|
helpers.workbenchTraceReadabilityFailures({
|
|
scope: { conversationCount: 1, debugPanelCountInsideConversation: 0 },
|
|
startedDuringRunning: true,
|
|
disclosure: { openBefore: false, autoReadable: false },
|
|
running: { observed: true, snapshot: { rowCount: 2, sourceAuthorityRowCount: 2, projectedAuthorityRowCount: 0 } },
|
|
terminalArrival: { observed: true },
|
|
terminal: { stable: true, snapshot: { disclosure: { open: true }, rowCount: 2, sourceAuthorityRowCount: 2, readableSourceRowCount: 2, projectedAuthorityRowCount: 0 } },
|
|
runningRetention: { passed: true },
|
|
retainedRunningRowCount: 1,
|
|
}).map((failure) => failure.code),
|
|
["trace_not_auto_expanded"],
|
|
);
|
|
|
|
assert.deepEqual(helpers.workbenchTraceReadabilityRetention({
|
|
rowIdentities: ["source:1:tool"],
|
|
disclosure: { eventCount: 121 },
|
|
sourceSeqMin: 1,
|
|
sourceSeqMax: 120,
|
|
}, {
|
|
rowIdentities: ["source:121:terminal"],
|
|
disclosure: { eventCount: 122, rowWindowed: true },
|
|
sourceSeqMin: 2,
|
|
sourceSeqMax: 121,
|
|
}), {
|
|
mode: "window-progress",
|
|
passed: true,
|
|
retainedRowCount: 0,
|
|
retainedConcreteRowCount: 0,
|
|
runningEventCount: 121,
|
|
terminalEventCount: 122,
|
|
runningSourceSeqMin: 1,
|
|
runningSourceSeqMax: 120,
|
|
terminalSourceSeqMin: 2,
|
|
terminalSourceSeqMax: 121,
|
|
valuesRedacted: true,
|
|
});
|
|
assert.deepEqual(helpers.workbenchTraceReadabilityRetention({
|
|
rowIdentities: ["source:18:trace-noise-summary"],
|
|
disclosure: { eventCount: 25, rowWindowed: false },
|
|
sourceSeqMin: 18,
|
|
sourceSeqMax: 18,
|
|
}, {
|
|
rowIdentities: ["source:21:event:evt_fixture", "source:42:terminal:evt_terminal"],
|
|
disclosure: { eventCount: 50, rowWindowed: false },
|
|
sourceSeqMin: 21,
|
|
sourceSeqMax: 42,
|
|
}), {
|
|
mode: "summary-progress",
|
|
passed: true,
|
|
retainedRowCount: 0,
|
|
retainedConcreteRowCount: 0,
|
|
runningEventCount: 25,
|
|
terminalEventCount: 50,
|
|
runningSourceSeqMin: 18,
|
|
runningSourceSeqMax: 18,
|
|
terminalSourceSeqMin: 21,
|
|
terminalSourceSeqMax: 42,
|
|
valuesRedacted: true,
|
|
});
|
|
assert.deepEqual(helpers.workbenchTraceReadabilityRetention({
|
|
rowIdentities: ["source:18:event:evt_running"],
|
|
disclosure: { eventCount: 25, rowWindowed: false },
|
|
sourceSeqMin: 18,
|
|
sourceSeqMax: 18,
|
|
}, {
|
|
rowIdentities: ["source:21:event:evt_terminal"],
|
|
disclosure: { eventCount: 50, rowWindowed: false },
|
|
sourceSeqMin: 21,
|
|
sourceSeqMax: 21,
|
|
}), {
|
|
mode: "identity-overlap",
|
|
passed: false,
|
|
retainedRowCount: 0,
|
|
retainedConcreteRowCount: 0,
|
|
valuesRedacted: true,
|
|
});
|
|
});
|
|
|
|
test("运行卡片命中后使用 traceId 稳定定位,不随状态变更漂移", async () => {
|
|
const source = nodeWebObserveRunnerTraceReadabilitySource();
|
|
const runningTimeline = {
|
|
status: "running",
|
|
getAttribute(name: string) {
|
|
return name === "data-status" ? this.status : null;
|
|
},
|
|
};
|
|
const cardElement = (traceId: string, timeline: typeof runningTimeline) => ({
|
|
getAttribute(name: string) {
|
|
return name === "data-trace-id" ? traceId : null;
|
|
},
|
|
querySelector() {
|
|
return timeline;
|
|
},
|
|
});
|
|
const selectedTraceId = "trc_current";
|
|
const stableCard = {
|
|
async count() { return 1; },
|
|
locator() { return { count: async () => 1 }; },
|
|
async getAttribute(name: string) { return name === "data-trace-id" ? selectedTraceId : null; },
|
|
};
|
|
const cards = {
|
|
async count() { return 2; },
|
|
async evaluateAll(callback: (elements: unknown[]) => unknown) {
|
|
return callback([
|
|
cardElement("trc_old", { ...runningTimeline, status: "completed" }),
|
|
cardElement(selectedTraceId, runningTimeline),
|
|
]);
|
|
},
|
|
};
|
|
const conversation = {
|
|
locator(selector: string) {
|
|
if (selector === ':scope > .message-card[data-role="agent"][data-trace-id]') return cards;
|
|
if (selector === '[data-testid="workbench-kafka-debug-panel"]') return { count: async () => 0 };
|
|
if (selector.includes(`[data-trace-id="${selectedTraceId}"]`)) return { first: () => stableCard };
|
|
throw new Error(`unexpected selector: ${selector}`);
|
|
},
|
|
};
|
|
const conversationRoot = {
|
|
first: () => conversation,
|
|
count: async () => 1,
|
|
};
|
|
const page = {
|
|
locator(selector: string) {
|
|
if (selector === "#conversation-list") return conversationRoot;
|
|
throw new Error(`unexpected page selector: ${selector}`);
|
|
},
|
|
};
|
|
const build = new Function("page", "sleep", `${source}\nreturn { workbenchTraceReadabilityWaitForCurrentProductCard };`) as (
|
|
pageValue: typeof page,
|
|
sleepValue: () => Promise<void>,
|
|
) => {
|
|
workbenchTraceReadabilityWaitForCurrentProductCard: (timeoutMs: number, pollIntervalMs: number) => Promise<Record<string, any>>;
|
|
};
|
|
const helpers = build(page, async () => {});
|
|
const target = await helpers.workbenchTraceReadabilityWaitForCurrentProductCard(1_000, 1);
|
|
runningTimeline.status = "completed";
|
|
|
|
assert.equal(target.traceId, selectedTraceId);
|
|
assert.equal(target.card, stableCard);
|
|
assert.equal(await target.card.getAttribute("data-trace-id"), selectedTraceId);
|
|
});
|
|
|
|
test("生成的 observer runner 包含产品 Trace 命令并通过 Node 语法检查", async () => {
|
|
const source = nodeWebObserveRunnerSource();
|
|
assert.match(source, /validateWorkbenchTraceReadability/u);
|
|
assert.match(source, /#conversation-list/u);
|
|
assert.match(source, /debugPanelCountInsideConversation/u);
|
|
assert.match(source, /trace_not_auto_expanded/u);
|
|
const child = Bun.spawn(["node", "--input-type=module", "--check", "-"], { stdin: "pipe", stdout: "pipe", stderr: "pipe" });
|
|
child.stdin.write(source);
|
|
child.stdin.end();
|
|
const [stderr, exitCode] = await Promise.all([new Response(child.stderr).text(), child.exited]);
|
|
assert.equal(exitCode, 0, stderr);
|
|
});
|