@@ -20,8 +20,18 @@ interface ServerStub {
2020
2121const serverInstances : ServerStub [ ] = [ ] ;
2222
23+ class ServerNotRunningError extends Error implements NodeJS . ErrnoException {
24+ code = 'ERR_SERVER_NOT_RUNNING' ;
25+
26+ constructor ( ) {
27+ super ( 'Server is not running.' ) ;
28+ this . name = 'ServerNotRunningError' ;
29+ }
30+ }
31+
2332const createServerStub = ( handler : ServerHandler ) : ServerStub => {
2433 let errorListener : ( ( error : Error ) => void ) | undefined ;
34+ let closed = false ;
2535 const stub : ServerStub = {
2636 handler,
2737 listen : vi . fn ( ( _port : number , cb ?: ( ) => void ) => {
@@ -32,7 +42,9 @@ const createServerStub = (handler: ServerHandler): ServerStub => {
3242 return stub ;
3343 } ) ,
3444 close : vi . fn ( ( cb ?: ( err ?: Error ) => void ) => {
35- cb ?.( ) ;
45+ const closeError = closed ? new ServerNotRunningError ( ) : undefined ;
46+ closed = true ;
47+ setImmediate ( ( ) => cb ?.( closeError ) ) ;
3648 return stub ;
3749 } ) ,
3850 on : vi . fn ( ( event : string , cb : ( err : Error ) => void ) => {
@@ -80,8 +92,8 @@ vi.mock('../../../src/utils/open-in-browser.ts', () => ({
8092 openInBrowser : vi . fn ( ) ,
8193} ) ) ;
8294
83- const questionMock = vi . fn < [ string , ( answer : string ) => void ] , void > ( ) ;
84- const closeMock = vi . fn < [ ] , void > ( ) ;
95+ const questionMock = vi . fn < ( question : string , callback : ( answer : string ) => void ) => void > ( ) ;
96+ const closeMock = vi . fn < ( ) => void > ( ) ;
8597
8698vi . mock ( 'node:readline' , ( ) => ( {
8799 __esModule : true ,
@@ -303,6 +315,30 @@ describe('AuthLogin', () => {
303315 warnSpy . mockRestore ( ) ;
304316 }
305317 } ) ;
318+
319+ it ( 'deduplicates shutdown when callback success and server error race' , async ( ) => {
320+ const command = createCommand ( basePort + 8 ) ;
321+ const state = 'expected-state' ;
322+ const pendingCode = (
323+ command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
324+ ) . startServerAndAwaitCode ( authUrl , state ) ;
325+ const server = getLatestServer ( ) ;
326+ const warnSpy = vi
327+ . spyOn ( command as unknown as { warn : ( ...args : unknown [ ] ) => unknown } , 'warn' )
328+ . mockImplementation ( ( ) => { } ) ;
329+
330+ try {
331+ await flushAsync ( ) ;
332+ sendCallbackThroughStub ( { code : 'race-code' , state } ) ;
333+ server . emitError ( new Error ( 'late listener error' ) ) ;
334+
335+ await expect ( pendingCode ) . resolves . toBe ( 'race-code' ) ;
336+ expect ( server . close ) . toHaveBeenCalledTimes ( 1 ) ;
337+ expect ( warnSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( 'Failed to stop local OAuth callback server' ) ) ;
338+ } finally {
339+ warnSpy . mockRestore ( ) ;
340+ }
341+ } ) ;
306342 } ) ;
307343
308344 describe ( 'exchangeCodeForToken' , ( ) => {
0 commit comments