import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { api, tokenManager } from '../src/lib/api'; // Mock localStorage for Node.js test environment const localStorageMock = (() => { let store: Record = {}; return { getItem: (key: string) => store[key] || null, setItem: (key: string, value: string) => { store[key] = value; }, removeItem: (key: string) => { delete store[key]; }, clear: () => { store = {}; }, }; })(); Object.defineProperty(global, 'localStorage', { value: localStorageMock, }); describe('Session API', () => { let fetchMock: ReturnType; beforeEach(() => { fetchMock = vi.fn(); global.fetch = fetchMock; localStorage.clear(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('refreshSession', () => { it('calls the correct endpoint and updates token expiry', async () => { const mockResponse = { success: true, code: 200, message: 'Session refreshed', data: { expires_at: '2026-04-26T18:30:00Z' }, }; fetchMock.mockResolvedValueOnce({ json: () => Promise.resolve(mockResponse), }); // Set a token first tokenManager.setToken('test-token', '2026-04-26T17:00:00Z'); const result = await api.auth.refreshSession(); expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining('/auth/sessions/refresh'), expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ Authorization: 'Bearer test-token', }), }) ); expect(result.expires_at).toBe('2026-04-26T18:30:00Z'); expect(tokenManager.getExpiry()).toBe('2026-04-26T18:30:00Z'); }); }); describe('listSessions', () => { it('calls the correct endpoint', async () => { const mockResponse = { success: true, code: 200, message: 'Sessions listed', data: { sessions: [ { id: 'sess-1', created_at: '2026-04-26T10:00:00Z', expires_at: '2026-04-26T18:00:00Z', last_used_at: '2026-04-26T17:00:00Z', ip_address: '127.0.0.1', user_agent: 'Mozilla/5.0', is_current: true, }, ], }, }; fetchMock.mockResolvedValueOnce({ json: () => Promise.resolve(mockResponse), }); tokenManager.setToken('test-token'); const result = await api.auth.listSessions(); expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining('/auth/sessions'), expect.objectContaining({ credentials: 'include', }) ); expect(result.sessions).toHaveLength(1); expect(result.sessions[0].id).toBe('sess-1'); }); }); describe('revokeSession', () => { it('encodes session ID and calls the correct endpoint', async () => { const mockResponse = { success: true, code: 200, message: 'Session revoked', data: {}, }; fetchMock.mockResolvedValueOnce({ json: () => Promise.resolve(mockResponse), }); tokenManager.setToken('test-token'); await api.auth.revokeSession('sess-abc/123'); expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining('/auth/sessions/sess-abc%2F123'), expect.objectContaining({ method: 'DELETE', }) ); }); }); describe('tokenManager', () => { it('updateExpiry sets the expiry in localStorage', () => { tokenManager.setToken('test-token'); tokenManager.updateExpiry('2026-04-26T20:00:00Z'); expect(tokenManager.getExpiry()).toBe('2026-04-26T20:00:00Z'); }); it('getExpiry returns null when no expiry is set', () => { localStorage.clear(); expect(tokenManager.getExpiry()).toBeNull(); }); }); });