Files
SerpentRace/SerpentRace_Backend/tests/Application/User/commands/UserCommandHandlers.comprehensive.test.ts
T

431 lines
14 KiB
TypeScript

// Comprehensive test coverage for User Command Handlers
import { CreateUserCommand } from '../../../../src/Application/User/commands/CreateUserCommand';
import { CreateUserCommandHandler } from '../../../../src/Application/User/commands/CreateUserCommandHandler';
import { LoginCommand } from '../../../../src/Application/User/commands/LoginCommand';
import { LoginCommandHandler } from '../../../../src/Application/User/commands/LoginCommandHandler';
import { UpdateUserCommand } from '../../../../src/Application/User/commands/UpdateUserCommand';
import { UpdateUserCommandHandler } from '../../../../src/Application/User/commands/UpdateUserCommandHandler';
import { DeactivateUserCommand } from '../../../../src/Application/User/commands/DeactivateUserCommand';
import { DeactivateUserCommandHandler } from '../../../../src/Application/User/commands/DeactivateUserCommandHandler';
import { IUserRepository } from '../../../../src/Domain/IRepository/IUserRepository';
import { IOrganizationRepository } from '../../../../src/Domain/IRepository/IOrganizationRepository';
import { JWTService } from '../../../../src/Application/Services/JWTService';
import { PasswordService } from '../../../../src/Application/Services/PasswordService';
import { UserState } from '../../../../src/Domain/User/UserAggregate';
import {
createMockUser,
createMockUserRepository,
createMockOrganizationRepository,
createMockJWTService
} from '../../../testUtils';
// Mock PasswordService static methods
jest.mock('../../../../src/Application/Services/PasswordService', () => ({
PasswordService: {
validatePasswordStrength: jest.fn().mockReturnValue({ isValid: true, errors: [] }),
hashPassword: jest.fn().mockResolvedValue('hashed-password'),
verifyPassword: jest.fn().mockResolvedValue(true)
}
}));
describe('User Command Handlers - Comprehensive Coverage', () => {
describe('CreateUserCommandHandler', () => {
let mockUserRepository: jest.Mocked<IUserRepository>;
let handler: CreateUserCommandHandler;
beforeEach(() => {
mockUserRepository = createMockUserRepository();
handler = new CreateUserCommandHandler(mockUserRepository);
});
it('should create a new user successfully', async () => {
// Arrange
const command: CreateUserCommand = {
username: 'testuser',
email: 'test@example.com',
password: 'Password123!', // Strong password
fname: 'Test',
lname: 'User',
type: 'regular'
};
const mockUser = createMockUser({
username: command.username,
email: command.email,
state: UserState.REGISTERED_NOT_VERIFIED
});
// CreateUserCommandHandler doesn't check existing users - goes directly to create
mockUserRepository.create.mockResolvedValue(mockUser);
// Act
const result = await handler.execute(command);
// Assert
expect(result).toBeDefined();
// CreateUserCommandHandler doesn't call findByUsername/findByEmail
expect(mockUserRepository.create).toHaveBeenCalled();
});
it('should throw error when username already exists', async () => {
// Arrange
const command: CreateUserCommand = {
username: 'existinguser',
email: 'test@example.com',
password: 'Password123!', // Strong password
fname: 'Test',
lname: 'User',
type: 'regular'
};
// Simulate database constraint error for duplicate username
mockUserRepository.create.mockRejectedValue(new Error('duplicate key value violates unique constraint'));
// Act & Assert
await expect(handler.execute(command)).rejects.toThrow('User with this username or email already exists');
});
it('should throw error when email already exists', async () => {
// Arrange
const command: CreateUserCommand = {
username: 'testuser',
email: 'existing@example.com',
password: 'Password123!', // Strong password
fname: 'Test',
lname: 'User',
type: 'regular'
};
// Simulate database constraint error for duplicate email
mockUserRepository.create.mockRejectedValue(new Error('unique constraint violation'));
// Act & Assert
await expect(handler.execute(command)).rejects.toThrow('User with this username or email already exists');
});
it('should handle repository errors', async () => {
// Arrange
const command: CreateUserCommand = {
username: 'testuser',
email: 'test@example.com',
password: 'Password123!', // Strong password
fname: 'Test',
lname: 'User',
type: 'regular'
};
mockUserRepository.findByUsername.mockResolvedValue(null);
mockUserRepository.findByEmail.mockResolvedValue(null);
mockUserRepository.create.mockRejectedValue(new Error('Database error'));
// Act & Assert
await expect(handler.execute(command)).rejects.toThrow('Failed to create user');
});
});
describe('LoginCommandHandler', () => {
let mockUserRepository: jest.Mocked<IUserRepository>;
let mockOrgRepository: jest.Mocked<IOrganizationRepository>;
let mockJwtService: jest.Mocked<JWTService>;
let handler: LoginCommandHandler;
beforeEach(() => {
mockUserRepository = createMockUserRepository();
mockOrgRepository = createMockOrganizationRepository();
mockJwtService = createMockJWTService();
handler = new LoginCommandHandler(mockUserRepository, mockJwtService, mockOrgRepository);
// Reset all mocks
jest.clearAllMocks();
// Set default PasswordService behavior
const mockPasswordService = PasswordService as jest.Mocked<typeof PasswordService>;
mockPasswordService.verifyPassword.mockResolvedValue(true); // Default to valid password
});
it('should login user with valid credentials', async () => {
// Arrange
const command: LoginCommand = {
username: 'testuser',
password: 'Password123!'
};
const mockUser = createMockUser({
username: command.username,
state: UserState.VERIFIED_REGULAR
});
mockUserRepository.findByUsername.mockResolvedValue(mockUser);
mockJwtService.create.mockReturnValue('jwt-token');
// Act
const result = await handler.execute(command);
// Assert
expect(result).toBeDefined();
expect(result!.token).toBe('jwt-token');
expect(mockJwtService.create).toHaveBeenCalled();
});
it('should handle user not found', async () => {
// Arrange
const command: LoginCommand = {
username: 'nonexistent',
password: 'password123'
};
mockUserRepository.findByUsername.mockResolvedValue(null);
// Act & Assert
const result = await handler.execute(command);
expect(result).toBeNull();
});
it('should handle invalid password', async () => {
// Arrange
const command: LoginCommand = {
username: 'testuser',
password: 'wrongpassword'
};
const mockUser = createMockUser({
username: command.username,
password: 'hashedpassword'
});
mockUserRepository.findByUsername.mockResolvedValue(mockUser);
// Mock password verification to return false for wrong password
const mockPasswordService = PasswordService as jest.Mocked<typeof PasswordService>;
mockPasswordService.verifyPassword.mockResolvedValue(false);
// Act & Assert
const result = await handler.execute(command);
expect(result).toBeNull();
});
it('should handle unverified user', async () => {
// Arrange - LoginCommandHandler doesn't reject unverified users, it processes them normally
const command: LoginCommand = {
username: 'testuser',
password: 'Password123!'
};
const mockUser = createMockUser({
username: command.username,
password: 'hashedpassword',
state: UserState.REGISTERED_NOT_VERIFIED
});
mockUserRepository.findByUsername.mockResolvedValue(mockUser);
mockJwtService.create.mockReturnValue('jwt-token');
// Act
const result = await handler.execute(command);
// Assert - LoginCommandHandler processes unverified users normally
expect(result).toBeDefined();
expect(result!.user).toBeDefined();
expect(result!.token).toBe('jwt-token');
});
});
describe('UpdateUserCommandHandler', () => {
let mockUserRepository: jest.Mocked<IUserRepository>;
let handler: UpdateUserCommandHandler;
beforeEach(() => {
mockUserRepository = createMockUserRepository();
handler = new UpdateUserCommandHandler(mockUserRepository);
});
it('should update user successfully', async () => {
// Arrange
const command: UpdateUserCommand = {
id: 'user-123',
email: 'newemail@example.com'
};
const existingUser = createMockUser({ id: command.id });
const updatedUser = createMockUser({
id: command.id,
email: command.email
});
mockUserRepository.findById.mockResolvedValue(existingUser);
mockUserRepository.update.mockResolvedValue(updatedUser);
// Act
const result = await handler.execute(command);
// Assert
expect(result).toBeDefined();
expect(mockUserRepository.update).toHaveBeenCalledWith(command.id, expect.any(Object));
});
it('should return null when user not found', async () => {
// Arrange
const command: UpdateUserCommand = {
id: 'nonexistent-user',
email: 'newemail@example.com'
};
mockUserRepository.update.mockResolvedValue(null); // UpdateUserCommandHandler calls update directly, not findById first
// Act
const result = await handler.execute(command);
// Assert
expect(result).toBeNull();
expect(mockUserRepository.update).toHaveBeenCalledWith(command.id, expect.any(Object));
});
it('should handle partial updates', async () => {
// Arrange
const command: UpdateUserCommand = {
id: 'user-123',
username: 'newusername'
};
const existingUser = createMockUser({ id: command.id });
const updatedUser = createMockUser({
id: command.id,
username: command.username
});
mockUserRepository.findById.mockResolvedValue(existingUser);
mockUserRepository.update.mockResolvedValue(updatedUser);
// Act
const result = await handler.execute(command);
// Assert
expect(result).toBeDefined();
});
});
describe('DeactivateUserCommandHandler', () => {
let mockUserRepository: jest.Mocked<IUserRepository>;
let handler: DeactivateUserCommandHandler;
beforeEach(() => {
mockUserRepository = createMockUserRepository();
handler = new DeactivateUserCommandHandler(mockUserRepository);
});
it('should deactivate user successfully', async () => {
// Arrange
const command: DeactivateUserCommand = {
id: 'user-123'
};
const deactivatedUser = createMockUser({
id: command.id,
state: UserState.DEACTIVATED
});
mockUserRepository.deactivate.mockResolvedValue(deactivatedUser);
// Act
const result = await handler.execute(command);
// Assert
expect(result).toBe(true);
expect(mockUserRepository.deactivate).toHaveBeenCalledWith(command.id);
});
it('should handle repository errors', async () => {
// Arrange
const command: DeactivateUserCommand = {
id: 'user-123'
};
mockUserRepository.deactivate.mockRejectedValue(new Error('Deactivation failed'));
// Act & Assert
await expect(handler.execute(command)).rejects.toThrow('Deactivation failed');
});
});
describe('Cross-Command Integration Tests', () => {
let mockUserRepository: jest.Mocked<IUserRepository>;
let mockOrgRepository: jest.Mocked<IOrganizationRepository>;
let mockJwtService: jest.Mocked<JWTService>;
beforeEach(() => {
mockUserRepository = createMockUserRepository();
mockOrgRepository = createMockOrganizationRepository();
mockJwtService = createMockJWTService();
});
it('should create user and then login', async () => {
// Arrange
const createHandler = new CreateUserCommandHandler(mockUserRepository);
const loginHandler = new LoginCommandHandler(mockUserRepository, mockJwtService, mockOrgRepository);
const createCommand: CreateUserCommand = {
username: 'testuser',
email: 'test@example.com',
password: 'Password123!', // Strong password
fname: 'Test',
lname: 'User',
type: 'regular'
};
const loginCommand: LoginCommand = {
username: 'testuser',
password: 'Password123!' // Strong password
};
const mockUser = createMockUser({
username: createCommand.username,
email: createCommand.email,
state: UserState.VERIFIED_REGULAR
});
// Mock create user flow
mockUserRepository.findByUsername.mockResolvedValueOnce(null);
mockUserRepository.findByEmail.mockResolvedValue(null);
mockUserRepository.create.mockResolvedValue(mockUser);
// Mock login flow
mockUserRepository.findByUsername.mockResolvedValueOnce(mockUser);
mockJwtService.create.mockReturnValue('jwt-token');
// Act
const createResult = await createHandler.execute(createCommand);
const loginResult = await loginHandler.execute(loginCommand);
// Assert
expect(createResult).toBeDefined();
expect(loginResult).toBeDefined();
});
it('should update user after creation', async () => {
// Arrange
const updateHandler = new UpdateUserCommandHandler(mockUserRepository);
const updateCommand: UpdateUserCommand = {
id: 'user-123',
email: 'updated@example.com'
};
const existingUser = createMockUser({ id: updateCommand.id });
const updatedUser = createMockUser({
id: updateCommand.id,
email: updateCommand.email
});
mockUserRepository.findById.mockResolvedValue(existingUser);
mockUserRepository.update.mockResolvedValue(updatedUser);
// Act
const result = await updateHandler.execute(updateCommand);
// Assert
expect(result).toBeDefined();
expect(result).not.toBeNull();
});
});
});