@@ -25,6 +25,7 @@ import { ChatSession } from './chat-session';
2525import { ApiSettings } from '../types/internal' ;
2626import { VertexAIBackend } from '../backend' ;
2727import { fakeChromeAdapter } from '../../test-utils/get-fake-firebase-services' ;
28+ import { logger } from '../logger' ;
2829
2930use ( sinonChai ) ;
3031use ( chaiAsPromised ) ;
@@ -220,6 +221,70 @@ describe('ChatSession', () => {
220221 ) ;
221222 clock . restore ( ) ;
222223 } ) ;
224+ it ( 'logs error and rejects user promise when response aggregation fails' , async ( ) => {
225+ const loggerStub = stub ( logger , 'error' ) ;
226+ const error = new Error ( 'Aggregation failed' ) ;
227+
228+ // Simulate stream returning, but the response promise failing (e.g. parsing error)
229+ stub ( generateContentMethods , 'generateContentStream' ) . resolves ( {
230+ stream : ( async function * ( ) { } ) ( ) ,
231+ response : Promise . reject ( error )
232+ } as unknown as GenerateContentStreamResult ) ;
233+
234+ const chatSession = new ChatSession ( fakeApiSettings , 'a-model' ) ;
235+ const initialHistoryLength = ( await chatSession . getHistory ( ) ) . length ;
236+
237+ // Immediate call resolves with the stream object
238+ const result = await chatSession . sendMessageStream ( 'hello' ) ;
239+
240+ // User's response promise should reject
241+ await expect ( result . response ) . to . be . rejectedWith ( error ) ;
242+
243+ // Wait for the internal _sendPromise chain to settle
244+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
245+
246+ expect ( loggerStub ) . to . have . been . calledWith ( error ) ;
247+
248+ // History should NOT have been updated (no response appended)
249+ const finalHistory = await chatSession . getHistory ( ) ;
250+ expect ( finalHistory . length ) . to . equal ( initialHistoryLength ) ;
251+ } ) ;
252+ it ( 'logs error but resolves user promise when history appending logic fails' , async ( ) => {
253+ const loggerStub = stub ( logger , 'error' ) ;
254+
255+ // Simulate a response that is technically valid enough to resolve aggregation,
256+ // but malformed in a way that causes the history update logic to throw.
257+ // Passing `null` as a candidate causes `{ ...response.candidates[0].content }` to throw.
258+ const malformedResponse = {
259+ candidates : [ null ]
260+ } ;
261+
262+ stub ( generateContentMethods , 'generateContentStream' ) . resolves ( {
263+ stream : ( async function * ( ) { } ) ( ) ,
264+ response : Promise . resolve ( malformedResponse )
265+ } as unknown as GenerateContentStreamResult ) ;
266+
267+ const chatSession = new ChatSession ( fakeApiSettings , 'a-model' ) ;
268+ const initialHistoryLength = ( await chatSession . getHistory ( ) ) . length ;
269+
270+ const result = await chatSession . sendMessageStream ( 'hello' ) ;
271+
272+ // The user's response promise SHOULD resolve, because aggregation succeeded.
273+ // The error is purely internal side-effect (history update).
274+ await expect ( result . response ) . to . eventually . equal ( malformedResponse ) ;
275+
276+ // Wait for internal chain
277+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
278+
279+ expect ( loggerStub ) . to . have . been . called ;
280+ const errorArg = loggerStub . firstCall . args [ 0 ] ;
281+ expect ( errorArg ) . to . be . instanceOf ( TypeError ) ;
282+
283+ // The user message WAS added before the crash, but the response wasn't.
284+ const finalHistory = await chatSession . getHistory ( ) ;
285+ expect ( finalHistory . length ) . to . equal ( initialHistoryLength + 1 ) ;
286+ expect ( finalHistory [ finalHistory . length - 1 ] . role ) . to . equal ( 'user' ) ;
287+ } ) ;
223288 it ( 'error from stream promise should not be logged' , async ( ) => {
224289 const consoleStub = stub ( console , 'error' ) ;
225290 stub ( generateContentMethods , 'generateContentStream' ) . rejects ( 'foo' ) ;
@@ -313,4 +378,4 @@ describe('ChatSession', () => {
313378 ) ;
314379 } ) ;
315380 } ) ;
316- } ) ;
381+ } ) ;
0 commit comments