@@ -89,6 +89,16 @@ describe('WebSocketManager', () => {
8989 mgr . handleMessage ( buildFrame ( 999 , 0 , payload ) ) ;
9090 assert . equal ( mgr . pending . size , 0 ) ;
9191 } ) ;
92+
93+ it ( 'survives a malformed server-push payload' , ( ) => {
94+ const mgr = new WebSocketManager ( 'ws://fake' ) ;
95+ let pushed = false ;
96+ mgr . onPush = ( ) => { pushed = true ; } ;
97+ // id=0 push frame with invalid JSON must not throw or call onPush.
98+ const bad = new TextEncoder ( ) . encode ( '{ not json' ) ;
99+ assert . doesNotThrow ( ( ) => mgr . handleMessage ( buildFrame ( 0 , 0 , bad ) ) ) ;
100+ assert . equal ( pushed , false , 'no push delivered for malformed JSON' ) ;
101+ } ) ;
92102 } ) ;
93103
94104 describe ( 'request' , ( ) => {
@@ -286,6 +296,32 @@ describe('WebSocketManager flow control', () => {
286296 assert . equal ( mgr . socket . sent . length , sentBefore ,
287297 'a queued cancel sends nothing' ) ;
288298 } ) ;
299+
300+ it ( 'cancelling a queued request settles its promise' , async ( ) => {
301+ const mgr = new WebSocketManager ( 'ws://fake' ) ;
302+ await mgr . readyPromise ;
303+
304+ // Saturate the wire so the next request stays queued.
305+ const ids = [ ] ;
306+ for ( let i = 0 ; i < 200 ; i ++ ) {
307+ const p = mgr . request ( { type : 'tile' , n : i } ) ;
308+ p . catch ( ( ) => { } ) ;
309+ ids . push ( p . requestId ) ;
310+ }
311+ const queuedId = ids [ ids . length - 1 ] ;
312+ assert . ok ( mgr . _queue . has ( queuedId ) , 'last request is queued' ) ;
313+
314+ // Re-issue a tracked promise for the queued id so we can observe it.
315+ const entry = mgr . _queue . get ( queuedId ) ;
316+ const observed = new Promise ( ( resolve , reject ) => {
317+ entry . resolve = resolve ;
318+ entry . reject = reject ;
319+ } ) ;
320+
321+ mgr . cancel ( queuedId ) ;
322+ // The promise must reject — not hang forever — so callers clean up.
323+ await assert . rejects ( observed , { message : 'Request cancelled' } ) ;
324+ } ) ;
289325} ) ;
290326
291327describe ( 'WebSocketManager liveness' , ( ) => {
@@ -353,6 +389,21 @@ describe('WebSocketManager liveness', () => {
353389 performance . now = realNow ;
354390 }
355391 } ) ;
392+
393+ it ( 'stops the liveness timer on intentional shutdown' , async ( ) => {
394+ const mgr = new WebSocketManager ( 'ws://fake' ) ;
395+ await mgr . readyPromise ;
396+ assert . notEqual ( mgr . _livenessTimer , undefined ,
397+ 'timer runs while connected' ) ;
398+
399+ // Server-initiated shutdown: the socket closes and must not reconnect,
400+ // so the recurring liveness check must be cleared too.
401+ mgr . _shutdown = true ;
402+ mgr . socket . readyState = 3 ; // CLOSED
403+ mgr . socket . onclose ?. ( ) ;
404+ assert . equal ( mgr . _livenessTimer , undefined ,
405+ 'liveness timer cleared after shutdown' ) ;
406+ } ) ;
356407} ) ;
357408
358409describe ( 'WebSocketManager.fromCache' , ( ) => {
0 commit comments