Files
Jyotisha/frontend/tests/consultation-recovery.test.ts
T
Jesse_Chen 7d667fecbf fix(consult): treat pinched answers as failed and restore reply actions
Incomplete Flash generations were billed as completed consultations. Fail
those runs, keep the partial text, and reuse the rectification like/copy/rerun
bar on ordinary chat replies.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 19:40:50 +08:00

147 lines
11 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const source = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const sendSource = source.slice(source.indexOf(" async function send("), source.indexOf("\n\n consultationReplay.current"));
const stopSource = source.slice(source.indexOf(" async function stopResponse("), source.indexOf("\n\n function completeConsultationInterface"));
test("consultation persists the optimistic user message before generation starts", () => {
const undo = sendSource.indexOf("await waitForUndoWindow(controller.signal)");
const persist = sendSource.indexOf("await persistSession(userSession)");
const consult = sendSource.indexOf('fetch("/api/consult"');
assert.ok(undo >= 0 && undo < persist && persist < consult);
assert.match(sendSource, /问题保存失败,未开始生成;问题已放回输入框。[\s\S]*?return false;[\s\S]*?fetch\("\/api\/consult"/);
});
test("only explicit stop can request consultation cancellation", () => {
const streamCatch = sendSource.indexOf(" } catch (caught) {", sendSource.indexOf('fetch("/api/consult"'));
assert.equal(source.match(/confirmCancellation\(/g)?.length, 2);
assert.match(stopSource, /await confirmCancellation\(/);
assert.ok(streamCatch >= 0);
assert.doesNotMatch(sendSource.slice(streamCatch), /confirmCancellation\(|\/api\/consult\/cancel/);
});
test("durable partial stop cancels before preserving content and recovers completed conflicts", () => {
const partialStop = stopSource.slice(
stopSource.indexOf(" if (pending.partialReply)"),
stopSource.indexOf("\n updateSession(pending.sessionId, () => pending.previousSession)"),
);
const cancel = partialStop.indexOf("await requestCancellation(pending.requestId)");
const persist = partialStop.indexOf("await persistSession(stoppedSession)");
const conflict = partialStop.indexOf("error.status === 409");
const recoveryPersist = partialStop.slice(conflict, persist);
assert.ok(cancel >= 0 && cancel < persist);
assert.ok(conflict >= 0 && conflict < persist);
assert.doesNotMatch(recoveryPersist, /persistSession\(stoppedSession\)/);
assert.match(recoveryPersist, /fetchConsultationStatus\(pending.sessionId, pending.requestId\)/);
assert.match(recoveryPersist, /status.status === "completed"[\s\S]*?fetchSessions\(\)[\s\S]*?readSessions\(payload, modelCatalog\)/);
assert.match(partialStop, /已停止回答,现有内容已保留,本次点数已退回。/);
assert.match(source, /停止回答,保留已生成内容并退回本次点数/);
assert.match(source, /停止回答,保留现有内容并申请退回本次点数/);
});
test("explicit consultation HTTP failures unlock instead of entering recovery", () => {
assert.match(sendSource, /!response\.ok[\s\S]*throw new ConsultationResponseError\([\s\S]*response\.status/);
const explicitFailure = sendSource.slice(
sendSource.indexOf("caught instanceof ConsultationResponseError"),
sendSource.indexOf("if (!cancelled && ownsInterface && pendingConsultation.current)"),
);
assert.match(explicitFailure, /caught\.message === "request_conflict"[\s\S]*setConsultationPhase\("recovering"\)/);
const genericFailure = explicitFailure.slice(explicitFailure.indexOf("setRequestError({ sessionId, message: caught.message })"));
assert.match(genericFailure, /setRequestError\(\{ sessionId, message: caught\.message \}\)/);
assert.match(genericFailure, /completeConsultationInterface\(requestId\)/);
assert.doesNotMatch(genericFailure, /phase: "recovering"|setConsultationPhase\("recovering"\)/);
});
test("reserved consultations recover through the status endpoint", () => {
assert.match(source, /\/api\/consult\/status\?sessionId=\$\{encodeURIComponent\(sessionId\)\}&requestId=\$\{encodeURIComponent\(requestId\)\}/);
assert.match(source, /fetch\("\/api\/consult\/status", \{ signal, cache: "no-store" \}\)/);
assert.match(source, /reservedConsultation\?\.status === "reserved"[\s\S]*nextSessions\.find\(\(session\) => session\.id === reservedConsultation\.sessionId\)/);
assert.match(source, /status\?\.status !== "reserved"[\s\S]*sessions\.find\(\(item\) => item\.id === status\.sessionId\)/);
assert.match(source, /status: "reserved" \| "completed" \| "cancelled"/);
assert.match(source, /readonly responseMessage\?: unknown/);
assert.match(source, /phase: "recovering"/);
assert.match(source, /window\.setTimeout\(\(\) => void poll\(\), 1_750\)/);
assert.match(source, /status\.status === "completed"[\s\S]*?fetchSessions\(controller\.signal\)[\s\S]*?readSessions\(payload, modelCatalog\)/);
assert.match(source, /window\.addEventListener\("online", onOnline\)/);
assert.match(source, /window\.addEventListener\("pageshow", onPageShow\)/);
assert.match(source, /网络已断开,回答仍在后台生成;联网后会自动恢复。/);
});
test("a missing reservation is replayed once instead of being polled until the question is lost", () => {
assert.match(source, /class ConsultationStatusError extends Error[\s\S]*readonly status: number/);
assert.match(source, /throw new ConsultationStatusError\([\s\S]*response\.status/);
const recoveryEffect = source.slice(
source.indexOf('if (consultationPhase !== "recovering"'),
source.indexOf("}, [consultationPhase, modelCatalog, pendingRequestId, pendingSessionId])"),
);
assert.match(source, /const consultationStatusMissingCount = useRef\(0\)/);
assert.match(source, /const consultationReplayStarted = useRef<string \| null>\(null\)/);
assert.match(recoveryEffect, /status\.status === "reserved"[\s\S]*consultationStatusMissingCount\.current = 0/);
assert.match(recoveryEffect, /caught instanceof ConsultationStatusError && caught\.status === 404[\s\S]*consultationStatusMissingCount\.current \+= 1/);
assert.match(recoveryEffect, /consultationStatusMissingCount\.current === 1[\s\S]*正在确认本次咨询请求是否已开始/);
assert.match(recoveryEffect, /consultationReplayStarted\.current !== pendingRequestId[\s\S]*consultationReplay\.current\(\)/);
assert.match(recoveryEffect, /consultationStatusMissingCount\.current >= 4[\s\S]*setPendingSessionId\(null\)[\s\S]*setPendingRequestId\(null\)[\s\S]*setConsultationPhase\(null\)/);
assert.match(recoveryEffect, /后台未找到本次咨询请求,已停止恢复,请重新发送。/);
assert.match(recoveryEffect, /consultationStatusMissingCount\.current = 0;[\s\S]*回答仍在后台生成,正在自动恢复。/);
assert.match(recoveryEffect, /consultationStatusMissingCount\.current > 0[\s\S]*window\.setTimeout\(\(\) => void poll\(\), 1_750\)[\s\S]*else \{[\s\S]*void poll\(\)/);
});
test("tab-local pending ids drive strict bootstrap recovery before the global fallback", () => {
const bootstrap = source.slice(
source.indexOf("let reservedConsultation: ConsultationStatus | null = null"),
source.indexOf("if (controller.signal.aborted) return;", source.indexOf("let reservedConsultation: ConsultationStatus | null = null")),
);
const storageSync = source.slice(
source.indexOf("if (!hydrated || uiPreview.current) return;", source.indexOf("}, []);")),
source.indexOf('if (consultationPhase !== "recovering"'),
);
assert.match(bootstrap, /readStoredPendingConsultation\([\s\S]*pendingConsultationStorageKey[\s\S]*nextSessions\.map\(\(session\) => session\.id\)/);
assert.match(bootstrap, /if \(!storedPending && sessionStorage\.getItem\(pendingConsultationStorageKey\)\) \{\s*sessionStorage\.removeItem\(pendingConsultationStorageKey\)/);
assert.ok(bootstrap.indexOf("if (storedPending)") < bootstrap.indexOf("fetchActiveConsultationStatus(controller.signal)"));
assert.match(bootstrap, /if \(storedPending\) \{[\s\S]*fetchConsultationStatus\([\s\S]*storedPending\.sessionId,[\s\S]*storedPending\.requestId,[\s\S]*\} else \{[\s\S]*fetchActiveConsultationStatus/);
assert.match(bootstrap, /status\.status === "reserved"[\s\S]*reservedConsultation = status;[\s\S]*else \{[\s\S]*sessionStorage\.removeItem\(pendingConsultationStorageKey\)/);
assert.match(bootstrap, /caught instanceof ConsultationStatusError && caught\.status === 404 \? 1 : 0/);
assert.match(storageSync, /pendingSessionId && pendingRequestId[\s\S]*sessionStorage\.setItem\(pendingConsultationStorageKey,[\s\S]*sessionId: pendingSessionId,[\s\S]*requestId: pendingRequestId,[\s\S]*question:/);
assert.match(storageSync, /else \{[\s\S]*sessionStorage\.removeItem\(pendingConsultationStorageKey\)/);
});
test("pending storage keeps the question so a refresh during the undo window can replay", () => {
assert.match(source, /function readStoredPendingConsultation\(/);
assert.match(source, /typeof parsedPending\.question === "string" \? parsedPending\.question : ""/);
assert.match(sendSource, /sessionStorage\.setItem\(pendingConsultationStorageKey, JSON\.stringify\(\{[\s\S]*sessionId,[\s\S]*requestId,[\s\S]*question: originalQuestion/);
assert.match(source, /restoreConsultationRecovery\(recoverySession, reservedConsultation\.requestId, storedPending\)/);
assert.match(source, /stored\?\.question\?\.trim\(\)/);
assert.match(sendSource, /resumeRequestId/);
assert.match(sendSource, /questionAlreadyPresent/);
assert.match(source, /consultationReplay\.current = \(\) => \{[\s\S]*resumeRequestId: pending\.requestId/);
});
test("the first default consultation title is persisted with the user question", () => {
const userSessionBlock = sendSource.slice(
sendSource.indexOf("const userSession: ChatSession"),
sendSource.indexOf("const requestId = resumeRequestId ?? globalThis.crypto.randomUUID()"),
);
assert.match(userSessionBlock, /currentSession\.messages\.length === 0 && currentSession\.title === "新对话"[\s\S]*resolveSessionTitle\(question\)/);
assert.ok(sendSource.indexOf("await persistSession(userSession)") < sendSource.indexOf('fetch("/api/consult"'));
assert.doesNotMatch(sendSource, /persistSession\(completedSession\)/);
assert.match(sendSource, /const completedSession: ChatSession = \{[\s\S]*title: userSession\.title/);
assert.doesNotMatch(sendSource, /resolveSessionTitle\(question, reply\.title\)/);
});
test("a truncated generation keeps the partial answer and does not wait for a successful run", () => {
const stream = sendSource.slice(sendSource.indexOf('fetch("/api/consult"'));
assert.match(stream, /event\.code === "answer_truncated"/);
assert.match(stream, /truncatedFailure = event/);
assert.match(stream, /const truncatedSession: ChatSession = \{[\s\S]*role: "assistant"[\s\S]*text: reply\.text/);
assert.match(stream, /await persistSession\(truncatedSession\)/);
assert.match(stream, /setComposerNotice\(truncatedFailure\.message\)/);
assert.match(stream, /if \(!runCompleted && !truncatedFailure\) throw new ConsultationResponseError/);
assert.doesNotMatch(stream.slice(stream.indexOf("if (truncatedFailure)")), /runCompleted = true/);
});