Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/workbench-r2-session-parity.test.ts
T

85 lines
5.0 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import type { ChatMessage, WorkspaceRecord } from "../src/types/index.ts";
import { defaultProviderProfileOptions, normalizeRecentDrafts, providerProfileOptionsFromPayload, recordRecentDraft, resolveComposerState, sortSessionTabs, workspaceWithClearedActiveTrace } from "../src/stores/workbench-session.ts";
test("R2 composer ignores stale workspace activeTraceId without verified active message", () => {
const workspace = workspaceRecord({ activeTraceId: "trc_stale", sessionStatus: "running" });
const composer = resolveComposerState({ workspace, messages: [], conversations: [], activeConversationId: "cnv_r2", chatPending: false });
assert.equal(composer.submitMode, "turn");
assert.equal(composer.route, "/v1/agent/chat");
assert.equal(composer.targetTraceId, null);
assert.equal(composer.sessionId, "ses_r2");
});
test("R2 composer steers only when an active agent message or status exists", () => {
const messages: ChatMessage[] = [agentMessage({ status: "running", traceId: "trc_running", conversationId: "cnv_r2", sessionId: "ses_r2" })];
const composer = resolveComposerState({ workspace: workspaceRecord({ activeTraceId: "trc_running", sessionStatus: "running" }), messages, activeConversationId: "cnv_r2", chatPending: true });
assert.equal(composer.submitMode, "steer");
assert.equal(composer.route, "/v1/agent/chat/steer");
assert.equal(composer.targetTraceId, "trc_running");
});
test("R2 active trace cleanup preserves workspace and records stale reason", () => {
const cleared = workspaceWithClearedActiveTrace(workspaceRecord({ activeTraceId: "trc_stale", sessionStatus: "running" }), "trc_stale", "steer-trace-not-found");
assert.equal(cleared?.activeTraceId, null);
assert.equal(cleared?.workspace?.activeTraceId, null);
assert.equal(cleared?.workspace?.staleActiveTraceId, "trc_stale");
assert.equal(cleared?.workspace?.staleActiveTraceReason, "steer-trace-not-found");
assert.equal(cleared?.workspace?.sessionStatus, "failed");
});
test("R2 drafts keep recent unique values and normalize corrupt storage", () => {
const next = recordRecentDraft([{ text: "旧输入", ts: "2026-01-01T00:00:00.000Z" }], "新输入", "2026-01-02T00:00:00.000Z");
assert.deepEqual(next.map((item) => item.text), ["新输入", "旧输入"]);
const deduped = recordRecentDraft(next, "旧输入", "2026-01-03T00:00:00.000Z");
assert.deepEqual(deduped.map((item) => item.text), ["旧输入", "新输入"]);
assert.deepEqual(normalizeRecentDrafts([{ text: "ok", ts: "now" }, { text: 1 }]).map((item) => item.text), ["ok"]);
});
test("R2 session tabs expose label, model metadata source, count and active state", () => {
const tabs = sortSessionTabs([{ conversationId: "cnv_r2", sessionId: "ses_r2", lastTraceId: "trc_done", messageCount: 2, firstUserMessagePreview: "请执行一次长任务", status: "completed", updatedAt: "2026-01-02T00:00:00.000Z" }], "cnv_r2");
assert.equal(tabs.length, 1);
assert.equal(tabs[0]?.active, true);
assert.equal(tabs[0]?.label, "请执行一次长任务");
assert.match(tabs[0]?.subtitle ?? "", /trace trc_done/u);
});
test("R2 session tab labels normalize object previews and reject object placeholders", () => {
const tabs = sortSessionTabs([
{ conversationId: "cnv_object", sessionId: "ses_object", firstUserMessagePreview: { text: "对象预览标题" } as unknown as string, updatedAt: "2026-01-03T00:00:00.000Z" },
{ conversationId: "cnv_literal", sessionId: "ses_literal", firstUserMessagePreview: "[object Object]", updatedAt: "2026-01-02T00:00:00.000Z" }
], null);
assert.equal(tabs[0]?.label, "对象预览标题");
assert.equal(tabs[1]?.label, "Session ses_lite...");
assert.ok(tabs.every((tab) => !tab.label.includes("[object Object]")));
});
test("R2 provider options follow catalog and keep selected fallback", () => {
const catalog = providerProfileOptionsFromPayload({ profiles: [{ profile: "codex-api", configured: true }, { profile: "deepseek", configured: false }] }, "minimax-m3");
assert.deepEqual(catalog.map((item) => item.value), ["deepseek", "codex-api", "minimax-m3"]);
assert.equal(catalog.find((item) => item.value === "codex-api")?.configured, true);
assert.ok(defaultProviderProfileOptions("custom-profile").some((item) => item.value === "custom-profile"));
});
function workspaceRecord(input: { activeTraceId: string | null; sessionStatus: string | null }): WorkspaceRecord {
return {
workspaceId: "wsp_r2",
projectId: "prj_hwpod_workbench",
selectedConversationId: "cnv_r2",
selectedAgentSessionId: "ses_r2",
activeTraceId: input.activeTraceId,
workspace: {
selectedConversationId: "cnv_r2",
selectedAgentSessionId: "ses_r2",
activeTraceId: input.activeTraceId,
sessionStatus: input.sessionStatus
}
};
}
function agentMessage(patch: Partial<ChatMessage>): ChatMessage {
return { id: "msg_r2", role: "agent", title: "Code Agent", text: "running", status: "running", createdAt: "2026-01-01T00:00:00.000Z", ...patch };
}