@@ -706,5 +706,124 @@ describe('request methods', () => {
706706 expect ( fetchOptions . signal ) . to . be . instanceOf ( AbortSignal ) ;
707707 expect ( fetchOptions . signal ?. aborted ) . to . be . false ;
708708 } ) ;
709+
710+ it ( 'should remove abort listener on successful completion to prevent memory leaks' , async ( ) => {
711+ const controller = new AbortController ( ) ;
712+ const addSpy = Sinon . spy ( controller . signal , 'addEventListener' ) ;
713+ const removeSpy = Sinon . spy ( controller . signal , 'removeEventListener' ) ;
714+
715+ const mockResponse = new Response ( '{}' , {
716+ status : 200 ,
717+ statusText : 'OK'
718+ } ) ;
719+ fetchStub . resolves ( mockResponse ) ;
720+
721+ await makeRequest (
722+ {
723+ model : 'models/model-name' ,
724+ task : Task . GENERATE_CONTENT ,
725+ apiSettings : fakeApiSettings ,
726+ stream : false ,
727+ singleRequestOptions : { signal : controller . signal }
728+ } ,
729+ '{}'
730+ ) ;
731+
732+ expect ( addSpy ) . to . have . been . calledOnceWith ( 'abort' ) ;
733+ expect ( removeSpy ) . to . have . been . calledOnceWith ( 'abort' ) ;
734+ } ) ;
735+
736+ it ( 'should remove listener if fetch itself rejects' , async ( ) => {
737+ const controller = new AbortController ( ) ;
738+ const removeSpy = Sinon . spy ( controller . signal , 'removeEventListener' ) ;
739+ const error = new Error ( 'Network failure' ) ;
740+ fetchStub . rejects ( error ) ;
741+
742+ const requestPromise = makeRequest (
743+ {
744+ model : 'models/model-name' ,
745+ task : Task . GENERATE_CONTENT ,
746+ apiSettings : fakeApiSettings ,
747+ stream : false ,
748+ singleRequestOptions : { signal : controller . signal }
749+ } ,
750+ '{}'
751+ ) ;
752+
753+ await expect ( requestPromise ) . to . be . rejectedWith ( AIError , / N e t w o r k f a i l u r e / ) ;
754+ expect ( removeSpy ) . to . have . been . calledOnce ;
755+ } ) ;
756+
757+ it ( 'should remove listener if response is not ok' , async ( ) => {
758+ const controller = new AbortController ( ) ;
759+ const removeSpy = Sinon . spy ( controller . signal , 'removeEventListener' ) ;
760+ const mockResponse = new Response ( '{}' , {
761+ status : 500 ,
762+ statusText : 'Internal Server Error'
763+ } ) ;
764+ fetchStub . resolves ( mockResponse ) ;
765+
766+ const requestPromise = makeRequest (
767+ {
768+ model : 'models/model-name' ,
769+ task : Task . GENERATE_CONTENT ,
770+ apiSettings : fakeApiSettings ,
771+ stream : false ,
772+ singleRequestOptions : { signal : controller . signal }
773+ } ,
774+ '{}'
775+ ) ;
776+
777+ await expect ( requestPromise ) . to . be . rejectedWith ( AIError , / 5 0 0 / ) ;
778+ expect ( removeSpy ) . to . have . been . calledOnce ;
779+ } ) ;
780+
781+ it ( 'should abort immediately if timeout is 0' , async ( ) => {
782+ fetchStub . callsFake ( fetchAborter ) ;
783+ const requestPromise = makeRequest (
784+ {
785+ model : 'models/model-name' ,
786+ task : Task . GENERATE_CONTENT ,
787+ apiSettings : fakeApiSettings ,
788+ stream : false ,
789+ singleRequestOptions : { timeout : 0 }
790+ } ,
791+ '{}'
792+ ) ;
793+
794+ // Tick the clock just enough to trigger a timeout(0)
795+ await clock . tickAsync ( 1 ) ;
796+
797+ await expect ( requestPromise ) . to . be . rejectedWith (
798+ AIError ,
799+ / T i m e o u t h a s e x p i r e d /
800+ ) ;
801+ } ) ;
802+
803+ it ( 'should not error if signal is aborted after completion' , async ( ) => {
804+ const controller = new AbortController ( ) ;
805+ const removeSpy = Sinon . spy ( controller . signal , 'removeEventListener' ) ;
806+ const mockResponse = new Response ( '{}' , {
807+ status : 200 ,
808+ statusText : 'OK'
809+ } ) ;
810+ fetchStub . resolves ( mockResponse ) ;
811+
812+ await makeRequest (
813+ {
814+ model : 'models/model-name' ,
815+ task : Task . GENERATE_CONTENT ,
816+ apiSettings : fakeApiSettings ,
817+ stream : false ,
818+ singleRequestOptions : { signal : controller . signal }
819+ } ,
820+ '{}'
821+ ) ;
822+
823+ // Listener should be removed, so this abort should do nothing.
824+ controller . abort ( 'Too late' ) ;
825+
826+ expect ( removeSpy ) . to . have . been . calledOnce ;
827+ } ) ;
709828 } ) ;
710829} ) ;
0 commit comments