Skip to content

Commit 7de6e08

Browse files
fix: address PR #121 review feedback for auth status --json (#123)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent eb872ba commit 7de6e08

File tree

4 files changed

+60
-17
lines changed

4 files changed

+60
-17
lines changed

src/__tests__/auth.test.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import { Command } from 'commander'
22
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
33

44
// Mock the auth module
5-
vi.mock('../lib/auth.js', () => ({
6-
saveApiToken: vi.fn(),
7-
clearApiToken: vi.fn(),
8-
}))
5+
vi.mock('../lib/auth.js', async (importOriginal) => {
6+
const actual = await importOriginal<typeof import('../lib/auth.js')>()
7+
return {
8+
...actual,
9+
saveApiToken: vi.fn(),
10+
clearApiToken: vi.fn(),
11+
}
12+
})
913

1014
// Mock the api module
1115
vi.mock('../lib/api/core.js', () => ({
@@ -54,7 +58,7 @@ import { createInterface, type Interface } from 'node:readline'
5458
import open from 'open'
5559
import { registerAuthCommand } from '../commands/auth.js'
5660
import { getApi } from '../lib/api/core.js'
57-
import { clearApiToken, saveApiToken } from '../lib/auth.js'
61+
import { NoTokenError, clearApiToken, saveApiToken } from '../lib/auth.js'
5862
import { startCallbackServer } from '../lib/oauth-server.js'
5963
import { exchangeCodeForToken } from '../lib/oauth.js'
6064
import { createMockApi } from './helpers/mock-api.js'
@@ -88,6 +92,7 @@ describe('auth command', () => {
8892
afterEach(() => {
8993
consoleSpy.mockRestore()
9094
errorSpy.mockRestore()
95+
process.exitCode = undefined
9196
})
9297

9398
describe('token subcommand', () => {
@@ -328,18 +333,37 @@ describe('auth command', () => {
328333

329334
it('outputs JSON error when --json flag is used and not authenticated', async () => {
330335
const program = createProgram()
331-
mockGetApi.mockRejectedValue(new Error('No API token found'))
336+
mockGetApi.mockRejectedValue(new NoTokenError())
332337

333338
await program.parseAsync(['node', 'td', 'auth', 'status', '--json'])
334339

335340
expect(consoleSpy).toHaveBeenCalledWith(
336341
JSON.stringify({ error: 'Not authenticated' }, null, 2),
337342
)
343+
expect(process.exitCode).toBe(1)
344+
})
345+
346+
it('rethrows non-auth errors in JSON mode', async () => {
347+
const program = createProgram()
348+
mockGetApi.mockRejectedValue(new Error('Network timeout'))
349+
350+
await expect(
351+
program.parseAsync(['node', 'td', 'auth', 'status', '--json']),
352+
).rejects.toThrow('Network timeout')
353+
})
354+
355+
it('rethrows non-auth errors in human-readable mode', async () => {
356+
const program = createProgram()
357+
mockGetApi.mockRejectedValue(new Error('Network timeout'))
358+
359+
await expect(program.parseAsync(['node', 'td', 'auth', 'status'])).rejects.toThrow(
360+
'Network timeout',
361+
)
338362
})
339363

340364
it('shows not authenticated when no token', async () => {
341365
const program = createProgram()
342-
mockGetApi.mockRejectedValue(new Error('No API token found'))
366+
mockGetApi.mockRejectedValue(new NoTokenError())
343367

344368
await program.parseAsync(['node', 'td', 'auth', 'status'])
345369

src/__tests__/lib-auth.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ describe('lib/auth', () => {
312312

313313
const { getApiToken } = await import('../lib/auth.js')
314314

315-
await expect(getApiToken()).rejects.toThrow('No API token found')
315+
const { NoTokenError } = await import('../lib/auth.js')
316+
await expect(getApiToken()).rejects.toBeInstanceOf(NoTokenError)
316317
expect(keyringState.deleteCalls).toBe(1)
317318
expect(keyringState.getCalls).toBe(0)
318319
expect(keyringState.token).toBeNull()

src/commands/auth.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import chalk from 'chalk'
33
import { Command } from 'commander'
44
import open from 'open'
55
import { getApi } from '../lib/api/core.js'
6-
import { clearApiToken, saveApiToken, type TokenStorageResult } from '../lib/auth.js'
6+
import { NoTokenError, clearApiToken, saveApiToken, type TokenStorageResult } from '../lib/auth.js'
77
import { startCallbackServer } from '../lib/oauth-server.js'
88
import { buildAuthorizationUrl, exchangeCodeForToken } from '../lib/oauth.js'
99
import { generateCodeChallenge, generateCodeVerifier, generateState } from '../lib/pkce.js'
@@ -89,13 +89,24 @@ async function showStatus(options: { json?: boolean }): Promise<void> {
8989
console.log(` Email: ${user.email}`)
9090
console.log(` Name: ${user.fullName}`)
9191
}
92-
} catch {
92+
} catch (error) {
93+
const isAuthError = error instanceof NoTokenError
9394
if (options.json) {
94-
console.log(JSON.stringify({ error: 'Not authenticated' }, null, 2))
95-
process.exitCode = 1
95+
if (isAuthError) {
96+
console.log(JSON.stringify({ error: 'Not authenticated' }, null, 2))
97+
process.exitCode = 1
98+
} else {
99+
throw error
100+
}
96101
} else {
97-
console.log(chalk.yellow('Not authenticated'))
98-
console.log(chalk.dim('Run `td auth login` or `td auth token <token>` to authenticate'))
102+
if (isAuthError) {
103+
console.log(chalk.yellow('Not authenticated'))
104+
console.log(
105+
chalk.dim('Run `td auth login` or `td auth token <token>` to authenticate'),
106+
)
107+
} else {
108+
throw error
109+
}
99110
}
100111
}
101112
}

src/lib/auth.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import {
1010
export const CONFIG_PATH = join(homedir(), '.config', 'todoist-cli', 'config.json')
1111
export const TOKEN_ENV_VAR = 'TODOIST_API_TOKEN'
1212

13+
export class NoTokenError extends Error {
14+
constructor() {
15+
super(
16+
`No API token found. Set ${TOKEN_ENV_VAR} or run \`td auth login\` or \`td auth token <token>\`.`,
17+
)
18+
this.name = 'NoTokenError'
19+
}
20+
}
21+
1322
export type TokenStorageLocation = 'secure-store' | 'config-file'
1423

1524
export interface TokenStorageResult {
@@ -70,9 +79,7 @@ export async function getApiToken(): Promise<string> {
7079
}
7180
}
7281

73-
throw new Error(
74-
`No API token found. Set ${TOKEN_ENV_VAR} or run \`td auth login\` or \`td auth token <token>\`.`,
75-
)
82+
throw new NoTokenError()
7683
}
7784

7885
try {

0 commit comments

Comments
 (0)