204 lines
5.8 KiB
TypeScript
204 lines
5.8 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
}); |