import assert from "node:assert/strict"; import test from "node:test"; import { runConsultationRecoveryPoll, type ConsultationRecoveryPollDeps } from "../src/lib/consultation-recovery-poll.ts"; import { ConsultationStatusError } from "../src/lib/home-cloud-sync.ts"; import { previewModelCatalog } from "../src/lib/home-types.ts"; import { withFakeTimers } from "./home-bootstrap-fake-timers.ts"; const sessionId = "11111111-1111-4111-8111-111111111111"; const requestId = "22222222-2222-4222-8222-222222222222"; async function flush() { for (let step = 0; step < 8; step += 1) await Promise.resolve(); } function createPoll(overrides: { fetchConsultationStatus: ConsultationRecoveryPollDeps["fetchConsultationStatus"]; missing?: number; }) { const state = { notice: "", phase: "recovering" as "recovering" | null, pendingSession: sessionId as string | null, pendingRequest: requestId as string | null, replay: 0, error: "", refreshed: 0, }; const deps: ConsultationRecoveryPollDeps = { pendingSessionId: sessionId, pendingRequestId: requestId, modelCatalog: previewModelCatalog, consultationStatusMissingCount: { current: overrides.missing ?? 0 }, consultationReplayStarted: { current: null }, consultationReplay: { current: () => { state.replay += 1; deps.consultationReplayStarted.current = requestId; }, }, pendingConsultation: { current: { phase: "recovering" } }, setComposerNotice: (notice) => { state.notice = notice; }, setSessions: () => undefined, setActiveSessionId: () => undefined, setPendingSessionId: (id) => { state.pendingSession = id; }, setPendingRequestId: (id) => { state.pendingRequest = id; }, setConsultationPhase: (phase) => { state.phase = phase === "recovering" ? "recovering" : null; }, setStreamingReply: () => undefined, setRequestError: (value) => { state.error = value?.message ?? ""; }, refreshAccount: () => { state.refreshed += 1; }, online: () => true, fetchConsultationStatus: overrides.fetchConsultationStatus, fetchSessionDetail: async () => null, }; return { deps, state }; } test("the first 404 confirms the request and the next 404 replays it once", async () => { await withFakeTimers(async (tick) => { const { deps, state } = createPoll({ fetchConsultationStatus: async () => { throw new ConsultationStatusError(404, "missing"); }, }); runConsultationRecoveryPoll(deps, new AbortController().signal); await flush(); assert.equal(deps.consultationStatusMissingCount.current, 1); assert.equal(state.notice, "正在确认本次咨询请求是否已开始…"); assert.equal(state.replay, 0); assert.equal(state.phase, "recovering"); tick(1_750); await flush(); assert.equal(deps.consultationStatusMissingCount.current, 2); assert.equal(state.replay, 1); assert.equal(state.phase, "recovering"); }); }); test("the fourth missing status gives up and stops polling", async () => { await withFakeTimers(async (tick) => { const { deps, state } = createPoll({ fetchConsultationStatus: async () => { throw new ConsultationStatusError(404, "missing"); }, }); runConsultationRecoveryPoll(deps, new AbortController().signal); await flush(); tick(1_750); await flush(); tick(1_750); await flush(); assert.equal(deps.consultationStatusMissingCount.current, 3); assert.equal(state.notice, "正在重新发起本次咨询…"); assert.equal(state.replay, 1); tick(1_750); await flush(); assert.equal(deps.consultationStatusMissingCount.current, 4); assert.equal(state.pendingSession, null); assert.equal(state.pendingRequest, null); assert.equal(state.phase, null); assert.equal(state.error, "后台未找到本次咨询请求,请重新发送。"); assert.equal(state.notice, "后台未找到本次咨询请求,已停止恢复,请重新发送。"); const timersBefore = state.notice; tick(1_750); await flush(); assert.equal(state.notice, timersBefore); assert.equal(deps.consultationStatusMissingCount.current, 4); }); }); test("a reserved status clears the missing count", async () => { await withFakeTimers(async (tick) => { const { deps, state } = createPoll({ missing: 2, fetchConsultationStatus: async () => ({ sessionId, requestId, status: "reserved", }), }); runConsultationRecoveryPoll(deps, new AbortController().signal); await flush(); assert.equal(deps.consultationStatusMissingCount.current, 2); tick(1_750); await flush(); assert.equal(deps.consultationStatusMissingCount.current, 0); assert.equal(state.notice, "回答仍在后台生成,正在自动恢复。"); assert.equal(state.phase, "recovering"); }); });