@@ -161,12 +161,14 @@ describe("accounts edge branches", () => {
161161 email : "match@example.com" ,
162162 accessToken : "refreshed-access" ,
163163 expiresAt : now + 300_000 ,
164+ refreshToken : "refreshed-refresh" ,
164165 accountId : "account-from-cache" ,
165166 } ,
166167 {
167168 email : "expired@example.com" ,
168169 accessToken : "expired-access" ,
169170 expiresAt : now - 1 ,
171+ refreshToken : "expired-refresh-updated" ,
170172 accountId : "expired-id" ,
171173 } ,
172174 {
@@ -187,12 +189,94 @@ describe("accounts edge branches", () => {
187189 const snapshot = manager . getAccountsSnapshot ( ) ;
188190 const updated = snapshot [ 0 ] ;
189191 expect ( updated ?. access ) . toBe ( "refreshed-access" ) ;
192+ expect ( updated ?. refreshToken ) . toBe ( "refresh-1" ) ;
190193 expect ( updated ?. accountId ) . toBe ( "account-from-cache" ) ;
191194 expect ( updated ?. accountIdSource ) . toBe ( "token" ) ;
192195
193196 const expired = snapshot [ 1 ] ;
194197 expect ( expired ?. access ) . toBe ( "existing-access" ) ;
195- expect ( expired ?. accountId ) . toBeUndefined ( ) ;
198+ expect ( expired ?. refreshToken ) . toBe ( "refresh-2" ) ;
199+ expect ( expired ?. accountId ) . toBe ( "expired-id" ) ;
200+ expect ( expired ?. accountIdSource ) . toBe ( "token" ) ;
201+ } ) ;
202+
203+ it ( "does not overwrite a local refresh token with a stale usable CLI cache token" , async ( ) => {
204+ const now = Date . now ( ) ;
205+ const stored = buildStored ( [
206+ buildStoredAccount ( {
207+ refreshToken : "local-refresh-new" ,
208+ email : "match@example.com" ,
209+ accessToken : "local-access" ,
210+ expiresAt : now + 120_000 ,
211+ } ) ,
212+ ] ) ;
213+
214+ const { AccountManager } = await importAccountsModule ( ) ;
215+ const manager = new AccountManager ( undefined , stored as never ) ;
216+
217+ mockLoadCodexCliState . mockResolvedValue ( {
218+ sourceUpdatedAtMs : now - 60_000 ,
219+ accounts : [
220+ {
221+ email : "match@example.com" ,
222+ accessToken : "cached-access-old" ,
223+ expiresAt : now + 300_000 ,
224+ refreshToken : "cached-refresh-old" ,
225+ } ,
226+ ] ,
227+ } ) ;
228+
229+ const hydrate = getPrivate < ( ) => Promise < void > > (
230+ manager as object ,
231+ "hydrateFromCodexCli" ,
232+ ) ;
233+ await hydrate . call ( manager ) ;
234+
235+ const snapshot = manager . getAccountsSnapshot ( ) ;
236+ expect ( snapshot [ 0 ] ?. refreshToken ) . toBe ( "local-refresh-new" ) ;
237+ expect ( snapshot [ 0 ] ?. access ) . toBe ( "local-access" ) ;
238+ expect ( mockSaveAccounts ) . not . toHaveBeenCalled ( ) ;
239+ } ) ;
240+
241+ it ( "hydrates a missing local refresh token from an expired CLI cache entry" , async ( ) => {
242+ const now = Date . now ( ) ;
243+ const stored = buildStored ( [
244+ buildStoredAccount ( {
245+ refreshToken : "local-refresh-placeholder" ,
246+ email : "expired@example.com" ,
247+ accessToken : "local-access" ,
248+ expiresAt : now + 120_000 ,
249+ } ) ,
250+ ] ) ;
251+
252+ const { AccountManager } = await importAccountsModule ( ) ;
253+ const manager = new AccountManager ( undefined , stored as never ) ;
254+ const account = manager . getAccountByIndex ( 0 ) ! ;
255+ account . refreshToken = "" ;
256+
257+ mockLoadCodexCliState . mockResolvedValue ( {
258+ sourceUpdatedAtMs : now - 60_000 ,
259+ accounts : [
260+ {
261+ email : "expired@example.com" ,
262+ accessToken : "cached-access-old" ,
263+ expiresAt : now - 1 ,
264+ refreshToken : "cached-refresh-restored" ,
265+ accountId : "expired-account-id" ,
266+ } ,
267+ ] ,
268+ } ) ;
269+
270+ const hydrate = getPrivate < ( ) => Promise < void > > (
271+ manager as object ,
272+ "hydrateFromCodexCli" ,
273+ ) ;
274+ await hydrate . call ( manager ) ;
275+
276+ const snapshot = manager . getAccountsSnapshot ( ) ;
277+ expect ( snapshot [ 0 ] ?. refreshToken ) . toBe ( "cached-refresh-restored" ) ;
278+ expect ( snapshot [ 0 ] ?. access ) . toBe ( "local-access" ) ;
279+ expect ( snapshot [ 0 ] ?. accountId ) . toBe ( "expired-account-id" ) ;
196280 } ) ;
197281
198282 it ( "returns early when Codex CLI state has no usable cache entries" , async ( ) => {
0 commit comments