import { describe, it, expect, vi, beforeEach } from 'vitest'; import { AuthService } from './AuthService'; import { AuthApiClient } from '../../api/auth/AuthApiClient'; import type { LoginParamsDto, SignupParamsDto, SessionDataDto } from '../../dtos'; describe('AuthService', () => { let mockApiClient: AuthApiClient; let service: AuthService; beforeEach(() => { mockApiClient = { signup: vi.fn(), login: vi.fn(), logout: vi.fn(), getSession: vi.fn(), getIracingAuthUrl: vi.fn(), } as unknown as AuthApiClient; service = new AuthService(mockApiClient); }); describe('signup', () => { it('should sign up user via API client', async () => { // Arrange const params: SignupParamsDto = { email: 'test@example.com', password: 'password123', displayName: 'Test User', }; const expectedSession: SessionDataDto = { userId: 'user-1', email: 'test@example.com', displayName: 'Test User', isAuthenticated: true, }; vi.mocked(mockApiClient.signup).mockResolvedValue(expectedSession); // Act const result = await service.signup(params); // Assert expect(mockApiClient.signup).toHaveBeenCalledWith(params); expect(mockApiClient.signup).toHaveBeenCalledTimes(1); expect(result).toBe(expectedSession); }); it('should propagate API client errors', async () => { // Arrange const params: SignupParamsDto = { email: 'test@example.com', password: 'password123', }; const error = new Error('API Error: Failed to sign up'); vi.mocked(mockApiClient.signup).mockRejectedValue(error); // Act & Assert await expect(service.signup(params)).rejects.toThrow( 'API Error: Failed to sign up' ); expect(mockApiClient.signup).toHaveBeenCalledWith(params); expect(mockApiClient.signup).toHaveBeenCalledTimes(1); }); }); describe('login', () => { it('should log in user via API client', async () => { // Arrange const params: LoginParamsDto = { email: 'test@example.com', password: 'password123', }; const expectedSession: SessionDataDto = { userId: 'user-1', email: 'test@example.com', isAuthenticated: true, }; vi.mocked(mockApiClient.login).mockResolvedValue(expectedSession); // Act const result = await service.login(params); // Assert expect(mockApiClient.login).toHaveBeenCalledWith(params); expect(mockApiClient.login).toHaveBeenCalledTimes(1); expect(result).toBe(expectedSession); }); it('should propagate API client errors', async () => { // Arrange const params: LoginParamsDto = { email: 'test@example.com', password: 'password123', }; const error = new Error('API Error: Invalid credentials'); vi.mocked(mockApiClient.login).mockRejectedValue(error); // Act & Assert await expect(service.login(params)).rejects.toThrow( 'API Error: Invalid credentials' ); expect(mockApiClient.login).toHaveBeenCalledWith(params); expect(mockApiClient.login).toHaveBeenCalledTimes(1); }); }); describe('logout', () => { it('should log out user via API client', async () => { // Arrange vi.mocked(mockApiClient.logout).mockResolvedValue(undefined); // Act await service.logout(); // Assert expect(mockApiClient.logout).toHaveBeenCalledTimes(1); }); it('should propagate API client errors', async () => { // Arrange const error = new Error('API Error: Failed to logout'); vi.mocked(mockApiClient.logout).mockRejectedValue(error); // Act & Assert await expect(service.logout()).rejects.toThrow( 'API Error: Failed to logout' ); expect(mockApiClient.logout).toHaveBeenCalledTimes(1); }); }); describe('getIracingAuthUrl', () => { it('should get iRacing auth URL via API client', () => { // Arrange const returnTo = '/dashboard'; const expectedUrl = 'http://localhost:3001/auth/iracing/start?returnTo=%2Fdashboard'; vi.mocked(mockApiClient.getIracingAuthUrl).mockReturnValue(expectedUrl); // Act const result = service.getIracingAuthUrl(returnTo); // Assert expect(mockApiClient.getIracingAuthUrl).toHaveBeenCalledWith(returnTo); expect(mockApiClient.getIracingAuthUrl).toHaveBeenCalledTimes(1); expect(result).toBe(expectedUrl); }); it('should handle undefined returnTo', () => { // Arrange const expectedUrl = 'http://localhost:3001/auth/iracing/start'; vi.mocked(mockApiClient.getIracingAuthUrl).mockReturnValue(expectedUrl); // Act const result = service.getIracingAuthUrl(); // Assert expect(mockApiClient.getIracingAuthUrl).toHaveBeenCalledWith(undefined); expect(result).toBe(expectedUrl); }); }); describe('Constructor Dependency Injection', () => { it('should require apiClient', () => { // This test verifies the constructor signature expect(() => { new AuthService(mockApiClient); }).not.toThrow(); }); it('should use injected apiClient', async () => { // Arrange const customApiClient = { signup: vi.fn().mockResolvedValue({ userId: 'user-1', email: 'test@example.com', isAuthenticated: true }), login: vi.fn(), logout: vi.fn(), getSession: vi.fn(), getIracingAuthUrl: vi.fn(), } as unknown as AuthApiClient; const customService = new AuthService(customApiClient); const params: SignupParamsDto = { email: 'test@example.com', password: 'password123', }; // Act await customService.signup(params); // Assert expect(customApiClient.signup).toHaveBeenCalledWith(params); }); }); });