WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
at main 166 lines 6.9 kB view raw
1import { beforeEach, describe, expect, it, vi } from 'vitest'; 2import { createAuthCommand } from '../../src/commands/auth.js'; 3import * as apiClientModule from '../../src/lib/api-client.js'; 4import * as sessionModule from '../../src/lib/session.js'; 5import * as promptsModule from '../../src/utils/prompts.js'; 6import { mockSessionData, mockSessionMetadata } from '../helpers/mock-data.js'; 7 8// Mock modules 9vi.mock('../../src/lib/api-client.js'); 10vi.mock('../../src/lib/session.js'); 11vi.mock('../../src/utils/prompts.js'); 12 13describe('Auth Commands', () => { 14 let mockClient: { 15 login: ReturnType<typeof vi.fn>; 16 logout: ReturnType<typeof vi.fn>; 17 }; 18 let consoleLogSpy: ReturnType<typeof vi.fn>; 19 let consoleErrorSpy: ReturnType<typeof vi.fn>; 20 let processExitSpy: ReturnType<typeof vi.fn>; 21 22 beforeEach(() => { 23 vi.clearAllMocks(); 24 25 // Mock API client 26 mockClient = { 27 login: vi.fn(), 28 logout: vi.fn(), 29 }; 30 vi.mocked(apiClientModule.createApiClient).mockReturnValue(mockClient as never); 31 32 // Mock console methods 33 consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) as never; 34 consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) as never; 35 36 // Mock process.exit to throw to stop execution (mimicking real behavior) 37 processExitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => { 38 throw new Error(`process.exit(${code})`); 39 }) as never; 40 }); 41 42 describe('login command', () => { 43 it('should login successfully with valid credentials', async () => { 44 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(null); 45 vi.mocked(promptsModule.promptForLogin).mockResolvedValue({ 46 identifier: 'user.bsky.social', 47 password: 'test-password', 48 }); 49 mockClient.login.mockResolvedValue(mockSessionData); 50 51 const auth = createAuthCommand(); 52 await auth.parseAsync(['node', 'test', 'login']); 53 54 expect(promptsModule.promptForLogin).toHaveBeenCalled(); 55 expect(mockClient.login).toHaveBeenCalledWith('user.bsky.social', 'test-password'); 56 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Successfully logged in')); 57 expect(consoleLogSpy).toHaveBeenCalledWith( 58 expect.stringContaining(`@${mockSessionData.handle}`) 59 ); 60 }); 61 62 it('should prevent login when already authenticated', async () => { 63 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(mockSessionMetadata); 64 65 const auth = createAuthCommand(); 66 await expect(auth.parseAsync(['node', 'test', 'login'])).rejects.toThrow('process.exit'); 67 68 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Already logged in')); 69 expect(promptsModule.promptForLogin).not.toHaveBeenCalled(); 70 expect(processExitSpy).toHaveBeenCalled(); 71 }); 72 73 it('should handle login errors gracefully', async () => { 74 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(null); 75 vi.mocked(promptsModule.promptForLogin).mockResolvedValue({ 76 identifier: 'user.bsky.social', 77 password: 'wrong-password', 78 }); 79 mockClient.login.mockRejectedValue(new Error('Invalid credentials')); 80 81 const auth = createAuthCommand(); 82 await expect(auth.parseAsync(['node', 'test', 'login'])).rejects.toThrow('process.exit(1)'); 83 84 expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Login failed')); 85 expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid credentials')); 86 expect(processExitSpy).toHaveBeenCalledWith(1); 87 }); 88 }); 89 90 describe('logout command', () => { 91 it('should logout successfully when authenticated', async () => { 92 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(mockSessionMetadata); 93 mockClient.logout.mockResolvedValue(undefined); 94 95 const auth = createAuthCommand(); 96 await auth.parseAsync(['node', 'test', 'logout']); 97 98 expect(mockClient.logout).toHaveBeenCalled(); 99 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Logged out')); 100 expect(consoleLogSpy).toHaveBeenCalledWith( 101 expect.stringContaining(`@${mockSessionMetadata.handle}`) 102 ); 103 }); 104 105 it('should handle logout when not authenticated', async () => { 106 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(null); 107 108 const auth = createAuthCommand(); 109 await expect(auth.parseAsync(['node', 'test', 'logout'])).rejects.toThrow('process.exit'); 110 111 expect(mockClient.logout).not.toHaveBeenCalled(); 112 expect(consoleLogSpy).toHaveBeenCalledWith('Not currently logged in'); 113 expect(processExitSpy).toHaveBeenCalled(); 114 }); 115 116 it('should handle logout errors gracefully', async () => { 117 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(mockSessionMetadata); 118 mockClient.logout.mockRejectedValue(new Error('Logout failed')); 119 120 const auth = createAuthCommand(); 121 await expect(auth.parseAsync(['node', 'test', 'logout'])).rejects.toThrow('process.exit(1)'); 122 123 expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Logout failed')); 124 expect(processExitSpy).toHaveBeenCalledWith(1); 125 }); 126 }); 127 128 describe('status command', () => { 129 it('should show authenticated status with session details', async () => { 130 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(mockSessionMetadata); 131 132 const auth = createAuthCommand(); 133 await auth.parseAsync(['node', 'test', 'status']); 134 135 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Authenticated')); 136 expect(consoleLogSpy).toHaveBeenCalledWith( 137 expect.stringContaining(`@${mockSessionMetadata.handle}`) 138 ); 139 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(mockSessionMetadata.did)); 140 }); 141 142 it('should show not authenticated status', async () => { 143 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(null); 144 145 const auth = createAuthCommand(); 146 await auth.parseAsync(['node', 'test', 'status']); 147 148 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Not authenticated')); 149 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('tangled auth login')); 150 }); 151 152 it('should handle status check errors gracefully', async () => { 153 vi.mocked(sessionModule.getCurrentSessionMetadata).mockRejectedValue( 154 new Error('Failed to read session') 155 ); 156 157 const auth = createAuthCommand(); 158 await expect(auth.parseAsync(['node', 'test', 'status'])).rejects.toThrow('process.exit(1)'); 159 160 expect(consoleErrorSpy).toHaveBeenCalledWith( 161 expect.stringContaining('Failed to check status') 162 ); 163 expect(processExitSpy).toHaveBeenCalledWith(1); 164 }); 165 }); 166});