@@ -786,6 +786,81 @@ describe.each(testMatrix())(
786786 server,
787787 } ) ;
788788 } ) ;
789+
790+ test ( 'cancel is idempotent' , async ( ) => {
791+ const clientTransport = getClientTransport ( 'client' ) ;
792+ const serverTransport = getServerTransport ( ) ;
793+
794+ const clientProtocolError = vi . fn ( ) ;
795+ const serverProtocolError = vi . fn ( ) ;
796+ clientTransport . addEventListener ( 'protocolError' , clientProtocolError ) ;
797+ serverTransport . addEventListener ( 'protocolError' , serverProtocolError ) ;
798+ addPostTestCleanup ( async ( ) => {
799+ clientTransport . removeEventListener (
800+ 'protocolError' ,
801+ clientProtocolError ,
802+ ) ;
803+ serverTransport . removeEventListener (
804+ 'protocolError' ,
805+ serverProtocolError ,
806+ ) ;
807+ await cleanupTransports ( [ clientTransport , serverTransport ] ) ;
808+ } ) ;
809+
810+ const handler = makeMockHandler ( 'rpc' ) ;
811+ const services = {
812+ service : ServiceSchema . define ( {
813+ rpc : Procedure . rpc ( {
814+ requestInit : Type . Object ( { } ) ,
815+ responseData : Type . Object ( { } ) ,
816+ handler,
817+ } ) ,
818+ } ) ,
819+ } ;
820+
821+ const server = createServer ( serverTransport , services ) ;
822+ const client = createClient < typeof services > (
823+ clientTransport ,
824+ serverTransport . clientId ,
825+ ) ;
826+
827+ const resP = client . service . rpc . rpc ( { } ) ;
828+
829+ await waitFor ( ( ) => {
830+ expect ( handler ) . toHaveBeenCalledTimes ( 1 ) ;
831+ } ) ;
832+
833+ const [ { ctx } ] = handler . mock . calls [ 0 ] ;
834+ const onRequestFinished = vi . fn ( ) ;
835+ ctx . signal . addEventListener ( 'abort' , onRequestFinished ) ;
836+
837+ ctx . cancel ( ) ;
838+
839+ // calling cancel a second time should be a no-op
840+ ctx . cancel ( ) ;
841+
842+ await waitFor ( ( ) => {
843+ expect ( onRequestFinished ) . toHaveBeenCalled ( ) ;
844+ } ) ;
845+
846+ // should only get one cancel error, not two
847+ await expect ( resP ) . resolves . toEqual (
848+ Err ( {
849+ code : CANCEL_CODE ,
850+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
851+ message : expect . any ( String ) ,
852+ } ) ,
853+ ) ;
854+
855+ expect ( clientProtocolError ) . not . toHaveBeenCalled ( ) ;
856+ expect ( serverProtocolError ) . not . toHaveBeenCalled ( ) ;
857+
858+ await testFinishesCleanly ( {
859+ clientTransports : [ clientTransport ] ,
860+ serverTransport,
861+ server,
862+ } ) ;
863+ } ) ;
789864 } ) ;
790865 } ,
791866) ;
0 commit comments