@@ -4,6 +4,7 @@ import { ensureUserSetup } from '../../../src/api/user-setup.client.ts';
44import AuthLogin from '../../../src/commands/auth/login.ts' ;
55import { refreshIdentityFromStoredToken } from '../../../src/service/analytics.svc.ts' ;
66import { persistTokenResponse } from '../../../src/service/auth.svc.ts' ;
7+ import type { TokenResponse } from '../../../src/types/auth.ts' ;
78import { openInBrowser } from '../../../src/utils/open-in-browser.ts' ;
89
910type ServerRequest = { url ?: string } ;
@@ -167,22 +168,30 @@ describe('AuthLogin', () => {
167168 persistTokenResponseMock . mockClear ( ) ;
168169 } ) ;
169170
170- describe ( 'startServerAndAwaitCode ' , ( ) => {
171+ describe ( 'startServerAndAwaitToken ' , ( ) => {
171172 const authUrl = 'https://login.example/auth' ;
172173 const basePort = 4900 ;
173174
174- it ( 'resolves with the authorization code when the callback is valid' , async ( ) => {
175+ it ( 'resolves with the token response when the callback is valid' , async ( ) => {
175176 const command = createCommand ( basePort ) ;
176177 const state = 'expected-state' ;
177- const pendingCode = (
178- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
179- ) . startServerAndAwaitCode ( authUrl , state ) ;
178+ const codeVerifier = 'verifier-123' ;
179+
180+ const commandWithInternals = command as unknown as {
181+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < TokenResponse > ;
182+ exchangeCodeForToken : ( ...args : unknown [ ] ) => Promise < TokenResponse > ;
183+ } ;
184+ const tokenResponse = { access_token : 'access' , refresh_token : 'refresh' } ;
185+ vi . spyOn ( commandWithInternals , 'exchangeCodeForToken' ) . mockResolvedValue ( tokenResponse ) ;
186+
187+ const pendingCode = commandWithInternals . startServerAndAwaitToken ( authUrl , state , codeVerifier ) ;
188+
180189 const server = getLatestServer ( ) ;
181190
182191 await flushAsync ( ) ;
183192 sendCallbackThroughStub ( { code : 'test-code' , state } ) ;
184193
185- await expect ( pendingCode ) . resolves . toBe ( 'test-code' ) ;
194+ await expect ( pendingCode ) . resolves . toBe ( tokenResponse ) ;
186195 expect ( questionMock ) . toHaveBeenCalledWith ( expect . stringContaining ( authUrl ) , expect . any ( Function ) ) ;
187196 expect ( closeMock ) . toHaveBeenCalledTimes ( 1 ) ;
188197 expect ( openMock ) . toHaveBeenCalledWith ( authUrl ) ;
@@ -192,8 +201,10 @@ describe('AuthLogin', () => {
192201 it ( 'rejects when the callback is missing the state parameter' , async ( ) => {
193202 const command = createCommand ( basePort + 1 ) ;
194203 const pendingCode = (
195- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
196- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
204+ command as unknown as {
205+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
206+ }
207+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
197208 const server = getLatestServer ( ) ;
198209
199210 await flushAsync ( ) ;
@@ -206,8 +217,10 @@ describe('AuthLogin', () => {
206217 it ( 'rejects when the callback state does not match' , async ( ) => {
207218 const command = createCommand ( basePort + 2 ) ;
208219 const pendingCode = (
209- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
210- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
220+ command as unknown as {
221+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
222+ }
223+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
211224 const server = getLatestServer ( ) ;
212225
213226 await flushAsync ( ) ;
@@ -220,8 +233,10 @@ describe('AuthLogin', () => {
220233 it ( 'rejects with guidance when callback returns already_logged_in' , async ( ) => {
221234 const command = createCommand ( basePort + 3 ) ;
222235 const pendingCode = (
223- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
224- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
236+ command as unknown as {
237+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
238+ }
239+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
225240 const server = getLatestServer ( ) ;
226241
227242 await flushAsync ( ) ;
@@ -238,8 +253,10 @@ describe('AuthLogin', () => {
238253 it ( 'rejects when callback returns a generic OAuth error' , async ( ) => {
239254 const command = createCommand ( basePort + 4 ) ;
240255 const pendingCode = (
241- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
242- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
256+ command as unknown as {
257+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
258+ }
259+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
243260 const server = getLatestServer ( ) ;
244261
245262 await flushAsync ( ) ;
@@ -258,8 +275,10 @@ describe('AuthLogin', () => {
258275 it ( 'rejects with guidance when callback returns different_user_authenticated' , async ( ) => {
259276 const command = createCommand ( basePort + 5 ) ;
260277 const pendingCode = (
261- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
262- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
278+ command as unknown as {
279+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
280+ }
281+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
263282 const server = getLatestServer ( ) ;
264283
265284 await flushAsync ( ) ;
@@ -280,8 +299,10 @@ describe('AuthLogin', () => {
280299 it ( 'rejects when the callback omits the authorization code' , async ( ) => {
281300 const command = createCommand ( basePort + 6 ) ;
282301 const pendingCode = (
283- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
284- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
302+ command as unknown as {
303+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
304+ }
305+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
285306 const server = getLatestServer ( ) ;
286307
287308 await flushAsync ( ) ;
@@ -294,8 +315,10 @@ describe('AuthLogin', () => {
294315 it ( 'rejects when the callback URL is invalid' , async ( ) => {
295316 const command = createCommand ( basePort + 7 ) ;
296317 const pendingCode = (
297- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
298- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
318+ command as unknown as {
319+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
320+ }
321+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
299322 const server = getLatestServer ( ) ;
300323
301324 await flushAsync ( ) ;
@@ -310,8 +333,10 @@ describe('AuthLogin', () => {
310333 it ( 'returns a 400 response when the incoming request is missing a URL' , async ( ) => {
311334 const command = createCommand ( basePort + 8 ) ;
312335 const pendingCode = (
313- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
314- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
336+ command as unknown as {
337+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
338+ }
339+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
315340 const server = getLatestServer ( ) ;
316341
317342 await flushAsync ( ) ;
@@ -329,8 +354,10 @@ describe('AuthLogin', () => {
329354 it ( 'responds with not found for unrelated paths' , async ( ) => {
330355 const command = createCommand ( basePort + 9 ) ;
331356 const pendingCode = (
332- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
333- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
357+ command as unknown as {
358+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
359+ }
360+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
334361 const server = getLatestServer ( ) ;
335362
336363 await flushAsync ( ) ;
@@ -348,8 +375,10 @@ describe('AuthLogin', () => {
348375 it ( 'rejects when the local HTTP server emits an error' , async ( ) => {
349376 const command = createCommand ( basePort + 10 ) ;
350377 const pendingCode = (
351- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
352- ) . startServerAndAwaitCode ( authUrl , 'expected-state' ) ;
378+ command as unknown as {
379+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < string > ;
380+ }
381+ ) . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
353382 const server = getLatestServer ( ) ;
354383
355384 await flushAsync ( ) ;
@@ -369,17 +398,23 @@ describe('AuthLogin', () => {
369398 const state = 'expected-state' ;
370399
371400 try {
372- const pendingCode = (
373- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
374- ) . startServerAndAwaitCode ( authUrl , state ) ;
401+ const commandWithInternals = command as unknown as {
402+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < TokenResponse > ;
403+ exchangeCodeForToken : ( ...args : unknown [ ] ) => Promise < TokenResponse > ;
404+ } ;
405+ const tokenResponse = { access_token : 'access' , refresh_token : 'refresh' } ;
406+ vi . spyOn ( commandWithInternals , 'exchangeCodeForToken' ) . mockResolvedValue ( tokenResponse ) ;
407+
408+ const pendingCode = commandWithInternals . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
409+
375410 const server = getLatestServer ( ) ;
376411
377412 await flushAsync ( ) ;
378413
379414 expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( 'Failed to open browser automatically' ) ) ;
380415
381416 sendCallbackThroughStub ( { code : 'manual-code' , state } ) ;
382- await expect ( pendingCode ) . resolves . toBe ( 'manual-code' ) ;
417+ await expect ( pendingCode ) . resolves . toBe ( tokenResponse ) ;
383418 expect ( server . close ) . toHaveBeenCalledTimes ( 1 ) ;
384419 } finally {
385420 warnSpy . mockRestore ( ) ;
@@ -389,9 +424,12 @@ describe('AuthLogin', () => {
389424 it ( 'deduplicates shutdown when callback success and server error race' , async ( ) => {
390425 const command = createCommand ( basePort + 12 ) ;
391426 const state = 'expected-state' ;
392- const pendingCode = (
393- command as unknown as { startServerAndAwaitCode : ( url : string , state : string ) => Promise < string > }
394- ) . startServerAndAwaitCode ( authUrl , state ) ;
427+
428+ const commandWithInternals = command as unknown as {
429+ startServerAndAwaitToken : ( url : string , state : string , codeVerifier : string ) => Promise < TokenResponse > ;
430+ } ;
431+ const pendingCode = commandWithInternals . startServerAndAwaitToken ( authUrl , 'expected-state' , 'code-verifier' ) ;
432+
395433 const server = getLatestServer ( ) ;
396434 const warnSpy = vi
397435 . spyOn ( command as unknown as { warn : ( ...args : unknown [ ] ) => unknown } , 'warn' )
@@ -402,7 +440,7 @@ describe('AuthLogin', () => {
402440 sendCallbackThroughStub ( { code : 'race-code' , state } ) ;
403441 server . emitError ( new Error ( 'late listener error' ) ) ;
404442
405- await expect ( pendingCode ) . resolves . toBe ( 'race-code ') ;
443+ await expect ( pendingCode ) . rejects . toThrow ( 'late listener error ') ;
406444 expect ( server . close ) . toHaveBeenCalledTimes ( 1 ) ;
407445 expect ( warnSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( 'Failed to stop local OAuth callback server' ) ) ;
408446 } finally {
@@ -470,11 +508,9 @@ describe('AuthLogin', () => {
470508 const command = createCommand ( 6000 ) ;
471509 const tokenResponse = { access_token : 'access' , refresh_token : 'refresh' } ;
472510 const commandWithInternals = command as unknown as {
473- startServerAndAwaitCode : ( ...args : unknown [ ] ) => Promise < string > ;
474- exchangeCodeForToken : ( ...args : unknown [ ] ) => Promise < unknown > ;
511+ startServerAndAwaitToken : ( ...args : unknown [ ] ) => Promise < unknown > ;
475512 } ;
476- vi . spyOn ( commandWithInternals , 'startServerAndAwaitCode' ) . mockResolvedValue ( 'code-123' ) ;
477- vi . spyOn ( commandWithInternals , 'exchangeCodeForToken' ) . mockResolvedValue ( tokenResponse ) ;
513+ vi . spyOn ( commandWithInternals , 'startServerAndAwaitToken' ) . mockResolvedValue ( tokenResponse ) ;
478514
479515 await command . run ( ) ;
480516
@@ -488,11 +524,9 @@ describe('AuthLogin', () => {
488524 const command = createCommand ( 6001 ) ;
489525 const tokenResponse = { access_token : 'access' , refresh_token : 'refresh' } ;
490526 const commandWithInternals = command as unknown as {
491- startServerAndAwaitCode : ( ...args : unknown [ ] ) => Promise < string > ;
492- exchangeCodeForToken : ( ...args : unknown [ ] ) => Promise < unknown > ;
527+ startServerAndAwaitToken : ( ...args : unknown [ ] ) => Promise < unknown > ;
493528 } ;
494- vi . spyOn ( commandWithInternals , 'startServerAndAwaitCode' ) . mockResolvedValue ( 'code-123' ) ;
495- vi . spyOn ( commandWithInternals , 'exchangeCodeForToken' ) . mockResolvedValue ( tokenResponse ) ;
529+ vi . spyOn ( commandWithInternals , 'startServerAndAwaitToken' ) . mockResolvedValue ( tokenResponse ) ;
496530
497531 await command . run ( ) ;
498532
@@ -506,11 +540,9 @@ describe('AuthLogin', () => {
506540 const command = createCommand ( 6002 ) ;
507541 const tokenResponse = { access_token : 'access' , refresh_token : 'refresh' } ;
508542 const commandWithInternals = command as unknown as {
509- startServerAndAwaitCode : ( ...args : unknown [ ] ) => Promise < string > ;
510- exchangeCodeForToken : ( ...args : unknown [ ] ) => Promise < unknown > ;
543+ startServerAndAwaitToken : ( ...args : unknown [ ] ) => Promise < unknown > ;
511544 } ;
512- vi . spyOn ( commandWithInternals , 'startServerAndAwaitCode' ) . mockResolvedValue ( 'code-123' ) ;
513- vi . spyOn ( commandWithInternals , 'exchangeCodeForToken' ) . mockResolvedValue ( tokenResponse ) ;
545+ vi . spyOn ( commandWithInternals , 'startServerAndAwaitToken' ) . mockResolvedValue ( tokenResponse ) ;
514546
515547 await expect ( command . run ( ) ) . rejects . toThrow ( 'User setup failed' ) ;
516548 expect ( refreshIdentityFromStoredTokenMock ) . not . toHaveBeenCalled ( ) ;
0 commit comments