Files
Jyotisha/frontend/tests/rectification-activity-receipt.test.ts
T
Jesse_Chen 7e3b6cdc7a
Independent Staging Quality Gate / validate (push) Successful in 25m9s
Independent Staging Quality Gate / publish (push) Successful in 37m19s
fix(rectification): simplify delivery card and guard double turns (BUG-595, BUG-596)
Make the range card row-select, drop the composer 先这样 control and adopt status bar, keep delivery copy to three sentences with a folded verification report, and skip a second no-message agent run after a terminal delivery turn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 19:56:48 +08:00

153 lines
6.2 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
createRectificationActivityReceiptState,
reduceRectificationActivityReceipt,
receiptFromRectificationActivityState,
} from "../src/lib/rectification-activity-receipt";
test("a successful retry clears the same tool's earlier failure", () => {
let state = createRectificationActivityReceiptState();
for (const event of [
{ tool: "rectification-read-case", status: "completed" },
{ tool: "rectification-propose-evidence", status: "failed" },
{ tool: "rectification-propose-evidence", status: "started" },
{ tool: "rectification-propose-evidence", status: "completed" },
{ tool: "rectification-confirm-evidence", status: "completed" },
] as const) {
state = reduceRectificationActivityReceipt(state, event);
}
assert.deepEqual(receiptFromRectificationActivityState(state), {
steps: [
"rectification-read-case",
"rectification-propose-evidence",
"rectification-confirm-evidence",
],
methods: [],
});
});
test("an unrecovered latest tool failure remains visible", () => {
let state = createRectificationActivityReceiptState();
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-read-case",
status: "completed",
});
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-propose-evidence",
status: "failed",
});
assert.deepEqual(receiptFromRectificationActivityState(state), {
steps: ["rectification-read-case"],
methods: [],
failedTool: "rectification-propose-evidence",
});
});
test("a later failure overrides an earlier completion for the same tool", () => {
let state = createRectificationActivityReceiptState();
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-read-case",
status: "completed",
});
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-read-case",
status: "failed",
});
assert.deepEqual(receiptFromRectificationActivityState(state), {
steps: [],
methods: [],
failedTool: "rectification-read-case",
});
});
test("a later failure removes methods contributed by the failed tool", () => {
let state = createRectificationActivityReceiptState();
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-compare-candidates",
status: "completed",
methods: ["d1-rashi", "vimshottari-dasha"],
});
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-read-diagnostics",
status: "completed",
methods: ["d1-rashi", "shadbala"],
});
state = reduceRectificationActivityReceipt(state, {
tool: "rectification-compare-candidates",
status: "failed",
});
assert.deepEqual(receiptFromRectificationActivityState(state), {
steps: ["rectification-read-diagnostics"],
methods: ["d1-rashi", "shadbala"],
failedTool: "rectification-compare-candidates",
});
});
const chatSource = readFileSync(
new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url),
"utf8",
);
test("answer deltas preserve a still-running server activity", () => {
const deltaBranch = chatSource.slice(
chatSource.indexOf('event.type === "answer.delta"'),
chatSource.indexOf('event.type === "run.failed"'),
);
// Former locks: `state: raw.trim() ? "streaming" : "thinking"` and `text: raw,` inside the
// delta branch. Those pinned one setMessages per network chunk (BUG-473); the same state
// derivation now lives in the frame buffer's flush, which the delta branch feeds.
const frameFlush = chatSource.slice(
chatSource.indexOf("const frames = createStreamFrameBuffer"),
chatSource.indexOf("const abortController = new AbortController()"),
);
assert.match(frameFlush, /state: text\.trim\(\) \? "streaming" : "thinking"/);
assert.match(frameFlush, /^\s*text,$/m);
assert.match(deltaBranch, /frames\.setAnswer\(raw\)/);
assert.match(deltaBranch, /event\.replace === true \? event\.text : raw \+ event\.text/);
assert.doesNotMatch(deltaBranch, /settled\.spoken/);
assert.match(deltaBranch, /正在组织回答/);
assert.doesNotMatch(deltaBranch, /activeActivity:\s*undefined/);
});
test("failed server turns retain their receipt state without parsing activity from answer text", () => {
assert.match(chatSource, /failed:\s*true/);
assert.match(chatSource, /completedReceipt/);
// Former lock: `message.state === "settled" && message.failed`, the inline red banner above a
// failed reply. That banner was the second, rectification-only failure surface (BUG-476);
// failure now reaches the shared error notice while the receipt state still gates actions.
assert.match(chatSource, /&& !message\.failed/);
assert.doesNotMatch(chatSource, /rectification-activity-failure/);
assert.doesNotMatch(chatSource, /parsed\.(?:activity|receipt|tools|methods)/);
});
test("candidate acceptance copy never presents adoption as confirmation", () => {
// 旧:分钟卡「当前可能的出生时间」+ 「采用不等于确认出生时间」
// 新:区间交付卡;动作是「按这个范围用」;已采用仍不是确认
// 原因:TASK-rectification-range-delivery-card-20260907 交付对象改成区间
const delivery = readFileSync(
new URL("../src/components/rectification-range-delivery.tsx", import.meta.url),
"utf8",
);
assert.match(chatSource, /<RectificationRangeDelivery/);
assert.match(delivery, /rangeDeliveryAdopting/);
// 原值: 组件源码直写「已采用」
// 新值: 行内状态走 rangeDeliveryAdopted
// 原因: BUG-595 决策 1
assert.match(delivery, /rangeDeliveryAdopted/);
assert.doesNotMatch(delivery, /确认校正时间/);
assert.match(chatSource, /已确认校正时间/);
// 原值: 采用后状态条 rectification-adopt-status
// 新值: 采用状态只在卡片行内
// 原因: BUG-595 决策 3
assert.doesNotMatch(chatSource, /rectification-adopt-status/);
assert.doesNotMatch(chatSource, /rectification-consult-handoff/);
assert.doesNotMatch(chatSource, /本轮校正已收口|当前排盘时间(代表性时间|本会话以代表性时间收口/);
assert.doesNotMatch(chatSource, /candidateResult\.confirmationAllowed \? "确认校正时间"/);
});