@@ -120,6 +120,34 @@ describe("background service worker", () => {
120120 expect ( failed ) . toMatchObject ( { ok : false , error : "connection refused" } ) ;
121121 } ) ;
122122
123+ it ( "REGRESSION (#7007): bounds the miner-ui fetch with a 3s AbortSignal timeout" , async ( ) => {
124+ const timeoutSpy = vi . spyOn ( AbortSignal , "timeout" ) ;
125+ const { backgroundInternals } = await loadExtensionModules ( {
126+ fetchImpl : jsonFetch ( 200 , { candidates : [ ] } ) ,
127+ } ) ;
128+ await backgroundInternals . syncRankedCandidatesFromMinerUi ( ) ;
129+ expect ( timeoutSpy ) . toHaveBeenCalledWith ( 3000 ) ;
130+ timeoutSpy . mockRestore ( ) ;
131+ } ) ;
132+
133+ it ( "REGRESSION (#7007): a stalled miner-ui connection times out instead of hanging the sync alarm forever" , async ( ) => {
134+ // Mirrors what a real fetch does under an aborted signal: the promise never resolves on its own, it only
135+ // rejects once the signal fires -- so this proves the timeout actually bounds a genuinely-hung connection,
136+ // not just a fast-failing one.
137+ const hangingFetch = ( async ( _url : string , init ?: RequestInit ) =>
138+ new Promise ( ( _resolve , reject ) => {
139+ init ?. signal ?. addEventListener ( "abort" , ( ) => reject ( new DOMException ( "The operation was aborted." , "TimeoutError" ) ) ) ;
140+ } ) ) as typeof fetch ;
141+ const { backgroundInternals } = await loadExtensionModules ( { fetchImpl : hangingFetch } ) ;
142+
143+ const startedAt = Date . now ( ) ;
144+ const result = await backgroundInternals . syncRankedCandidatesFromMinerUi ( ) ;
145+ const elapsedMs = Date . now ( ) - startedAt ;
146+
147+ expect ( result . ok ) . toBe ( false ) ;
148+ expect ( elapsedMs ) . toBeLessThan ( 4000 ) ; // bounded well under what an unbounded hang would take
149+ } , 10_000 ) ;
150+
123151 it ( "falls back to the default miner UI URL when sync storage is empty or malformed" , async ( ) => {
124152 const empty = await loadExtensionModules ( { minerUiUrl : "" } ) ;
125153 expect ( await empty . backgroundInternals . loadMinerUiUrl ( ) ) . toBe (
0 commit comments