@@ -164,15 +164,37 @@ describe('withMcpOauthRefreshLock', () => {
164164 expect ( fn ) . not . toHaveBeenCalled ( )
165165 } )
166166
167- it ( 'falls open when Redis is unavailable on acquire ' , async ( ) => {
167+ it ( 'cleans up an uncertain owner token before falling open when Redis is unavailable' , async ( ) => {
168168 mockAcquireLock . mockRejectedValueOnce ( new Error ( 'Redis connection refused' ) )
169169 const fn = vi . fn ( async ( ) => 'uncoordinated' )
170170
171171 const result = await withMcpOauthRefreshLock ( 'row-redis-down' , fn )
172172
173173 expect ( result ) . toBe ( 'uncoordinated' )
174174 expect ( fn ) . toHaveBeenCalledTimes ( 1 )
175- expect ( mockReleaseLock ) . not . toHaveBeenCalled ( )
175+ expect ( mockReleaseLock ) . toHaveBeenCalledWith (
176+ 'mcp:oauth:refresh:row-redis-down' ,
177+ expect . any ( String )
178+ )
179+ } )
180+
181+ it ( 'cleans up an uncertain owner token before propagating cancellation' , async ( ) => {
182+ const controller = new AbortController ( )
183+ mockAcquireLock . mockImplementationOnce ( async ( ) => {
184+ controller . abort ( new Error ( 'cancelled' ) )
185+ throw new Error ( 'Redis operation timed out' )
186+ } )
187+ const fn = vi . fn ( async ( ) => 'should-not-run' )
188+
189+ await expect (
190+ withMcpOauthRefreshLock ( 'row-aborted-acquire' , fn , controller . signal )
191+ ) . rejects . toThrow ( 'cancelled' )
192+
193+ expect ( mockReleaseLock ) . toHaveBeenCalledWith (
194+ 'mcp:oauth:refresh:row-aborted-acquire' ,
195+ expect . any ( String )
196+ )
197+ expect ( fn ) . not . toHaveBeenCalled ( )
176198 } )
177199
178200 it ( 'releases the lock even when fn throws' , async ( ) => {
0 commit comments