import { Command } from 'commander'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const { mockLoadStoredAnthropicAuth, mockLoadStoredAnthropicAuthToken, mockStoreAnthropicAuth, mockStoreAnthropicAuthToken, } = vi.hoisted(() => ({ mockLoadStoredAnthropicAuth: vi.fn(), mockLoadStoredAnthropicAuthToken: vi.fn(), mockStoreAnthropicAuth: vi.fn(), mockStoreAnthropicAuthToken: vi.fn(), })); const { mockCreateInterface } = vi.hoisted(() => ({ mockCreateInterface: vi.fn(), })); vi.mock('../auth/index.js', () => ({ loadStoredAnthropicAuth: mockLoadStoredAnthropicAuth, loadStoredAnthropicAuthToken: mockLoadStoredAnthropicAuthToken, storeAnthropicAuth: mockStoreAnthropicAuth, storeAnthropicAuthToken: mockStoreAnthropicAuthToken, })); vi.mock('readline', () => ({ default: { createInterface: mockCreateInterface, }, })); function mockReadlineAnswers(answers: string[]): void { const queue = [...answers]; mockCreateInterface.mockImplementation(() => ({ question: (_prompt: string, cb: (answer: string) => void) => cb(queue.shift() ?? ''), close: () => undefined, })); } describe('anthropic-auth command', () => { beforeEach(() => { vi.clearAllMocks(); mockLoadStoredAnthropicAuth.mockReset(); mockLoadStoredAnthropicAuthToken.mockReset(); mockStoreAnthropicAuth.mockReset(); mockStoreAnthropicAuthToken.mockReset(); mockCreateInterface.mockReset(); }); it('cancels key re-auth when key exists and user answers no', async () => { mockLoadStoredAnthropicAuth.mockReturnValue({ api_key: 'sk-ant-existing', created_at: '2026-02-16T00:00:00.000Z' }); mockReadlineAnswers(['n']); const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code?: number) => { throw new Error(`EXIT:${code ?? 0}`); }) as never); await expect(program.parseAsync(['node', 'test', 'anthropic-auth'])).rejects.toThrow('EXIT:0'); expect(mockStoreAnthropicAuth).not.toHaveBeenCalled(); expect(consoleLog).toHaveBeenCalledWith('Cancelled.'); exitSpy.mockRestore(); consoleLog.mockRestore(); }); it('stores a new key when key exists and user answers yes', async () => { mockLoadStoredAnthropicAuth.mockReturnValue({ api_key: 'sk-ant-existing', created_at: '2026-02-16T00:00:00.000Z' }); mockReadlineAnswers(['y', 'sk-ant-new']); const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined); await program.parseAsync(['node', 'test', 'anthropic-auth']); expect(mockStoreAnthropicAuth).toHaveBeenCalledWith('sk-ant-new'); expect(consoleError).not.toHaveBeenCalled(); consoleLog.mockRestore(); consoleError.mockRestore(); }); it('cancels token re-auth when token exists and user answers no', async () => { mockLoadStoredAnthropicAuthToken.mockReturnValue('tok-existing'); mockReadlineAnswers(['n']); const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code?: number) => { throw new Error(`EXIT:${code ?? 0}`); }) as never); await expect(program.parseAsync(['node', 'test', 'anthropic-auth', '--token'])).rejects.toThrow('EXIT:0'); expect(mockStoreAnthropicAuthToken).not.toHaveBeenCalled(); expect(consoleLog).toHaveBeenCalledWith('Cancelled.'); exitSpy.mockRestore(); consoleLog.mockRestore(); }); it('stores a new token when token exists and user answers yes', async () => { mockLoadStoredAnthropicAuthToken.mockReturnValue('tok-existing'); mockReadlineAnswers(['y', 'tok-new']); const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined); await program.parseAsync(['node', 'test', 'anthropic-auth', '--token']); expect(mockStoreAnthropicAuthToken).toHaveBeenCalledWith('tok-new'); expect(consoleError).not.toHaveBeenCalled(); consoleLog.mockRestore(); consoleError.mockRestore(); }); it('accepts --mode api and stores API key', async () => { mockLoadStoredAnthropicAuth.mockReturnValue(null); mockReadlineAnswers(['sk-ant-from-mode']); const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined); await program.parseAsync(['node', 'test', 'anthropic-auth', '--mode', 'api']); expect(mockStoreAnthropicAuth).toHaveBeenCalledWith('sk-ant-from-mode'); expect(mockStoreAnthropicAuthToken).not.toHaveBeenCalled(); expect(consoleError).not.toHaveBeenCalled(); consoleLog.mockRestore(); consoleError.mockRestore(); }); it('accepts --mode token and stores auth token', async () => { mockLoadStoredAnthropicAuthToken.mockReturnValue(null); mockReadlineAnswers(['tok-from-mode']); const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined); await program.parseAsync(['node', 'test', 'anthropic-auth', '--mode', 'token']); expect(mockStoreAnthropicAuthToken).toHaveBeenCalledWith('tok-from-mode'); expect(mockStoreAnthropicAuth).not.toHaveBeenCalled(); expect(consoleError).not.toHaveBeenCalled(); consoleLog.mockRestore(); consoleError.mockRestore(); }); it('fails on conflicting --token and --mode api options', async () => { const program = new Command(); const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js'); registerAnthropicAuthCommand(program); await expect( program.parseAsync(['node', 'test', 'anthropic-auth', '--token', '--mode', 'api']), ).rejects.toThrow(/Conflicting options/); }); });