diff --git a/src/commands/auth.test.ts b/src/commands/auth.test.ts index 2e2da4d..109cf7a 100644 --- a/src/commands/auth.test.ts +++ b/src/commands/auth.test.ts @@ -1,13 +1,23 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('readline', () => ({ + createInterface: vi.fn(), +})); + +vi.mock('../lib/auth.js', () => ({ + auth: { setAccessToken: vi.fn(), deleteAccessToken: vi.fn() }, +})); vi.mock('../lib/api-client.js', () => ({ - client: { checkAuthentication: vi.fn() }, + client: { checkAuthentication: vi.fn(), clearApi: vi.fn(), getUser: vi.fn() }, })); vi.mock('../lib/output.js', () => ({ outputJson: vi.fn(), })); +import { createInterface } from 'readline'; +import { auth } from '../lib/auth.js'; import { client } from '../lib/api-client.js'; import { outputJson } from '../lib/output.js'; import { createAuthCommand } from './auth.js'; @@ -15,6 +25,47 @@ import { createAuthCommand } from './auth.js'; const mockCheckAuthentication = client.checkAuthentication as ReturnType; const mockOutputJson = outputJson as ReturnType; +describe('ynab auth login', () => { + const originalIsTTY = Object.getOwnPropertyDescriptor(process.stdin, 'isTTY'); + + beforeEach(() => { + vi.clearAllMocks(); + Object.defineProperty(process.stdin, 'isTTY', { configurable: true, value: true }); + vi.mocked(client.getUser).mockResolvedValue({ id: 'user-id' }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + if (originalIsTTY) { + Object.defineProperty(process.stdin, 'isTTY', originalIsTTY); + } else { + Reflect.deleteProperty(process.stdin, 'isTTY'); + } + }); + + it('passes the visible token prompt to readline and trims the answer', async () => { + const close = vi.fn(); + const question = vi.fn((_query: string, callback: (answer: string) => void) => { + callback(' test-token '); + }); + vi.mocked(createInterface).mockReturnValue({ question, close } as unknown as ReturnType); + + await createAuthCommand().parseAsync(['node', 'auth', 'login']); + + expect(createInterface).toHaveBeenCalledWith({ + input: process.stdin, + output: process.stderr, + }); + expect(question).toHaveBeenCalledWith('Enter YNAB Personal Access Token: ', expect.any(Function)); + expect(close).toHaveBeenCalledOnce(); + expect(auth.setAccessToken).toHaveBeenCalledWith('test-token'); + expect(mockOutputJson).toHaveBeenCalledWith({ + message: 'Successfully authenticated', + user: { id: 'user-id' }, + }); + }); +}); + describe('ynab auth status', () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/src/commands/auth.ts b/src/commands/auth.ts index de8a701..bf203ac 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -22,8 +22,7 @@ function promptForToken(): Promise { input: process.stdin, output: process.stderr, }); - process.stderr.write('Enter YNAB Personal Access Token: '); - rl.question('', (answer) => { + rl.question('Enter YNAB Personal Access Token: ', (answer) => { rl.close(); resolve(answer.trim()); });