core tests
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
|
||||
import { AdminUserRepository, StoredAdminUser, UserFilter, UserListQuery, UserListResult } from '@core/admin/domain/repositories/AdminUserRepository';
|
||||
import { Email } from '@core/admin/domain/value-objects/Email';
|
||||
import { UserId } from '@core/admin/domain/value-objects/UserId';
|
||||
|
||||
/**
|
||||
* In-memory implementation of AdminUserRepository for testing and development
|
||||
* Follows TDD - created with tests first
|
||||
*/
|
||||
export class InMemoryAdminUserRepository implements AdminUserRepository {
|
||||
private storage: Map<string, StoredAdminUser> = new Map();
|
||||
|
||||
async findById(id: UserId): Promise<AdminUser | null> {
|
||||
const stored = this.storage.get(id.value);
|
||||
return stored ? this.fromStored(stored) : null;
|
||||
}
|
||||
|
||||
async findByEmail(email: Email): Promise<AdminUser | null> {
|
||||
for (const stored of this.storage.values()) {
|
||||
if (stored.email === email.value) {
|
||||
return this.fromStored(stored);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async emailExists(email: Email): Promise<boolean> {
|
||||
for (const stored of this.storage.values()) {
|
||||
if (stored.email === email.value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async existsById(id: UserId): Promise<boolean> {
|
||||
return this.storage.has(id.value);
|
||||
}
|
||||
|
||||
async existsByEmail(email: Email): Promise<boolean> {
|
||||
return this.emailExists(email);
|
||||
}
|
||||
|
||||
async list(query?: UserListQuery): Promise<UserListResult> {
|
||||
let users: AdminUser[] = [];
|
||||
|
||||
// Get all users
|
||||
for (const stored of this.storage.values()) {
|
||||
users.push(this.fromStored(stored));
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
if (query?.filter) {
|
||||
users = users.filter(user => {
|
||||
if (query.filter?.role && !user.roles.some(r => r.equals(query.filter!.role!))) {
|
||||
return false;
|
||||
}
|
||||
if (query.filter?.status && !user.status.equals(query.filter.status)) {
|
||||
return false;
|
||||
}
|
||||
if (query.filter?.email && !user.email.equals(query.filter.email)) {
|
||||
return false;
|
||||
}
|
||||
if (query.filter?.search) {
|
||||
const search = query.filter.search.toLowerCase();
|
||||
const matchesEmail = user.email.value.toLowerCase().includes(search);
|
||||
const matchesDisplayName = user.displayName.toLowerCase().includes(search);
|
||||
if (!matchesEmail && !matchesDisplayName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const total = users.length;
|
||||
|
||||
// Apply sorting
|
||||
if (query?.sort) {
|
||||
const { field, direction } = query.sort;
|
||||
users.sort((a, b) => {
|
||||
let aVal: string | number | Date;
|
||||
let bVal: string | number | Date;
|
||||
|
||||
switch (field) {
|
||||
case 'email':
|
||||
aVal = a.email.value;
|
||||
bVal = b.email.value;
|
||||
break;
|
||||
case 'displayName':
|
||||
aVal = a.displayName;
|
||||
bVal = b.displayName;
|
||||
break;
|
||||
case 'createdAt':
|
||||
aVal = a.createdAt;
|
||||
bVal = b.createdAt;
|
||||
break;
|
||||
case 'lastLoginAt':
|
||||
aVal = a.lastLoginAt || 0;
|
||||
bVal = b.lastLoginAt || 0;
|
||||
break;
|
||||
case 'status':
|
||||
aVal = a.status.value;
|
||||
bVal = b.status.value;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (aVal < bVal) return direction === 'asc' ? -1 : 1;
|
||||
if (aVal > bVal) return direction === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
const page = query?.pagination?.page || 1;
|
||||
const limit = query?.pagination?.limit || 10;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
const paginatedUsers = users.slice(start, end);
|
||||
|
||||
return {
|
||||
users: paginatedUsers,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages,
|
||||
};
|
||||
}
|
||||
|
||||
async count(filter?: UserFilter): Promise<number> {
|
||||
let count = 0;
|
||||
|
||||
for (const stored of this.storage.values()) {
|
||||
const user = this.fromStored(stored);
|
||||
|
||||
if (filter?.role && !user.roles.some(r => r.equals(filter.role!))) {
|
||||
continue;
|
||||
}
|
||||
if (filter?.status && !user.status.equals(filter.status)) {
|
||||
continue;
|
||||
}
|
||||
if (filter?.email && !user.email.equals(filter.email)) {
|
||||
continue;
|
||||
}
|
||||
if (filter?.search) {
|
||||
const search = filter.search.toLowerCase();
|
||||
const matchesEmail = user.email.value.toLowerCase().includes(search);
|
||||
const matchesDisplayName = user.displayName.toLowerCase().includes(search);
|
||||
if (!matchesEmail && !matchesDisplayName) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
async create(user: AdminUser): Promise<AdminUser> {
|
||||
// Check for duplicate email
|
||||
if (await this.emailExists(user.email)) {
|
||||
throw new Error('Email already exists');
|
||||
}
|
||||
|
||||
// Check for duplicate ID
|
||||
if (this.storage.has(user.id.value)) {
|
||||
throw new Error('User ID already exists');
|
||||
}
|
||||
|
||||
const stored = this.toStored(user);
|
||||
this.storage.set(user.id.value, stored);
|
||||
return user;
|
||||
}
|
||||
|
||||
async update(user: AdminUser): Promise<AdminUser> {
|
||||
// Check if user exists
|
||||
if (!this.storage.has(user.id.value)) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Check for duplicate email (excluding current user)
|
||||
for (const [id, stored] of this.storage.entries()) {
|
||||
if (id !== user.id.value && stored.email === user.email.value) {
|
||||
throw new Error('Email already exists');
|
||||
}
|
||||
}
|
||||
|
||||
const stored = this.toStored(user);
|
||||
this.storage.set(user.id.value, stored);
|
||||
return user;
|
||||
}
|
||||
|
||||
async delete(id: UserId): Promise<void> {
|
||||
if (!this.storage.has(id.value)) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
this.storage.delete(id.value);
|
||||
}
|
||||
|
||||
toStored(user: AdminUser): StoredAdminUser {
|
||||
const stored: StoredAdminUser = {
|
||||
id: user.id.value,
|
||||
email: user.email.value,
|
||||
roles: user.roles.map(r => r.value),
|
||||
status: user.status.value,
|
||||
displayName: user.displayName,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt,
|
||||
};
|
||||
|
||||
if (user.lastLoginAt !== undefined) {
|
||||
stored.lastLoginAt = user.lastLoginAt;
|
||||
}
|
||||
|
||||
if (user.primaryDriverId !== undefined) {
|
||||
stored.primaryDriverId = user.primaryDriverId;
|
||||
}
|
||||
|
||||
return stored;
|
||||
}
|
||||
|
||||
fromStored(stored: StoredAdminUser): AdminUser {
|
||||
const props: {
|
||||
id: string;
|
||||
email: string;
|
||||
roles: string[];
|
||||
status: string;
|
||||
displayName: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
lastLoginAt?: Date;
|
||||
primaryDriverId?: string;
|
||||
} = {
|
||||
id: stored.id,
|
||||
email: stored.email,
|
||||
roles: stored.roles,
|
||||
status: stored.status,
|
||||
displayName: stored.displayName,
|
||||
createdAt: stored.createdAt,
|
||||
updatedAt: stored.updatedAt,
|
||||
};
|
||||
|
||||
if (stored.lastLoginAt !== undefined) {
|
||||
props.lastLoginAt = stored.lastLoginAt;
|
||||
}
|
||||
|
||||
if (stored.primaryDriverId !== undefined) {
|
||||
props.primaryDriverId = stored.primaryDriverId;
|
||||
}
|
||||
|
||||
return AdminUser.rehydrate(props);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user