import { describe, it, expect, vi, Mocked, beforeEach } from 'vitest'; import { SessionService } from '@/lib/services/auth/SessionService'; import { AuthApiClient } from '@/lib/api/auth/AuthApiClient'; import { SessionViewModel } from '@/lib/view-models/SessionViewModel'; // Mock dependencies vi.mock('@/lib/config/apiBaseUrl', () => ({ getWebsiteApiBaseUrl: () => 'http://localhost:3000', })); vi.mock('@/lib/config/env', () => ({ isProductionEnvironment: () => false, })); describe('SessionService', () => { let mockApiClient: Mocked; let service: SessionService; beforeEach(() => { mockApiClient = { signup: vi.fn(), login: vi.fn(), logout: vi.fn(), forgotPassword: vi.fn(), resetPassword: vi.fn(), getSession: vi.fn(), } as Mocked; service = new SessionService(mockApiClient); }); describe('getSession', () => { describe('happy paths', () => { it('should call apiClient.getSession and return SessionViewModel when session exists', async () => { const mockResponse = { token: 'jwt-token', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(mockApiClient.getSession).toHaveBeenCalled(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm).toBeInstanceOf(SessionViewModel); expect(vm?.userId).toBe('user-123'); expect(vm?.email).toBe('test@example.com'); expect(vm?.displayName).toBe('Test User'); expect(vm?.isAuthenticated).toBe(true); }); it('should return null when apiClient.getSession returns null', async () => { mockApiClient.getSession.mockResolvedValue(null); const result = await service.getSession(); expect(mockApiClient.getSession).toHaveBeenCalled(); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeNull(); }); it('should return null when apiClient.getSession returns undefined', async () => { mockApiClient.getSession.mockResolvedValue(undefined); const result = await service.getSession(); expect(mockApiClient.getSession).toHaveBeenCalled(); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeNull(); }); it('should return null when session has no user data', async () => { const mockResponse = { token: 'jwt-token', user: null, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(mockApiClient.getSession).toHaveBeenCalled(); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeNull(); }); }); describe('failure modes', () => { it('should handle server errors', async () => { const error = new Error('Get session failed'); mockApiClient.getSession.mockRejectedValue(error); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('Get session failed'); }); it('should handle network errors', async () => { const error = new Error('Network error'); mockApiClient.getSession.mockRejectedValue(error); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('Network error'); }); it('should handle authentication errors', async () => { const error = new Error('Invalid token'); mockApiClient.getSession.mockRejectedValue(error); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('Invalid token'); }); it('should handle timeout errors', async () => { const error = new Error('Request timeout'); mockApiClient.getSession.mockRejectedValue(error); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('Request timeout'); }); }); describe('decision branches', () => { it('should handle different user data structures', async () => { const mockResponse = { token: 'jwt-token', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', avatarUrl: 'https://example.com/avatar.jpg', role: 'admin', permissions: ['read', 'write'], lastLogin: '2024-01-01T00:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm).toBeInstanceOf(SessionViewModel); expect(vm?.userId).toBe('user-123'); expect(vm?.email).toBe('test@example.com'); expect(vm?.displayName).toBe('Test User'); expect(vm?.isAuthenticated).toBe(true); }); it('should handle user with minimal data', async () => { const mockResponse = { token: 'jwt-token', user: { userId: 'user-123', email: 'test@example.com', displayName: '', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm?.displayName).toBe(''); expect(vm?.isAuthenticated).toBe(true); }); it('should handle user with special characters in display name', async () => { const mockResponse = { token: 'jwt-token', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User ', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm?.displayName).toBe('Test User '); expect(vm?.isAuthenticated).toBe(true); }); it('should handle different email formats', async () => { const emails = [ 'user@example.com', 'user+tag@example.com', 'user.name@example.com', 'user@subdomain.example.com', ]; for (const email of emails) { const mockResponse = { token: 'jwt-token', user: { userId: 'user-123', email, displayName: 'Test User', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm?.email).toBe(email); } }); it('should handle different token formats', async () => { const tokens = [ 'simple-token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', 'token-with-special-chars-!@#$%^&*()', ]; for (const token of tokens) { const mockResponse = { token, user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm?.isAuthenticated).toBe(true); } }); }); describe('aggregation logic', () => { it('should aggregate session data correctly', async () => { const mockResponse = { token: 'jwt-token', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); // Verify all user data is aggregated into the view model expect(vm?.userId).toBe('user-123'); expect(vm?.email).toBe('test@example.com'); expect(vm?.displayName).toBe('Test User'); expect(vm?.isAuthenticated).toBe(true); }); it('should handle empty user object', async () => { const mockResponse = { token: 'jwt-token', user: {}, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeNull(); }); it('should handle missing token', async () => { const mockResponse = { token: null, user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', }, }; mockApiClient.getSession.mockResolvedValue(mockResponse); const result = await service.getSession(); expect(result.isOk()).toBe(true); const vm = result.unwrap(); expect(vm).toBeInstanceOf(SessionViewModel); expect(vm?.userId).toBe('user-123'); }); }); }); describe('error handling', () => { it('should handle unexpected error types', async () => { const error = { customError: 'Something went wrong' }; mockApiClient.getSession.mockRejectedValue(error); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('Something went wrong'); }); it('should handle string errors', async () => { mockApiClient.getSession.mockRejectedValue('String error'); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('String error'); }); it('should handle undefined errors', async () => { mockApiClient.getSession.mockRejectedValue(undefined); const result = await service.getSession(); expect(result.isErr()).toBe(true); expect(result.getError().type).toBe('serverError'); expect(result.getError().message).toBe('Failed to get session'); }); }); });