test(cli): route existing dedup tests through fast-path accessor (PR #4176)

One Suggestion thread from the deepseek-v4-pro pass on commit
c30bba6e7.

`MockedGeminiClientClass` did not expose `getHistoryFunctionResponseIds`,
so 3 of the 4 dedup tests in `useGeminiStream.test.tsx` fell through
to the `else if (typeof geminiClient.getHistory === 'function')`
branch — the `structuredClone(this.history)` slow path. Only the
mixed-batch test added in c30bba6e7 wired the fast path explicitly.
Net effect: a regression in `getHistoryFunctionResponseIds` (wrong
ids, missing ids, exception) would silently re-route every dedup
batch onto the slow clone path with all 4 tests still green, while
production paid the multi-millisecond UI-thread stall this PR
specifically added the accessor to avoid.

Fix in two parts:

1. Add `getHistoryFunctionResponseIds = vi.fn().mockReturnValue(new
   Set<string>())` to the `MockedGeminiClientClass` default. Every
   test now exposes the fast-path accessor, so the dispatcher takes
   the `getHistoryFunctionResponseIds` branch by default — matching
   production. Tests that need a non-empty dedup set override the
   mock explicitly.

2. Override the mock in each of the three previously-affected dedup
   tests with a `Set` containing the callId(s) their `getHistory()`
   fixture had paired. The dedup assertions stay identical — but
   the path the production code takes to reach them now matches
   the path under test.

Tests: 96/96 useGeminiStream (no new tests; existing dedup tests
now exercise the fast path the previous commit added). 353/353
across core + cli. tsc + eslint + prettier clean.
This commit is contained in:
高铁 2026-05-20 21:29:11 +08:00
parent c30bba6e77
commit 06a695156b

View file

@ -53,6 +53,18 @@ const MockedGeminiClientClass = vi.hoisted(() =>
this.addHistory = vi.fn();
this.consumePendingMemoryTaskPromises = vi.fn().mockReturnValue([]);
this.recordCompletedToolCall = vi.fn();
// Default to the fast-path accessor returning an empty Set so the
// dedup dispatcher in `handleCompletedTools` takes the
// `getHistoryFunctionResponseIds` branch by default (matching
// production). Tests that need a non-empty dedup set override
// this. Without exposing the method at all, the dispatcher would
// fall through to the `structuredClone(getHistory())` slow path
// and any regression in the fast path would silently route
// production onto the expensive branch while CI stays green.
// deepseek-v4-pro thread on PR #4176.
this.getHistoryFunctionResponseIds = vi
.fn()
.mockReturnValue(new Set<string>());
this.getChatRecordingService = vi.fn().mockReturnValue({
recordThought: vi.fn(),
initialize: vi.fn(),
@ -766,7 +778,14 @@ describe('useGeminiStream', () => {
const client = new MockedGeminiClientClass(mockConfig);
// Simulate the chat-internal repair pass having already planted a
// synthetic functionResponse for the same callId on the previous
// (Retry) push.
// (Retry) push. The dedup dispatcher consults
// `getHistoryFunctionResponseIds` first; we override the default
// empty-Set mock to return the matching callId so the fast path
// is what production code exercises in this test (instead of
// falling through to the structuredClone slow path).
client.getHistoryFunctionResponseIds = vi
.fn()
.mockReturnValue(new Set(['call_race_A']));
client.getHistory = vi.fn().mockReturnValue([
{ role: 'user', parts: [{ text: 'open /tmp/x.txt' }] },
{
@ -901,7 +920,13 @@ describe('useGeminiStream', () => {
} as unknown as TrackedCancelledToolCall;
const client = new MockedGeminiClientClass(mockConfig);
// Pre-paired in history: dedup will fire for this callId.
// Pre-paired in history: dedup will fire for this callId. Wire
// the fast-path accessor so the dispatcher takes the
// `getHistoryFunctionResponseIds` branch (matches production
// path; see the default mock comment in MockedGeminiClientClass).
client.getHistoryFunctionResponseIds = vi
.fn()
.mockReturnValue(new Set(['call_dedup_cancelled']));
client.getHistory = vi.fn().mockReturnValue([
{ role: 'user', parts: [{ text: 'cancelled write' }] },
{
@ -1027,6 +1052,12 @@ describe('useGeminiStream', () => {
} as unknown as TrackedCompletedToolCall;
const client = new MockedGeminiClientClass(mockConfig);
// Wire the fast-path accessor so the dispatcher takes the
// `getHistoryFunctionResponseIds` branch (matches production
// path; see the default mock comment in MockedGeminiClientClass).
client.getHistoryFunctionResponseIds = vi
.fn()
.mockReturnValue(new Set(['call_race_A_responding']));
client.getHistory = vi.fn().mockReturnValue([
{ role: 'user', parts: [{ text: 'open /tmp/y.txt' }] },
{