@@ -2953,9 +2953,9 @@ describe("codex manager cli commands", () => {
29532953 expect ( withAccountStorageTransactionMock ) . toHaveBeenCalledTimes ( 1 ) ;
29542954 expect ( storageState . accounts ) . toHaveLength ( 2 ) ;
29552955 expect ( storageState . activeIndex ) . toBe ( 1 ) ;
2956- expect ( storageState . activeIndexByFamily . codex ) . toBe ( 1 ) ;
2957- expect ( setCodexCliActiveSelectionMock ) . toHaveBeenCalledTimes ( 1 ) ;
2958- } ) ;
2956+ expect ( storageState . activeIndexByFamily . codex ) . toBe ( 1 ) ;
2957+ expect ( setCodexCliActiveSelectionMock ) . toHaveBeenCalledTimes ( 1 ) ;
2958+ } ) ;
29592959
29602960 it ( "supports --manual login without launching a browser" , async ( ) => {
29612961 setInteractiveTTY ( true ) ;
@@ -3000,13 +3000,19 @@ describe("codex manager cli commands", () => {
30003000 promptQuestionMock . mockResolvedValueOnce (
30013001 "http://127.0.0.1:1455/auth/callback?code=oauth-code&state=oauth-state" ,
30023002 ) ;
3003+ const logSpy = vi . spyOn ( console , "log" ) . mockImplementation ( ( ) => { } ) ;
30033004
30043005 const { runCodexMultiAuthCli } = await import ( "../lib/codex-manager.js" ) ;
30053006 const exitCode = await runCodexMultiAuthCli ( [ "auth" , "login" , "--manual" ] ) ;
3007+ const renderedLogs = logSpy . mock . calls . flat ( ) . map ( ( entry ) => String ( entry ) ) ;
30063008
30073009 expect ( exitCode ) . toBe ( 0 ) ;
30083010 expect ( openBrowserUrlMock ) . not . toHaveBeenCalled ( ) ;
30093011 expect ( waitForCodeMock ) . not . toHaveBeenCalled ( ) ;
3012+ expect ( renderedLogs . some ( ( entry ) => entry . includes ( "Callback listener unavailable" ) ) ) . toBe (
3013+ true ,
3014+ ) ;
3015+ expect ( renderedLogs . some ( ( entry ) => entry . includes ( "No callback received" ) ) ) . toBe ( false ) ;
30103016 expect ( storageState . accounts ) . toHaveLength ( 1 ) ;
30113017 } ) ;
30123018
@@ -3110,6 +3116,48 @@ describe("codex manager cli commands", () => {
31103116 expect ( storageState . accounts ) . toHaveLength ( 1 ) ;
31113117 } ) ;
31123118
3119+ it ( "rejects mismatched manual callback state in non-tty mode without persisting login" , async ( ) => {
3120+ setInteractiveTTY ( false ) ;
3121+ let storageState = {
3122+ version : 3 as const ,
3123+ activeIndex : 0 ,
3124+ activeIndexByFamily : { codex : 0 } ,
3125+ accounts : [ ] as Array < Record < string , unknown > > ,
3126+ } ;
3127+ loadAccountsMock . mockImplementation ( async ( ) => structuredClone ( storageState ) ) ;
3128+ saveAccountsMock . mockImplementation ( async ( nextStorage ) => {
3129+ storageState = structuredClone ( nextStorage ) ;
3130+ } ) ;
3131+ promptLoginModeMock . mockResolvedValueOnce ( { mode : "cancel" } ) ;
3132+ promptQuestionMock . mockResolvedValueOnce (
3133+ "http://127.0.0.1:1455/auth/callback?code=oauth-code&state=wrong-state" ,
3134+ ) ;
3135+
3136+ const authModule = await import ( "../lib/auth/auth.js" ) ;
3137+ vi . mocked ( authModule . createAuthorizationFlow ) . mockResolvedValueOnce ( {
3138+ pkce : { challenge : "pkce-challenge" , verifier : "pkce-verifier" } ,
3139+ state : "oauth-state" ,
3140+ url : "https://auth.openai.com/mock" ,
3141+ } ) ;
3142+ const exchangeAuthorizationCodeMock = vi . mocked ( authModule . exchangeAuthorizationCode ) ;
3143+
3144+ const browserModule = await import ( "../lib/auth/browser.js" ) ;
3145+ const openBrowserUrlMock = vi . mocked ( browserModule . openBrowserUrl ) ;
3146+ const serverModule = await import ( "../lib/auth/server.js" ) ;
3147+ vi . mocked ( serverModule . startLocalOAuthServer ) . mockRejectedValueOnce (
3148+ new Error ( "port in use" ) ,
3149+ ) ;
3150+
3151+ const { runCodexMultiAuthCli } = await import ( "../lib/codex-manager.js" ) ;
3152+ const exitCode = await runCodexMultiAuthCli ( [ "auth" , "login" , "--manual" ] ) ;
3153+
3154+ expect ( exitCode ) . toBe ( 0 ) ;
3155+ expect ( promptQuestionMock ) . toHaveBeenCalledWith ( "" ) ;
3156+ expect ( openBrowserUrlMock ) . not . toHaveBeenCalled ( ) ;
3157+ expect ( exchangeAuthorizationCodeMock ) . not . toHaveBeenCalled ( ) ;
3158+ expect ( storageState . accounts ) . toHaveLength ( 0 ) ;
3159+ } ) ;
3160+
31133161 it ( "falls back to pasted manual input when Windows-style callback bind fails" , async ( ) => {
31143162 setInteractiveTTY ( false ) ;
31153163 const now = Date . now ( ) ;
0 commit comments