1- import { describe , expect , it } from "vitest" ;
2- import { isSafeToOverwriteStoredOAuthIdentity , OAuthManagerRefreshError } from "./oauth-manager.js" ;
1+ import fs from "node:fs/promises" ;
2+ import os from "node:os" ;
3+ import path from "node:path" ;
4+ import { afterEach , describe , expect , it , vi } from "vitest" ;
5+ import {
6+ createOAuthManager ,
7+ isSafeToAdoptBootstrapOAuthIdentity ,
8+ isSafeToOverwriteStoredOAuthIdentity ,
9+ OAuthManagerRefreshError ,
10+ } from "./oauth-manager.js" ;
11+ import { ensureAuthProfileStore , saveAuthProfileStore } from "./store.js" ;
312import type { AuthProfileStore , OAuthCredential } from "./types.js" ;
413
514function createCredential ( overrides : Partial < OAuthCredential > = { } ) : OAuthCredential {
@@ -13,6 +22,12 @@ function createCredential(overrides: Partial<OAuthCredential> = {}): OAuthCreden
1322 } ;
1423}
1524
25+ const tempDirs : string [ ] = [ ] ;
26+
27+ afterEach ( async ( ) => {
28+ await Promise . all ( tempDirs . splice ( 0 ) . map ( ( dir ) => fs . rm ( dir , { recursive : true , force : true } ) ) ) ;
29+ } ) ;
30+
1631describe ( "isSafeToOverwriteStoredOAuthIdentity" , ( ) => {
1732 it ( "accepts matching account identities" , ( ) => {
1833 expect (
@@ -40,6 +55,22 @@ describe("isSafeToOverwriteStoredOAuthIdentity", () => {
4055 ) ,
4156 ) . toBe ( false ) ;
4257 } ) ;
58+
59+ it ( "still allows identity-less external bootstrap adoption" , ( ) => {
60+ const existing = createCredential ( {
61+ access : "expired-local-access" ,
62+ refresh : "expired-local-refresh" ,
63+ expires : Date . now ( ) - 60_000 ,
64+ } ) ;
65+ const incoming = createCredential ( {
66+ access : "external-access" ,
67+ refresh : "external-refresh" ,
68+ expires : Date . now ( ) + 60_000 ,
69+ } ) ;
70+
71+ expect ( isSafeToOverwriteStoredOAuthIdentity ( existing , incoming ) ) . toBe ( false ) ;
72+ expect ( isSafeToAdoptBootstrapOAuthIdentity ( existing , incoming ) ) . toBe ( true ) ;
73+ } ) ;
4374} ) ;
4475
4576describe ( "OAuthManagerRefreshError" , ( ) => {
@@ -69,3 +100,63 @@ describe("OAuthManagerRefreshError", () => {
69100 expect ( serialized ) . not . toContain ( "store-refresh" ) ;
70101 } ) ;
71102} ) ;
103+
104+ describe ( "createOAuthManager" , ( ) => {
105+ it ( "refreshes with the adopted external oauth credential" , async ( ) => {
106+ const agentDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "oauth-manager-refresh-" ) ) ;
107+ tempDirs . push ( agentDir ) ;
108+ const profileId = "minimax-portal:default" ;
109+ const localCredential = createCredential ( {
110+ provider : "minimax-portal" ,
111+ access : "stale-local-access" ,
112+ refresh : "stale-local-refresh" ,
113+ expires : Date . now ( ) - 60_000 ,
114+ } ) ;
115+ saveAuthProfileStore (
116+ {
117+ version : 1 ,
118+ profiles : {
119+ [ profileId ] : localCredential ,
120+ } ,
121+ } ,
122+ agentDir ,
123+ ) ;
124+
125+ const manager = createOAuthManager ( {
126+ buildApiKey : async ( _provider , credential ) => credential . access ,
127+ refreshCredential : vi . fn ( async ( credential ) => {
128+ expect ( credential . refresh ) . toBe ( "external-refresh" ) ;
129+ return {
130+ access : "rotated-access" ,
131+ refresh : "rotated-refresh" ,
132+ expires : Date . now ( ) + 60_000 ,
133+ } ;
134+ } ) ,
135+ readBootstrapCredential : ( ) =>
136+ createCredential ( {
137+ provider : "minimax-portal" ,
138+ access : "expired-external-access" ,
139+ refresh : "external-refresh" ,
140+ expires : Date . now ( ) - 30_000 ,
141+ } ) ,
142+ isRefreshTokenReusedError : ( ) => false ,
143+ isSafeToCopyOAuthIdentity : ( ) => true ,
144+ } ) ;
145+
146+ const result = await manager . resolveOAuthAccess ( {
147+ store : ensureAuthProfileStore ( agentDir ) ,
148+ profileId,
149+ credential : localCredential ,
150+ agentDir,
151+ } ) ;
152+
153+ expect ( result ) . toEqual ( {
154+ apiKey : "rotated-access" ,
155+ credential : expect . objectContaining ( {
156+ provider : "minimax-portal" ,
157+ access : "rotated-access" ,
158+ refresh : "rotated-refresh" ,
159+ } ) ,
160+ } ) ;
161+ } ) ;
162+ } ) ;
0 commit comments