@@ -7,6 +7,7 @@ import { VerifiableCertificate } from '../../auth/certificates/VerifiableCertifi
77import { MasterCertificate } from '../../auth/certificates/MasterCertificate.js'
88import { getVerifiableCertificates } from '../../auth/utils/getVerifiableCertificates.js'
99import { CompletedProtoWallet } from '../certificates/__tests/CompletedProtoWallet.js'
10+ import { SimplifiedFetchTransport } from '../../auth/transports/SimplifiedFetchTransport.js'
1011
1112const certifierPrivKey = new PrivateKey ( 21 )
1213const alicePrivKey = new PrivateKey ( 22 )
@@ -355,7 +356,7 @@ describe('Peer class mutual authentication and certificate exchange', () => {
355356 // console.error(e)
356357 } )
357358 } )
358- } )
359+ } )
359360
360361 await alice . toPeer ( Utils . toArray ( 'Hello Bob!' ) )
361362 await bobReceivedGeneralMessage
@@ -364,6 +365,57 @@ describe('Peer class mutual authentication and certificate exchange', () => {
364365 expect ( certificatesReceivedByBob ) . toEqual ( [ aliceVerifiableCertificate ] )
365366 } , 15000 )
366367
368+ describe ( 'propagateTransportError' , ( ) => {
369+ const createPeerInstance = ( ) : Peer => {
370+ const transport : Transport = {
371+ send : jest . fn ( async ( _message : AuthMessage ) => { } ) ,
372+ onData : async ( ) => { }
373+ }
374+ return new Peer ( { } as WalletInterface , transport )
375+ }
376+
377+ it ( 'adds peer identity details to existing errors' , ( ) => {
378+ const peer = createPeerInstance ( )
379+ const originalError = new Error ( 'send failed' )
380+
381+ let thrown : Error | undefined
382+ try {
383+ ( peer as any ) . propagateTransportError ( 'peer-public-key' , originalError )
384+ } catch ( error ) {
385+ thrown = error as Error
386+ }
387+
388+ expect ( thrown ) . toBe ( originalError )
389+ expect ( ( thrown as any ) . details ) . toEqual ( { peerIdentityKey : 'peer-public-key' } )
390+ } )
391+
392+ it ( 'preserves existing details when appending peer identity' , ( ) => {
393+ const peer = createPeerInstance ( )
394+ const originalError = new Error ( 'existing details' )
395+ ; ( originalError as any ) . details = { status : 503 }
396+
397+ let thrown : Error | undefined
398+ try {
399+ ( peer as any ) . propagateTransportError ( 'peer-public-key' , originalError )
400+ } catch ( error ) {
401+ thrown = error as Error
402+ }
403+
404+ expect ( thrown ) . toBe ( originalError )
405+ expect ( ( thrown as any ) . details ) . toEqual ( {
406+ status : 503 ,
407+ peerIdentityKey : 'peer-public-key'
408+ } )
409+ } )
410+
411+ it ( 'wraps non-error values with a helpful message' , ( ) => {
412+ const peer = createPeerInstance ( )
413+ expect ( ( ) => ( peer as any ) . propagateTransportError ( undefined , 'timeout' ) ) . toThrow (
414+ 'Failed to send message to peer unknown: timeout'
415+ )
416+ } )
417+ } )
418+
367419 it ( 'Alice requests Bob to present his library card before lending him a book' , async ( ) => {
368420 const alicePubKey = ( await walletA . getPublicKey ( { identityKey : true } ) )
369421 . publicKey
@@ -818,4 +870,189 @@ describe('Peer class mutual authentication and certificate exchange', () => {
818870 expect ( certificatesReceivedByAlice ) . toEqual ( [ bobVerifiableCertificate ] )
819871 expect ( certificatesReceivedByBob ) . toEqual ( [ aliceVerifiableCertificate ] )
820872 } , 20000 )
873+
874+ describe ( 'Transport Error Handling' , ( ) => {
875+ const privKey = PrivateKey . fromRandom ( )
876+
877+ test ( 'Should trigger "Failed to send message to peer" error with network failure' , async ( ) => {
878+ // Create a mock fetch that always fails
879+ const failingFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( new Error ( 'Network connection failed' ) )
880+
881+ // Create a transport that will fail
882+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , failingFetch )
883+
884+ // Create a peer with the failing transport
885+ const wallet = new CompletedProtoWallet ( privKey )
886+ const peer = new Peer ( wallet , transport )
887+
888+ // Register a dummy onData callback (required before sending)
889+ await transport . onData ( async ( message ) => {
890+ // This won't be called due to network failure
891+ } )
892+
893+ // Try to send a message to peer - this should fail and trigger the error
894+ try {
895+ await peer . toPeer ( [ 1 , 2 , 3 , 4 ] , '03abc123def456' )
896+ fail ( 'Expected error to be thrown' )
897+ } catch ( error : any ) {
898+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
899+ expect ( error . message ) . toContain ( 'Network connection failed' )
900+ }
901+ } , 15000 )
902+
903+ test ( 'Should trigger error with connection timeout' , async ( ) => {
904+ // Create a fetch that times out
905+ const timeoutFetch = ( jest . fn ( ) as any ) . mockImplementation ( ( ) => {
906+ return new Promise ( ( _ , reject ) => {
907+ setTimeout ( ( ) => {
908+ reject ( new Error ( 'Request timeout' ) )
909+ } , 100 )
910+ } )
911+ } )
912+
913+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , timeoutFetch )
914+ const wallet = new CompletedProtoWallet ( privKey )
915+ const peer = new Peer ( wallet , transport )
916+
917+ await transport . onData ( async ( message ) => { } )
918+
919+ try {
920+ await peer . toPeer ( [ 5 , 6 , 7 , 8 ] , '03def789abc123' )
921+ fail ( 'Expected error to be thrown' )
922+ } catch ( error : any ) {
923+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
924+ expect ( error . message ) . toContain ( 'Request timeout' )
925+ }
926+ } , 15000 )
927+
928+ test ( 'Should trigger error with DNS resolution failure' , async ( ) => {
929+ // Create a fetch that fails with DNS error
930+ const dnsFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( {
931+ code : 'ENOTFOUND' ,
932+ errno : - 3008 ,
933+ message : 'getaddrinfo ENOTFOUND nonexistent.domain'
934+ } )
935+
936+ const transport = new SimplifiedFetchTransport ( 'http://nonexistent.domain:3000' , dnsFetch )
937+ const wallet = new CompletedProtoWallet ( privKey )
938+ const peer = new Peer ( wallet , transport )
939+
940+ await transport . onData ( async ( message ) => { } )
941+
942+ try {
943+ await peer . toPeer ( [ 9 , 10 , 11 , 12 ] , '03xyz987fed654' )
944+ fail ( 'Expected error to be thrown' )
945+ } catch ( error : any ) {
946+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
947+ expect ( error . message ) . toContain ( '[object Object]' )
948+ }
949+ } , 15000 )
950+
951+ test ( 'Should trigger error during certificate request send' , async ( ) => {
952+ // Create a failing fetch
953+ const failingFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( new Error ( 'Connection reset by peer' ) )
954+
955+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , failingFetch )
956+ const wallet = new CompletedProtoWallet ( privKey )
957+ const peer = new Peer ( wallet , transport )
958+
959+ await transport . onData ( async ( message ) => { } )
960+
961+ try {
962+ // Try to send a certificate request - this should also trigger the error
963+ await peer . requestCertificates ( {
964+ certifiers : [ '03certifier123' ] ,
965+ types : { 'type1' : [ 'field1' ] }
966+ } , '03abc123def456' )
967+ fail ( 'Expected error to be thrown' )
968+ } catch ( error : any ) {
969+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
970+ expect ( error . message ) . toContain ( 'Connection reset by peer' )
971+ }
972+ } , 15000 )
973+
974+ test ( 'Should trigger error during certificate response send' , async ( ) => {
975+ // Create a failing fetch
976+ const failingFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( new Error ( 'Socket hang up' ) )
977+
978+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , failingFetch )
979+ const wallet = new CompletedProtoWallet ( privKey )
980+ const peer = new Peer ( wallet , transport )
981+
982+ await transport . onData ( async ( message ) => { } )
983+
984+ try {
985+ // Try to send a certificate response - this should also trigger the error
986+ await peer . sendCertificateResponse ( '03verifier123' , [ ] )
987+ fail ( 'Expected error to be thrown' )
988+ } catch ( error : any ) {
989+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
990+ expect ( error . message ) . toContain ( 'Socket hang up' )
991+ }
992+ } , 15000 )
993+
994+ test ( 'Should propagate network errors with proper details' , async ( ) => {
995+ // Create a fetch that throws a custom error
996+ const customError = new Error ( 'Custom transport error' )
997+ customError . stack = 'Custom stack trace'
998+ const failingFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( customError )
999+
1000+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , failingFetch )
1001+ const wallet = new CompletedProtoWallet ( privKey )
1002+ const peer = new Peer ( wallet , transport )
1003+
1004+ await transport . onData ( async ( message ) => { } )
1005+
1006+ try {
1007+ await peer . toPeer ( [ 13 , 14 , 15 , 16 ] , '03peer123456' )
1008+ fail ( 'Expected error to be thrown' )
1009+ } catch ( error : any ) {
1010+ // Should create a network error wrapping the original error
1011+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
1012+ expect ( error . message ) . toContain ( 'Custom transport error' )
1013+ expect ( error . cause ) . toBeDefined ( )
1014+ expect ( error . cause . message ) . toBe ( 'Custom transport error' )
1015+ }
1016+ } , 15000 )
1017+
1018+ test ( 'Should handle non-Error transport failures' , async ( ) => {
1019+ // Create a fetch that throws a non-Error object
1020+ const failingFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( 'String error message' )
1021+
1022+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , failingFetch )
1023+ const wallet = new CompletedProtoWallet ( privKey )
1024+ const peer = new Peer ( wallet , transport )
1025+
1026+ await transport . onData ( async ( message ) => { } )
1027+
1028+ try {
1029+ await peer . toPeer ( [ 17 , 18 , 19 , 20 ] , '03peer789abc' )
1030+ fail ( 'Expected error to be thrown' )
1031+ } catch ( error : any ) {
1032+ // Should create network error for non-Error objects
1033+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
1034+ expect ( error . message ) . toContain ( 'String error message' )
1035+ }
1036+ } , 15000 )
1037+
1038+ test ( 'Should handle undefined peer identity gracefully' , async ( ) => {
1039+ // Create a failing fetch
1040+ const failingFetch = ( jest . fn ( ) as any ) . mockRejectedValue ( 'Network failure' )
1041+
1042+ const transport = new SimplifiedFetchTransport ( 'http://localhost:9999' , failingFetch )
1043+ const wallet = new CompletedProtoWallet ( privKey )
1044+ const peer = new Peer ( wallet , transport )
1045+
1046+ await transport . onData ( async ( message ) => { } )
1047+
1048+ try {
1049+ // Try to send to an undefined peer (this might happen in some edge cases)
1050+ await peer . toPeer ( [ 21 , 22 , 23 , 24 ] , undefined as any )
1051+ fail ( 'Expected error to be thrown' )
1052+ } catch ( error : any ) {
1053+ expect ( error . message ) . toContain ( 'Network error while sending authenticated request' )
1054+ expect ( error . message ) . toContain ( 'Network failure' )
1055+ }
1056+ } , 15000 )
1057+ } )
8211058} )
0 commit comments