791 lines
22 KiB
TypeScript
791 lines
22 KiB
TypeScript
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
|
|
import { UserRole } from '@core/admin/domain/value-objects/UserRole';
|
|
import { UserStatus } from '@core/admin/domain/value-objects/UserStatus';
|
|
import { InMemoryAdminUserRepository } from './InMemoryAdminUserRepository';
|
|
|
|
describe('InMemoryAdminUserRepository', () => {
|
|
describe('TDD - Test First', () => {
|
|
let repository: InMemoryAdminUserRepository;
|
|
|
|
beforeEach(() => {
|
|
repository = new InMemoryAdminUserRepository();
|
|
});
|
|
|
|
describe('create', () => {
|
|
it('should create a new user', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-123',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
// Act
|
|
const result = await repository.create(user);
|
|
|
|
// Assert
|
|
expect(result).toStrictEqual(user);
|
|
const found = await repository.findById(user.id);
|
|
expect(found).toStrictEqual(user);
|
|
});
|
|
|
|
it('should throw error when creating user with duplicate email', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
displayName: 'User 1',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'test@example.com',
|
|
displayName: 'User 2',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
|
|
// Act & Assert
|
|
await expect(repository.create(user2)).rejects.toThrow('Email already exists');
|
|
});
|
|
|
|
it('should throw error when creating user with duplicate ID', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'User 1',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user2@example.com',
|
|
displayName: 'User 2',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
|
|
// Act & Assert
|
|
await expect(repository.create(user2)).rejects.toThrow('User ID already exists');
|
|
});
|
|
});
|
|
|
|
describe('findById', () => {
|
|
it('should find user by ID', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-123',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user);
|
|
|
|
// Act
|
|
const found = await repository.findById(user.id);
|
|
|
|
// Assert
|
|
expect(found).toStrictEqual(user);
|
|
});
|
|
|
|
it('should return null for non-existent user', async () => {
|
|
// Arrange
|
|
const nonExistentId = AdminUser.create({
|
|
id: 'non-existent',
|
|
email: 'dummy@example.com',
|
|
displayName: 'Dummy',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
}).id;
|
|
|
|
// Act
|
|
const found = await repository.findById(nonExistentId);
|
|
|
|
// Assert
|
|
expect(found).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('findByEmail', () => {
|
|
it('should find user by email', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-123',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user);
|
|
|
|
// Act
|
|
const found = await repository.findByEmail(user.email);
|
|
|
|
// Assert
|
|
expect(found).toStrictEqual(user);
|
|
});
|
|
|
|
it('should return null for non-existent email', async () => {
|
|
// Arrange
|
|
const nonExistentEmail = AdminUser.create({
|
|
id: 'dummy',
|
|
email: 'non-existent@example.com',
|
|
displayName: 'Dummy',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
}).email;
|
|
|
|
// Act
|
|
const found = await repository.findByEmail(nonExistentEmail);
|
|
|
|
// Assert
|
|
expect(found).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('emailExists', () => {
|
|
it('should return true when email exists', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-123',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user);
|
|
|
|
// Act
|
|
const exists = await repository.emailExists(user.email);
|
|
|
|
// Assert
|
|
expect(exists).toBe(true);
|
|
});
|
|
|
|
it('should return false when email does not exist', async () => {
|
|
// Arrange
|
|
const nonExistentEmail = AdminUser.create({
|
|
id: 'dummy',
|
|
email: 'non-existent@example.com',
|
|
displayName: 'Dummy',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
}).email;
|
|
|
|
// Act
|
|
const exists = await repository.emailExists(nonExistentEmail);
|
|
|
|
// Assert
|
|
expect(exists).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('list', () => {
|
|
it('should return empty list when no users exist', async () => {
|
|
// Act
|
|
const result = await repository.list({});
|
|
|
|
// Assert
|
|
expect(result.users).toEqual([]);
|
|
expect(result.total).toBe(0);
|
|
expect(result.page).toBe(1);
|
|
expect(result.limit).toBe(10);
|
|
expect(result.totalPages).toBe(0);
|
|
});
|
|
|
|
it('should return all users when no filters provided', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'User 1',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
displayName: 'User 2',
|
|
roles: ['admin'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act
|
|
const result = await repository.list({});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(2);
|
|
expect(result.total).toBe(2);
|
|
expect(result.page).toBe(1);
|
|
expect(result.limit).toBe(10);
|
|
expect(result.totalPages).toBe(1);
|
|
});
|
|
|
|
it('should filter by role', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'User 1',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const admin1 = AdminUser.create({
|
|
id: 'admin-1',
|
|
email: 'admin1@example.com',
|
|
displayName: 'Admin 1',
|
|
roles: ['admin'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(admin1);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
filter: { role: UserRole.fromString('admin') },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0]?.id.value).toBe('admin-1');
|
|
expect(result.total).toBe(1);
|
|
});
|
|
|
|
it('should filter by status', async () => {
|
|
// Arrange
|
|
const activeUser = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'active@example.com',
|
|
displayName: 'Active User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const suspendedUser = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'suspended@example.com',
|
|
displayName: 'Suspended User',
|
|
roles: ['user'],
|
|
status: 'suspended',
|
|
});
|
|
|
|
await repository.create(activeUser);
|
|
await repository.create(suspendedUser);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
filter: { status: UserStatus.fromString('suspended') },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0]?.id.value).toBe('user-2');
|
|
expect(result.total).toBe(1);
|
|
});
|
|
|
|
it('should filter by email', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'other@example.com',
|
|
displayName: 'Other User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
filter: { email: user1.email },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0]?.id.value).toBe('user-1');
|
|
expect(result.total).toBe(1);
|
|
});
|
|
|
|
it('should filter by search (email or display name)', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'search@example.com',
|
|
displayName: 'Search User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'other@example.com',
|
|
displayName: 'Other User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
filter: { search: 'search' },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0]?.id.value).toBe('user-1');
|
|
expect(result.total).toBe(1);
|
|
});
|
|
|
|
it('should apply pagination', async () => {
|
|
// Arrange - Create 15 users
|
|
for (let i = 1; i <= 15; i++) {
|
|
const user = AdminUser.create({
|
|
id: `user-${i}`,
|
|
email: `user${i}@example.com`,
|
|
displayName: `User ${i}`,
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
await repository.create(user);
|
|
}
|
|
|
|
// Act - Get page 2 with limit 5
|
|
const result = await repository.list({
|
|
pagination: { page: 2, limit: 5 },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(5);
|
|
expect(result.total).toBe(15);
|
|
expect(result.page).toBe(2);
|
|
expect(result.limit).toBe(5);
|
|
expect(result.totalPages).toBe(3);
|
|
expect(result.users[0]?.id.value).toBe('user-6');
|
|
});
|
|
|
|
it('should sort by email ascending', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'zebra@example.com',
|
|
displayName: 'Zebra',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'alpha@example.com',
|
|
displayName: 'Alpha',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
sort: { field: 'email', direction: 'asc' },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(2);
|
|
expect(result.users[0]?.id.value).toBe('user-2');
|
|
expect(result.users[1]?.id.value).toBe('user-1');
|
|
});
|
|
|
|
it('should sort by display name descending', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'Zebra',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
displayName: 'Alpha',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
sort: { field: 'displayName', direction: 'desc' },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(2);
|
|
expect(result.users[0]?.id.value).toBe('user-1');
|
|
expect(result.users[1]?.id.value).toBe('user-2');
|
|
});
|
|
|
|
it('should apply multiple filters together', async () => {
|
|
// Arrange
|
|
const matchingUser = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'admin@example.com',
|
|
displayName: 'Admin User',
|
|
roles: ['admin'],
|
|
status: 'active',
|
|
});
|
|
|
|
const nonMatchingUser1 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'user@example.com',
|
|
displayName: 'User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const nonMatchingUser2 = AdminUser.create({
|
|
id: 'user-3',
|
|
email: 'admin-suspended@example.com',
|
|
displayName: 'Admin User',
|
|
roles: ['admin'],
|
|
status: 'suspended',
|
|
});
|
|
|
|
await repository.create(matchingUser);
|
|
await repository.create(nonMatchingUser1);
|
|
await repository.create(nonMatchingUser2);
|
|
|
|
// Act
|
|
const result = await repository.list({
|
|
filter: {
|
|
role: UserRole.fromString('admin'),
|
|
status: UserStatus.fromString('active'),
|
|
search: 'admin',
|
|
},
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0]?.id.value).toBe('user-1');
|
|
expect(result.total).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('count', () => {
|
|
it('should return 0 when no users exist', async () => {
|
|
// Act
|
|
const count = await repository.count();
|
|
|
|
// Assert
|
|
expect(count).toBe(0);
|
|
});
|
|
|
|
it('should return correct count of all users', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'User 1',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
displayName: 'User 2',
|
|
roles: ['admin'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act
|
|
const count = await repository.count();
|
|
|
|
// Assert
|
|
expect(count).toBe(2);
|
|
});
|
|
|
|
it('should count with filters', async () => {
|
|
// Arrange
|
|
const activeUser = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'active@example.com',
|
|
displayName: 'Active User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const suspendedUser = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'suspended@example.com',
|
|
displayName: 'Suspended User',
|
|
roles: ['user'],
|
|
status: 'suspended',
|
|
});
|
|
|
|
await repository.create(activeUser);
|
|
await repository.create(suspendedUser);
|
|
|
|
// Act
|
|
const count = await repository.count({
|
|
status: UserStatus.fromString('active'),
|
|
});
|
|
|
|
// Assert
|
|
expect(count).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('update', () => {
|
|
it('should update existing user', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user);
|
|
|
|
// Act
|
|
user.updateDisplayName('Updated Name');
|
|
const updated = await repository.update(user);
|
|
|
|
// Assert
|
|
expect(updated.displayName).toBe('Updated Name');
|
|
const found = await repository.findById(user.id);
|
|
expect(found?.displayName).toBe('Updated Name');
|
|
});
|
|
|
|
it('should throw error when updating non-existent user', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'non-existent',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
// Act & Assert
|
|
await expect(repository.update(user)).rejects.toThrow('User not found');
|
|
});
|
|
|
|
it('should throw error when updating with duplicate email', async () => {
|
|
// Arrange
|
|
const user1 = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'User 1',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
const user2 = AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
displayName: 'User 2',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user1);
|
|
await repository.create(user2);
|
|
|
|
// Act - Try to change user2's email to user1's email
|
|
user2.updateEmail(user1.email);
|
|
|
|
// Assert
|
|
await expect(repository.update(user2)).rejects.toThrow('Email already exists');
|
|
});
|
|
});
|
|
|
|
describe('delete', () => {
|
|
it('should delete existing user', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user);
|
|
|
|
// Act
|
|
await repository.delete(user.id);
|
|
|
|
// Assert
|
|
const found = await repository.findById(user.id);
|
|
expect(found).toBeNull();
|
|
});
|
|
|
|
it('should throw error when deleting non-existent user', async () => {
|
|
// Arrange
|
|
const nonExistentId = AdminUser.create({
|
|
id: 'non-existent',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
}).id;
|
|
|
|
// Act & Assert
|
|
await expect(repository.delete(nonExistentId)).rejects.toThrow('User not found');
|
|
});
|
|
|
|
it('should reduce count after deletion', async () => {
|
|
// Arrange
|
|
const user = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
await repository.create(user);
|
|
const initialCount = await repository.count();
|
|
|
|
// Act
|
|
await repository.delete(user.id);
|
|
|
|
// Assert
|
|
const finalCount = await repository.count();
|
|
expect(finalCount).toBe(initialCount - 1);
|
|
});
|
|
});
|
|
|
|
describe('integration scenarios', () => {
|
|
it('should handle complete user lifecycle', async () => {
|
|
// Arrange - Create user
|
|
const user = AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'lifecycle@example.com',
|
|
displayName: 'Lifecycle User',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
});
|
|
|
|
// Act - Create
|
|
await repository.create(user);
|
|
let found = await repository.findById(user.id);
|
|
expect(found).toStrictEqual(user);
|
|
|
|
// Act - Update
|
|
user.updateDisplayName('Updated Lifecycle');
|
|
user.addRole(UserRole.fromString('admin'));
|
|
await repository.update(user);
|
|
found = await repository.findById(user.id);
|
|
expect(found?.displayName).toBe('Updated Lifecycle');
|
|
expect(found?.hasRole('admin')).toBe(true);
|
|
|
|
// Act - Delete
|
|
await repository.delete(user.id);
|
|
found = await repository.findById(user.id);
|
|
expect(found).toBeNull();
|
|
});
|
|
|
|
it('should handle complex filtering and pagination', async () => {
|
|
// Arrange - Create mixed users
|
|
const users = [
|
|
AdminUser.create({
|
|
id: 'owner-1',
|
|
email: 'owner@example.com',
|
|
displayName: 'Owner User',
|
|
roles: ['owner'],
|
|
status: 'active',
|
|
}),
|
|
AdminUser.create({
|
|
id: 'admin-1',
|
|
email: 'admin1@example.com',
|
|
displayName: 'Admin One',
|
|
roles: ['admin'],
|
|
status: 'active',
|
|
}),
|
|
AdminUser.create({
|
|
id: 'admin-2',
|
|
email: 'admin2@example.com',
|
|
displayName: 'Admin Two',
|
|
roles: ['admin'],
|
|
status: 'suspended',
|
|
}),
|
|
AdminUser.create({
|
|
id: 'user-1',
|
|
email: 'user1@example.com',
|
|
displayName: 'User One',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
}),
|
|
AdminUser.create({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
displayName: 'User Two',
|
|
roles: ['user'],
|
|
status: 'active',
|
|
}),
|
|
];
|
|
|
|
for (const user of users) {
|
|
await repository.create(user);
|
|
}
|
|
|
|
// Act - Get active admins, sorted by email, page 1, limit 2
|
|
const result = await repository.list({
|
|
filter: {
|
|
role: UserRole.fromString('admin'),
|
|
status: UserStatus.fromString('active'),
|
|
},
|
|
sort: { field: 'email', direction: 'asc' },
|
|
pagination: { page: 1, limit: 2 },
|
|
});
|
|
|
|
// Assert
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0]?.id.value).toBe('admin-1');
|
|
expect(result.total).toBe(1);
|
|
expect(result.totalPages).toBe(1);
|
|
});
|
|
});
|
|
});
|
|
});
|