@@ -88,7 +88,7 @@ import { attachLoginCommand } from '@doist/cli-core/auth'
8888import { CommsRequestError, type User } from '@doist/comms-sdk'
8989import { createWrappedCommsClient } from '../../lib/api.js'
9090import { type CommsAccount , type CommsTokenStore } from '../../lib/auth-provider.js'
91- import { getApiTokenSnapshot , TOKEN_ENV_VAR } from '../../lib/auth.js'
91+ import { getApiTokenSnapshot , NoTokenError , TOKEN_ENV_VAR } from '../../lib/auth.js'
9292import { getConfig , updateConfig } from '../../lib/config.js'
9393import { resetGlobalArgs } from '../../lib/global-args.js'
9494import { registerAuthCommand } from './index.js'
@@ -284,30 +284,81 @@ describe('auth command', () => {
284284 vi . unstubAllEnvs ( )
285285 } )
286286
287- it ( 'prints exactly the stored token to stdout with no envelope (pipe-safe)' , async ( ) => {
287+ it ( 'prints exactly the current token snapshot to stdout with no envelope (pipe-safe)' , async ( ) => {
288288 vi . stubEnv ( TOKEN_ENV_VAR , '' )
289- storeMocks . active . mockResolvedValue ( STORED_SNAPSHOT )
289+ mockGetApiTokenSnapshot . mockResolvedValue ( STORED_SNAPSHOT )
290290
291291 await createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] )
292292
293+ expect ( mockGetApiTokenSnapshot ) . toHaveBeenCalledWith ( undefined )
293294 expect ( stdoutPayload ( ) ) . toBe ( 'tk_stored_1234567890' )
294295 expect ( consoleSpy ) . not . toHaveBeenCalled ( )
295296 } )
296297
298+ it ( 'adds a trailing newline only when stdout is a TTY' , async ( ) => {
299+ vi . stubEnv ( TOKEN_ENV_VAR , '' )
300+ mockGetApiTokenSnapshot . mockResolvedValue ( STORED_SNAPSHOT )
301+ const originalIsTTY = process . stdout . isTTY
302+ Object . defineProperty ( process . stdout , 'isTTY' , { value : true , configurable : true } )
303+
304+ try {
305+ await createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] )
306+ } finally {
307+ Object . defineProperty ( process . stdout , 'isTTY' , {
308+ value : originalIsTTY ,
309+ configurable : true ,
310+ } )
311+ }
312+
313+ expect ( stdoutPayload ( ) ) . toBe ( 'tk_stored_1234567890\n' )
314+ } )
315+
316+ it ( 'refreshes an expired OAuth token before printing' , async ( ) => {
317+ vi . stubEnv ( TOKEN_ENV_VAR , '' )
318+ const oauthAccount : CommsAccount = {
319+ ...STORED_ACCOUNT ,
320+ oauthClientId : 'tdd_123' ,
321+ authBaseUrl : 'https://todoist.com' ,
322+ authResource : 'https://comms.todoist.com' ,
323+ }
324+ mockGetApiTokenSnapshot . mockResolvedValue ( {
325+ token : 'tk_refreshed_1234567890' ,
326+ account : oauthAccount ,
327+ } )
328+
329+ await createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] )
330+
331+ expect ( mockGetApiTokenSnapshot ) . toHaveBeenCalledWith ( undefined )
332+ expect ( stdoutPayload ( ) ) . toBe ( 'tk_refreshed_1234567890' )
333+ } )
334+
335+ it ( 'prints manual tokens returned by the token snapshot path' , async ( ) => {
336+ vi . stubEnv ( TOKEN_ENV_VAR , '' )
337+ mockGetApiTokenSnapshot . mockResolvedValue ( {
338+ token : 'manual_token_1234567890' ,
339+ account : { id : '' , label : '' , authMode : 'unknown' , authScope : '' } ,
340+ } )
341+
342+ await createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] )
343+
344+ expect ( mockGetApiTokenSnapshot ) . toHaveBeenCalledWith ( undefined )
345+ expect ( stdoutPayload ( ) ) . toBe ( 'manual_token_1234567890' )
346+ } )
347+
297348 it ( 'refuses to print when the env var is set so the CLI does not disclose an unmanaged token' , async ( ) => {
298349 vi . stubEnv ( TOKEN_ENV_VAR , 'env_token_supplied_externally' )
299350
300351 await expect (
301352 createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] ) ,
302353 ) . rejects . toHaveProperty ( 'code' , 'TOKEN_FROM_ENV' )
303354
304- expect ( storeMocks . active ) . not . toHaveBeenCalled ( )
355+ expect ( mockGetApiTokenSnapshot ) . not . toHaveBeenCalled ( )
305356 expect ( stdoutPayload ( ) ) . toBe ( '' )
306357 } )
307358
308359 it ( 'throws NOT_AUTHENTICATED when no token is stored' , async ( ) => {
309360 vi . stubEnv ( TOKEN_ENV_VAR , '' )
310- storeMocks . active . mockResolvedValue ( null )
361+ mockGetApiTokenSnapshot . mockRejectedValue ( new NoTokenError ( ) )
311362
312363 await expect (
313364 createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] ) ,
@@ -316,9 +367,9 @@ describe('auth command', () => {
316367 expect ( stdoutPayload ( ) ) . toBe ( '' )
317368 } )
318369
319- it ( 'matches per-command --user against the stored account by id ' , async ( ) => {
370+ it ( 'passes per-command --user through to the token snapshot path ' , async ( ) => {
320371 vi . stubEnv ( TOKEN_ENV_VAR , '' )
321- storeMocks . active . mockResolvedValue ( STORED_SNAPSHOT )
372+ mockGetApiTokenSnapshot . mockResolvedValue ( STORED_SNAPSHOT )
322373
323374 await createProgram ( ) . parseAsync ( [
324375 'node' ,
@@ -330,13 +381,13 @@ describe('auth command', () => {
330381 '1' ,
331382 ] )
332383
333- expect ( storeMocks . active ) . toHaveBeenCalledWith ( '1' )
384+ expect ( mockGetApiTokenSnapshot ) . toHaveBeenCalledWith ( '1' )
334385 expect ( stdoutPayload ( ) ) . toBe ( 'tk_stored_1234567890' )
335386 } )
336387
337388 it ( 'rejects per-command --user with ACCOUNT_NOT_FOUND when the ref does not match' , async ( ) => {
338389 vi . stubEnv ( TOKEN_ENV_VAR , '' )
339- storeMocks . active . mockResolvedValue ( null )
390+ mockGetApiTokenSnapshot . mockRejectedValue ( new NoTokenError ( ) )
340391
341392 await expect (
342393 createProgram ( ) . parseAsync ( [
@@ -350,6 +401,7 @@ describe('auth command', () => {
350401 ] ) ,
351402 ) . rejects . toHaveProperty ( 'code' , 'ACCOUNT_NOT_FOUND' )
352403
404+ expect ( mockGetApiTokenSnapshot ) . toHaveBeenCalledWith ( '999' )
353405 expect ( stdoutPayload ( ) ) . toBe ( '' )
354406 } )
355407 } )
@@ -373,16 +425,15 @@ describe('auth command', () => {
373425 vi . unstubAllEnvs ( )
374426 } )
375427
376- it ( 'threads `tdc --user <ref> auth token view` into store.active ' , async ( ) => {
428+ it ( 'threads `tdc --user <ref> auth token view` into token refresh ' , async ( ) => {
377429 vi . stubEnv ( TOKEN_ENV_VAR , '' )
378- storeMocks . list . mockResolvedValue ( STORED_RECORDS )
379- storeMocks . active . mockResolvedValue ( STORED_SNAPSHOT )
430+ mockGetApiTokenSnapshot . mockResolvedValue ( STORED_SNAPSHOT )
380431 process . argv = [ 'node' , 'tdc' , '--user' , '1' , 'auth' , 'token' , 'view' ]
381432 resetGlobalArgs ( )
382433
383434 await createProgram ( ) . parseAsync ( [ 'node' , 'tdc' , 'auth' , 'token' , 'view' ] )
384435
385- expect ( storeMocks . active ) . toHaveBeenCalledWith ( '1' )
436+ expect ( mockGetApiTokenSnapshot ) . toHaveBeenCalledWith ( '1' )
386437 expect ( writeSpy . mock . calls . map ( ( c : unknown [ ] ) => String ( c [ 0 ] ) ) . join ( '' ) ) . toBe (
387438 'tk_stored_1234567890' ,
388439 )
0 commit comments