@@ -68,32 +68,45 @@ describe('bounded chat requests', () => {
6868 const controller = new AbortController ( ) ;
6969 const pending = chatApi . send ( 'room' , 'cancelled draft' , undefined , controller . signal ) ;
7070 controller . abort ( ) ;
71- expect ( await pending ) . toMatchObject ( { failureKind : 'unknown' } ) ;
71+ await vi . advanceTimersByTimeAsync ( 0 ) ;
72+ expect ( await pending ) . toEqual ( { error : 'Chat request interrupted' , failureKind : 'unknown' } ) ;
7273 finishAuth ( 'late-token' ) ;
7374 await vi . advanceTimersByTimeAsync ( 1 ) ;
7475 expect ( fetch ) . not . toHaveBeenCalled ( ) ;
7576 expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
7677 } ) ;
7778
78- it ( 'keeps a timed-out send unknown when its body subsequently resolves' , async ( ) => {
79- let finishBody ! : ( value : unknown ) => void ;
80- vi . mocked ( fetch ) . mockResolvedValue ( {
81- ok : true ,
82- json : ( ) =>
83- new Promise ( ( resolve ) => {
84- finishBody = resolve ;
85- } ) ,
86- } as Response ) ;
87- const pending = chatApi . send ( 'room' , 'once' ) ;
88- await vi . advanceTimersByTimeAsync ( CHAT_REQUEST_TIMEOUT_MS ) ;
89- const outcome = await pending ;
90- expect ( outcome ) . toMatchObject ( { failureKind : 'unknown' } ) ;
91- finishBody ( { data : { id : 'committed-message' , session_id : 'room' } } ) ;
92- await vi . advanceTimersByTimeAsync ( 1 ) ;
93- expect ( await pending ) . toBe ( outcome ) ;
94- expect ( fetch ) . toHaveBeenCalledTimes ( 1 ) ;
95- expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
96- } ) ;
79+ it . each ( [ 'fetch' , 'body' ] as const ) (
80+ 'handles the %s rejection produced by a deadline abort' ,
81+ async ( stage ) => {
82+ const rejected = vi . fn ( ) ;
83+ vi . mocked ( fetch ) . mockImplementation ( ( _url , options ) => {
84+ const waitForAbort = ( ) =>
85+ new Promise < never > ( ( _resolve , reject ) => {
86+ options ?. signal ?. addEventListener (
87+ 'abort' ,
88+ ( ) => {
89+ rejected ( ) ;
90+ reject ( new DOMException ( 'Aborted' , 'AbortError' ) ) ;
91+ } ,
92+ { once : true }
93+ ) ;
94+ } ) ;
95+ if ( stage === 'fetch' ) return waitForAbort ( ) ;
96+ const response = new Response ( null , { status : 200 } ) ;
97+ vi . spyOn ( response , 'json' ) . mockImplementation ( waitForAbort ) ;
98+ return Promise . resolve ( response ) ;
99+ } ) ;
100+ const pending = chatApi . send ( 'room' , 'once' ) ;
101+ await vi . advanceTimersByTimeAsync ( CHAT_REQUEST_TIMEOUT_MS ) ;
102+ expect ( await pending ) . toEqual ( { error : 'Chat request interrupted' , failureKind : 'unknown' } ) ;
103+ await vi . advanceTimersByTimeAsync ( 1 ) ;
104+ expect ( rejected ) . toHaveBeenCalledTimes ( 1 ) ;
105+ expect ( fetch ) . toHaveBeenCalledTimes ( 1 ) ;
106+ expect ( vi . mocked ( fetch ) . mock . calls [ 0 ] ?. [ 1 ] ?. signal ?. aborted ) . toBe ( true ) ;
107+ expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
108+ }
109+ ) ;
97110
98111 it ( 'removes cancellation listeners and deadlines after a successful send' , async ( ) => {
99112 const response = { data : { id : 'confirmed-message' , session_id : 'room' } } ;
@@ -116,11 +129,14 @@ describe('bounded chat requests', () => {
116129 vi . mocked ( fetch ) . mockImplementation ( ( ) => new Promise ( ( ) => { } ) ) ;
117130 const pending = chatApi . getHistory ( 'room' , { limit : 25 } ) ;
118131 await vi . advanceTimersByTimeAsync ( CHAT_REQUEST_TIMEOUT_MS ) ;
119- expect ( await pending ) . toMatchObject ( { failureKind : 'unknown' } ) ;
132+ expect ( await pending ) . toEqual ( { error : 'Chat request interrupted' , failureKind : 'unknown' } ) ;
120133 expect ( fetch ) . toHaveBeenCalledTimes ( 1 ) ;
121- expect ( vi . mocked ( fetch ) . mock . calls [ 0 ] ?. [ 0 ] ) . toContain (
122- '/api/chat/history?sessionId=room&limit=25'
123- ) ;
134+ const request = vi . mocked ( fetch ) . mock . calls [ 0 ] ?. [ 0 ] ;
135+ if ( ! request ) throw new Error ( 'Expected a history request' ) ;
136+ const url = new URL ( request instanceof Request ? request . url : request ) ;
137+ expect ( url . pathname ) . toBe ( '/api/chat/history' ) ;
138+ expect ( url . searchParams . get ( 'sessionId' ) ) . toBe ( 'room' ) ;
139+ expect ( url . searchParams . get ( 'limit' ) ) . toBe ( '25' ) ;
124140 expect ( vi . mocked ( fetch ) . mock . calls [ 0 ] ?. [ 1 ] ?. signal ?. aborted ) . toBe ( true ) ;
125141 expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
126142 } ) ;
0 commit comments