@@ -386,12 +386,7 @@ describe('RobotClient', () => {
386386 } ) ;
387387
388388 describe ( 'dial error handling' , ( ) => {
389- interface DisconnectedEventCapture {
390- events : unknown [ ] ;
391- setupListener : ( client : RobotClient ) => void ;
392- }
393-
394- const captureDisconnectedEvents = ( ) : DisconnectedEventCapture => {
389+ const captureDisconnectedEvents = ( ) => {
395390 const events : unknown [ ] = [ ] ;
396391 const setupListener = ( client : RobotClient ) => {
397392 client . on ( 'disconnected' , ( event ) => {
@@ -421,22 +416,38 @@ describe('RobotClient', () => {
421416 } ) ;
422417 } ;
423418
424- it ( 'should throw an error when both WebRTC and gRPC connections fail' , async ( ) => {
419+ it ( 'should return client instance when WebRTC connection succeeds' , async ( ) => {
420+ // Arrange
421+ const client = setupClientMocks ( ) ;
422+
423+ // Act
424+ const result = await client . dial ( {
425+ ...baseDialConfig ,
426+ noReconnect : true ,
427+ } ) ;
428+
429+ // Assert
430+ expect ( result ) . toBe ( client ) ;
431+ } ) ;
432+
433+ it ( 'should return client instance even when both WebRTC and gRPC connections fail' , async ( ) => {
425434 // Arrange
426435 const client = new RobotClient ( ) ;
427436 const webrtcError = new Error ( 'WebRTC connection failed' ) ;
428- const grpcError = new Error ( 'gRPC connection failed' ) ;
437+ const { events , setupListener } = captureDisconnectedEvents ( ) ;
429438
430439 vi . mocked ( rpcModule . dialWebRTC ) . mockRejectedValue ( webrtcError ) ;
431- vi . mocked ( rpcModule . dialDirect ) . mockRejectedValue ( grpcError ) ;
440+ setupListener ( client ) ;
432441
433- // Act & Assert
434- await expect (
435- client . dial ( {
436- ...baseDialConfig ,
437- noReconnect : true ,
438- } )
439- ) . rejects . toThrow ( 'Failed to connect via all methods' ) ;
442+ // Act
443+ const result = await client . dial ( {
444+ ...baseDialConfig ,
445+ noReconnect : true ,
446+ } ) ;
447+
448+ // Assert
449+ expect ( result ) . toBe ( client ) ;
450+ expect ( events . length ) . toBeGreaterThanOrEqual ( 2 ) ;
440451 } ) ;
441452
442453 it ( 'should emit DISCONNECTED event with error when WebRTC fails' , async ( ) => {
@@ -449,14 +460,10 @@ describe('RobotClient', () => {
449460 setupListener ( client ) ;
450461
451462 // Act
452- try {
453- await client . dial ( {
454- ...baseDialConfig ,
455- noReconnect : true ,
456- } ) ;
457- } catch {
458- // Expected to throw
459- }
463+ await client . dial ( {
464+ ...baseDialConfig ,
465+ noReconnect : true ,
466+ } ) ;
460467
461468 // Assert
462469 expect ( events . length ) . toBeGreaterThanOrEqual ( 2 ) ;
@@ -476,14 +483,10 @@ describe('RobotClient', () => {
476483 setupListener ( client ) ;
477484
478485 // Act
479- try {
480- await client . dial ( {
481- host : TEST_HOST ,
482- noReconnect : true ,
483- } ) ;
484- } catch {
485- // Expected to throw
486- }
486+ await client . dial ( {
487+ host : TEST_HOST ,
488+ noReconnect : true ,
489+ } ) ;
487490
488491 // Assert
489492 expect ( events . length ) . toBeGreaterThanOrEqual ( 1 ) ;
@@ -492,83 +495,39 @@ describe('RobotClient', () => {
492495 expect ( ( errorEvent as { error : Error } ) . error ) . toBeInstanceOf ( Error ) ;
493496 } ) ;
494497
495- it ( 'should emit DISCONNECTED events even for non-Error objects ' , async ( ) => {
498+ it ( 'should emit both errors as DISCONNECTED events when both connections fail ' , async ( ) => {
496499 // Arrange
497500 const client = new RobotClient ( ) ;
498- const webrtcError = 'string error' ;
501+ const webrtcError = new Error ( 'WebRTC connection failed' ) ;
499502 const { events, setupListener } = captureDisconnectedEvents ( ) ;
500503
501504 vi . mocked ( rpcModule . dialWebRTC ) . mockRejectedValue ( webrtcError ) ;
502505 setupListener ( client ) ;
503506
504507 // Act
505- try {
506- await client . dial ( {
507- ...baseDialConfig ,
508- noReconnect : true ,
509- } ) ;
510- } catch {
511- // Expected to throw
512- }
508+ await client . dial ( {
509+ ...baseDialConfig ,
510+ noReconnect : true ,
511+ } ) ;
513512
514513 // Assert
515514 expect ( events . length ) . toBeGreaterThanOrEqual ( 2 ) ;
516- const errorEvent = findEventWithError ( events ) ;
517- expect ( errorEvent ) . toBeDefined ( ) ;
518- expect ( ( errorEvent as { error : Error } ) . error ) . toBeInstanceOf ( Error ) ;
519- } ) ;
520-
521- it ( 'should include both errors in the thrown error cause' , async ( ) => {
522- // Arrange
523- const client = new RobotClient ( ) ;
524- const webrtcError = new Error ( 'WebRTC connection failed' ) ;
525-
526- vi . mocked ( rpcModule . dialWebRTC ) . mockRejectedValue ( webrtcError ) ;
527-
528- // Act
529- let caughtError : Error | undefined ;
530- try {
531- await client . dial ( {
532- ...baseDialConfig ,
533- noReconnect : true ,
534- } ) ;
535- } catch ( error ) {
536- caughtError = error as Error ;
537- }
538-
539- // Assert
540- expect ( caughtError ) . toBeDefined ( ) ;
541- expect ( caughtError ) . toBeInstanceOf ( Error ) ;
542- expect ( caughtError ! . message ) . toBe ( 'Failed to connect via all methods' ) ;
543- expect ( caughtError ! . cause ) . toBeDefined ( ) ;
544- expect ( Array . isArray ( caughtError ! . cause ) ) . toBe ( true ) ;
545- const causes = caughtError ! . cause as Error [ ] ;
546- expect ( causes ) . toHaveLength ( 2 ) ;
547- expect ( causes [ 0 ] ) . toBe ( webrtcError ) ;
548- expect ( causes [ 1 ] ) . toBeInstanceOf ( Error ) ;
515+ const webrtcEvent = findEventWithError (
516+ events ,
517+ 'WebRTC connection failed'
518+ ) ;
519+ expect ( webrtcEvent ) . toBeDefined ( ) ;
520+ expect ( webrtcEvent ) . toMatchObject ( { error : webrtcError } ) ;
549521 } ) ;
550522
551- it ( 'should handle non-Error objects thrown from dial methods ' , async ( ) => {
523+ it ( 'should convert non-Error objects to Errors and emit them ' , async ( ) => {
552524 // Arrange
553525 const client = new RobotClient ( ) ;
554526 const webrtcError = 'string error' ;
555- const grpcError = { message : 'object error' } ;
527+ const { events , setupListener } = captureDisconnectedEvents ( ) ;
556528
557529 vi . mocked ( rpcModule . dialWebRTC ) . mockRejectedValue ( webrtcError ) ;
558- vi . mocked ( rpcModule . dialDirect ) . mockRejectedValue ( grpcError ) ;
559-
560- // Act & Assert
561- await expect (
562- client . dial ( {
563- ...baseDialConfig ,
564- noReconnect : true ,
565- } )
566- ) . rejects . toThrow ( 'Failed to connect via all methods' ) ;
567- } ) ;
568-
569- it ( 'should not throw when WebRTC succeeds' , async ( ) => {
570- // Arrange
571- const client = setupClientMocks ( ) ;
530+ setupListener ( client ) ;
572531
573532 // Act
574533 const result = await client . dial ( {
@@ -578,45 +537,42 @@ describe('RobotClient', () => {
578537
579538 // Assert
580539 expect ( result ) . toBe ( client ) ;
540+ expect ( events . length ) . toBeGreaterThanOrEqual ( 1 ) ;
541+ const errorEvent = findEventWithError ( events ) ;
542+ expect ( errorEvent ) . toBeDefined ( ) ;
543+ expect ( ( errorEvent as { error : Error } ) . error ) . toBeInstanceOf ( Error ) ;
544+ expect ( ( errorEvent as { error : Error } ) . error . message ) . toBe (
545+ 'string error'
546+ ) ;
581547 } ) ;
582548
583- it ( 'should not throw when gRPC succeeds after WebRTC fails ' , async ( ) => {
549+ it ( 'should fallback to gRPC when WebRTC fails and emit WebRTC error ' , async ( ) => {
584550 // Arrange
585551 const client = new RobotClient ( ) ;
586552 const webrtcError = new Error ( 'WebRTC connection failed' ) ;
553+ const { events, setupListener } = captureDisconnectedEvents ( ) ;
587554
555+ setupListener ( client ) ;
588556 vi . mocked ( rpcModule . dialWebRTC ) . mockRejectedValue ( webrtcError ) ;
589557 vi . mocked ( rpcModule . dialDirect ) . mockResolvedValue (
590558 createMockRobotServiceTransport ( )
591559 ) ;
592560
593561 // Act
594- // Use a local host so dialDirect validation passes
595562 const result = await client . dial ( {
563+ ...baseDialConfig ,
596564 host : TEST_LOCAL_HOST ,
597565 noReconnect : true ,
598566 } ) ;
599567
600568 // Assert
601569 expect ( result ) . toBe ( client ) ;
602- } ) ;
603-
604- it ( 'should not throw when only gRPC dial is attempted (no WebRTC config)' , async ( ) => {
605- // Arrange
606- const client = new RobotClient ( ) ;
607- vi . mocked ( rpcModule . dialDirect ) . mockResolvedValue (
608- createMockRobotServiceTransport ( )
570+ expect ( events . length ) . toBeGreaterThanOrEqual ( 1 ) ;
571+ const webrtcEvent = findEventWithError (
572+ events ,
573+ 'WebRTC connection failed'
609574 ) ;
610-
611- // Act
612- // Use a local host so dialDirect validation passes
613- const result = await client . dial ( {
614- host : TEST_LOCAL_HOST ,
615- noReconnect : true ,
616- } ) ;
617-
618- // Assert
619- expect ( result ) . toBe ( client ) ;
575+ expect ( webrtcEvent ) . toBeDefined ( ) ;
620576 } ) ;
621577 } ) ;
622578} ) ;
0 commit comments