@@ -16,10 +16,11 @@ import (
1616// refreshAPIAccountRepo implements AccountRepository for OAuthRefreshAPI tests.
1717type refreshAPIAccountRepo struct {
1818 mockAccountRepoForGemini
19- account * Account // returned by GetByID
20- getByIDErr error
21- updateErr error
22- updateCalls int
19+ account * Account // returned by GetByID
20+ getByIDErr error
21+ updateErr error
22+ updateCalls int
23+ updateCredentialsCalls int
2324}
2425
2526func (r * refreshAPIAccountRepo ) GetByID (_ context.Context , _ int64 ) (* Account , error ) {
@@ -34,6 +35,19 @@ func (r *refreshAPIAccountRepo) Update(_ context.Context, _ *Account) error {
3435 return r .updateErr
3536}
3637
38+ func (r * refreshAPIAccountRepo ) UpdateCredentials (_ context.Context , id int64 , credentials map [string ]any ) error {
39+ r .updateCalls ++
40+ r .updateCredentialsCalls ++
41+ if r .updateErr != nil {
42+ return r .updateErr
43+ }
44+ if r .account == nil || r .account .ID != id {
45+ r .account = & Account {ID : id }
46+ }
47+ r .account .Credentials = cloneCredentials (credentials )
48+ return nil
49+ }
50+
3751// refreshAPIExecutorStub implements OAuthRefreshExecutor for tests.
3852type refreshAPIExecutorStub struct {
3953 needsRefresh bool
@@ -106,10 +120,36 @@ func TestRefreshIfNeeded_Success(t *testing.T) {
106120 require .Equal (t , "new-token" , result .NewCredentials ["access_token" ])
107121 require .NotNil (t , result .NewCredentials ["_token_version" ]) // version stamp set
108122 require .Equal (t , 1 , repo .updateCalls ) // DB updated
109- require .Equal (t , 1 , cache .releaseCalls ) // lock released
123+ require .Equal (t , 1 , repo .updateCredentialsCalls )
124+ require .Equal (t , 1 , cache .releaseCalls ) // lock released
110125 require .Equal (t , 1 , executor .refreshCalls )
111126}
112127
128+ func TestRefreshIfNeeded_UpdateCredentialsPreservesRateLimitState (t * testing.T ) {
129+ resetAt := time .Now ().Add (45 * time .Minute )
130+ account := & Account {
131+ ID : 11 ,
132+ Platform : PlatformGemini ,
133+ Type : AccountTypeOAuth ,
134+ RateLimitResetAt : & resetAt ,
135+ }
136+ repo := & refreshAPIAccountRepo {account : account }
137+ cache := & refreshAPICacheStub {lockResult : true }
138+ executor := & refreshAPIExecutorStub {
139+ needsRefresh : true ,
140+ credentials : map [string ]any {"access_token" : "safe-token" },
141+ }
142+
143+ api := NewOAuthRefreshAPI (repo , cache )
144+ result , err := api .RefreshIfNeeded (context .Background (), account , executor , 3 * time .Minute )
145+
146+ require .NoError (t , err )
147+ require .True (t , result .Refreshed )
148+ require .Equal (t , 1 , repo .updateCredentialsCalls )
149+ require .NotNil (t , repo .account .RateLimitResetAt )
150+ require .WithinDuration (t , resetAt , * repo .account .RateLimitResetAt , time .Second )
151+ }
152+
113153func TestRefreshIfNeeded_LockHeld (t * testing.T ) {
114154 account := & Account {ID : 2 , Platform : PlatformAnthropic }
115155 repo := & refreshAPIAccountRepo {account : account }
@@ -193,7 +233,7 @@ func TestRefreshIfNeeded_RefreshError(t *testing.T) {
193233 require .Error (t , err )
194234 require .Nil (t , result )
195235 require .Contains (t , err .Error (), "invalid_grant" )
196- require .Equal (t , 0 , repo .updateCalls ) // no DB update on refresh error
236+ require .Equal (t , 0 , repo .updateCalls ) // no DB update on refresh error
197237 require .Equal (t , 1 , cache .releaseCalls ) // lock still released via defer
198238}
199239
@@ -299,8 +339,8 @@ func TestMergeCredentials_NewOverridesOld(t *testing.T) {
299339
300340 result := MergeCredentials (old , new )
301341
302- require .Equal (t , "new-token" , result ["access_token" ]) // overridden
303- require .Equal (t , "old-refresh" , result ["refresh_token" ]) // preserved
342+ require .Equal (t , "new-token" , result ["access_token" ]) // overridden
343+ require .Equal (t , "old-refresh" , result ["refresh_token" ]) // preserved
304344}
305345
306346// ========== BuildClaudeAccountCredentials tests ==========
0 commit comments