@@ -2316,6 +2316,111 @@ describe("AccountManager", () => {
23162316 expect ( score ) . toBe ( 100 ) ;
23172317 } ) ;
23182318
2319+ it ( "recordSuccess clears stale auth failure state and persists the healed account" , async ( ) => {
2320+ const { saveAccounts } = await import ( "../lib/storage.js" ) ;
2321+ const mockSaveAccounts = vi . mocked ( saveAccounts ) ;
2322+ mockSaveAccounts . mockClear ( ) ;
2323+
2324+ const now = Date . now ( ) ;
2325+ const stored = {
2326+ version : 3 as const ,
2327+ activeIndex : 0 ,
2328+ accounts : [
2329+ {
2330+ refreshToken : "token-1" ,
2331+ addedAt : now ,
2332+ lastUsed : now ,
2333+ consecutiveAuthFailures : 2 ,
2334+ coolingDownUntil : now - 1000 ,
2335+ cooldownReason : "network-error" as const ,
2336+ } ,
2337+ ] ,
2338+ } ;
2339+
2340+ const manager = new AccountManager ( undefined , stored ) ;
2341+ const account = manager . getCurrentAccount ( ) ! ;
2342+ account . consecutiveAuthFailures = 2 ;
2343+
2344+ manager . recordSuccess ( account , "codex" , "gpt-5.1" ) ;
2345+ await manager . flushPendingSave ( ) ;
2346+
2347+ expect ( account . consecutiveAuthFailures ) . toBe ( 0 ) ;
2348+ expect ( account . coolingDownUntil ) . toBeUndefined ( ) ;
2349+ expect ( account . cooldownReason ) . toBeUndefined ( ) ;
2350+ expect ( mockSaveAccounts ) . toHaveBeenCalledTimes ( 1 ) ;
2351+ const persisted = mockSaveAccounts . mock . calls [ 0 ] ?. [ 0 ] ;
2352+ expect ( persisted ?. accounts [ 0 ] ?. consecutiveAuthFailures ?? 0 ) . toBe ( 0 ) ;
2353+ expect ( persisted ?. accounts [ 0 ] ?. coolingDownUntil ) . toBeUndefined ( ) ;
2354+ expect ( persisted ?. accounts [ 0 ] ?. cooldownReason ) . toBeUndefined ( ) ;
2355+ } ) ;
2356+
2357+ it ( "recordSuccess clears stale cooldown metadata when only the reason remains" , async ( ) => {
2358+ const { saveAccounts } = await import ( "../lib/storage.js" ) ;
2359+ const mockSaveAccounts = vi . mocked ( saveAccounts ) ;
2360+ mockSaveAccounts . mockClear ( ) ;
2361+
2362+ const now = Date . now ( ) ;
2363+ const stored = {
2364+ version : 3 as const ,
2365+ activeIndex : 0 ,
2366+ accounts : [
2367+ {
2368+ refreshToken : "token-1" ,
2369+ addedAt : now ,
2370+ lastUsed : now ,
2371+ cooldownReason : "network-error" as const ,
2372+ } ,
2373+ ] ,
2374+ } ;
2375+
2376+ const manager = new AccountManager ( undefined , stored ) ;
2377+ const account = manager . getCurrentAccount ( ) ! ;
2378+
2379+ manager . recordSuccess ( account , "codex" , "gpt-5.1" ) ;
2380+ await manager . flushPendingSave ( ) ;
2381+
2382+ expect ( account . coolingDownUntil ) . toBeUndefined ( ) ;
2383+ expect ( account . cooldownReason ) . toBeUndefined ( ) ;
2384+ expect ( mockSaveAccounts ) . toHaveBeenCalledTimes ( 1 ) ;
2385+ const persisted = mockSaveAccounts . mock . calls [ 0 ] ?. [ 0 ] ;
2386+ expect ( persisted ?. accounts [ 0 ] ?. coolingDownUntil ) . toBeUndefined ( ) ;
2387+ expect ( persisted ?. accounts [ 0 ] ?. cooldownReason ) . toBeUndefined ( ) ;
2388+ } ) ;
2389+
2390+ it ( "recordSuccess does not clear an active cooldown from a newer concurrent failure" , async ( ) => {
2391+ const { saveAccounts } = await import ( "../lib/storage.js" ) ;
2392+ const mockSaveAccounts = vi . mocked ( saveAccounts ) ;
2393+ mockSaveAccounts . mockClear ( ) ;
2394+
2395+ const now = Date . now ( ) ;
2396+ const stored = {
2397+ version : 3 as const ,
2398+ activeIndex : 0 ,
2399+ accounts : [
2400+ {
2401+ refreshToken : "token-1" ,
2402+ addedAt : now ,
2403+ lastUsed : now ,
2404+ consecutiveAuthFailures : 2 ,
2405+ coolingDownUntil : now + 60_000 ,
2406+ cooldownReason : "auth-failure" as const ,
2407+ } ,
2408+ ] ,
2409+ } ;
2410+
2411+ const manager = new AccountManager ( undefined , stored ) ;
2412+ const account = manager . getCurrentAccount ( ) ! ;
2413+ account . consecutiveAuthFailures = 2 ;
2414+
2415+ manager . recordSuccess ( account , "codex" , "gpt-5.1" ) ;
2416+ await manager . flushPendingSave ( ) ;
2417+
2418+ expect ( account . consecutiveAuthFailures ) . toBe ( 2 ) ;
2419+ expect ( account . coolingDownUntil ) . toBe ( now + 60_000 ) ;
2420+ expect ( account . cooldownReason ) . toBe ( "auth-failure" ) ;
2421+ expect ( mockSaveAccounts ) . not . toHaveBeenCalled ( ) ;
2422+ } ) ;
2423+
23192424 it ( "recordRateLimit updates health and drains token bucket" , ( ) => {
23202425 const now = Date . now ( ) ;
23212426 const stored = {
0 commit comments