Files
gridpilot.gg/adapters/admin/persistence/inmemory/InMemoryAdminUserRepository.test.ts
2026-01-24 02:05:43 +01:00

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);
});
});
});
});