core tests

This commit is contained in:
2026-01-24 02:05:43 +01:00
parent 78c9c1ec75
commit 3bef15f3bd
13 changed files with 379 additions and 267 deletions

View File

@@ -0,0 +1,790 @@
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);
});
});
});
});

View File

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

View File

@@ -0,0 +1,610 @@
import { describe, expect, it } from 'vitest';
import { AdminUserOrmEntity } from './AdminUserOrmEntity';
describe('AdminUserOrmEntity', () => {
describe('TDD - Test First', () => {
describe('entity properties', () => {
it('should have id property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('id');
});
it('should have email property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('email');
});
it('should have displayName property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('displayName');
});
it('should have roles property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('roles');
});
it('should have status property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('status');
});
it('should have primaryDriverId property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('primaryDriverId');
});
it('should have lastLoginAt property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('lastLoginAt');
});
it('should have createdAt property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('createdAt');
});
it('should have updatedAt property', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity).toHaveProperty('updatedAt');
});
});
describe('property types', () => {
it('should have id as string', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'test-id';
// Act & Assert
expect(typeof entity.id).toBe('string');
expect(entity.id).toBe('test-id');
});
it('should have email as string', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.email = 'test@example.com';
// Act & Assert
expect(typeof entity.email).toBe('string');
expect(entity.email).toBe('test@example.com');
});
it('should have displayName as string', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.displayName = 'Test User';
// Act & Assert
expect(typeof entity.displayName).toBe('string');
expect(entity.displayName).toBe('Test User');
});
it('should have roles as string array', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.roles = ['admin', 'user'];
// Act & Assert
expect(Array.isArray(entity.roles)).toBe(true);
expect(entity.roles).toEqual(['admin', 'user']);
});
it('should have status as string', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.status = 'active';
// Act & Assert
expect(typeof entity.status).toBe('string');
expect(entity.status).toBe('active');
});
it('should have primaryDriverId as optional string', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity.primaryDriverId).toBeUndefined();
entity.primaryDriverId = 'driver-123';
expect(typeof entity.primaryDriverId).toBe('string');
expect(entity.primaryDriverId).toBe('driver-123');
});
it('should have lastLoginAt as optional Date', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
expect(entity.lastLoginAt).toBeUndefined();
const now = new Date();
entity.lastLoginAt = now;
expect(entity.lastLoginAt).toBeInstanceOf(Date);
expect(entity.lastLoginAt).toBe(now);
});
it('should have createdAt as Date', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
entity.createdAt = now;
// Act & Assert
expect(entity.createdAt).toBeInstanceOf(Date);
expect(entity.createdAt).toBe(now);
});
it('should have updatedAt as Date', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
entity.updatedAt = now;
// Act & Assert
expect(entity.updatedAt).toBeInstanceOf(Date);
expect(entity.updatedAt).toBe(now);
});
});
describe('property values', () => {
it('should handle valid UUID for id', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const uuid = '123e4567-e89b-12d3-a456-426614174000';
// Act
entity.id = uuid;
// Assert
expect(entity.id).toBe(uuid);
});
it('should handle email with special characters', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const email = 'user+tag@example-domain.com';
// Act
entity.email = email;
// Assert
expect(entity.email).toBe(email);
});
it('should handle display name with spaces', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const displayName = 'John Doe Smith';
// Act
entity.displayName = displayName;
// Assert
expect(entity.displayName).toBe(displayName);
});
it('should handle roles with multiple entries', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const roles = ['owner', 'admin', 'user', 'moderator'];
// Act
entity.roles = roles;
// Assert
expect(entity.roles).toEqual(roles);
expect(entity.roles).toHaveLength(4);
});
it('should handle status with different values', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act & Assert
entity.status = 'active';
expect(entity.status).toBe('active');
entity.status = 'suspended';
expect(entity.status).toBe('suspended');
entity.status = 'deleted';
expect(entity.status).toBe('deleted');
});
it('should handle primaryDriverId with valid driver ID', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const driverId = 'driver-abc123';
// Act
entity.primaryDriverId = driverId;
// Assert
expect(entity.primaryDriverId).toBe(driverId);
});
it('should handle lastLoginAt with current date', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.lastLoginAt = now;
// Assert
expect(entity.lastLoginAt).toBe(now);
});
it('should handle createdAt with specific date', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const specificDate = new Date('2024-01-01T00:00:00.000Z');
// Act
entity.createdAt = specificDate;
// Assert
expect(entity.createdAt).toBe(specificDate);
});
it('should handle updatedAt with specific date', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const specificDate = new Date('2024-01-02T00:00:00.000Z');
// Act
entity.updatedAt = specificDate;
// Assert
expect(entity.updatedAt).toBe(specificDate);
});
});
describe('property assignments', () => {
it('should allow setting all properties', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['admin'];
entity.status = 'active';
entity.primaryDriverId = 'driver-456';
entity.lastLoginAt = now;
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.id).toBe('user-123');
expect(entity.email).toBe('test@example.com');
expect(entity.displayName).toBe('Test User');
expect(entity.roles).toEqual(['admin']);
expect(entity.status).toBe('active');
expect(entity.primaryDriverId).toBe('driver-456');
expect(entity.lastLoginAt).toBe(now);
expect(entity.createdAt).toBe(now);
expect(entity.updatedAt).toBe(now);
});
it('should allow updating properties', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
const later = new Date(now.getTime() + 1000);
// Act
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.primaryDriverId = 'driver-456';
entity.lastLoginAt = now;
entity.createdAt = now;
entity.updatedAt = now;
// Update
entity.displayName = 'Updated Name';
entity.roles = ['admin', 'user'];
entity.status = 'suspended';
entity.lastLoginAt = later;
entity.updatedAt = later;
// Assert
expect(entity.displayName).toBe('Updated Name');
expect(entity.roles).toEqual(['admin', 'user']);
expect(entity.status).toBe('suspended');
expect(entity.lastLoginAt).toBe(later);
expect(entity.updatedAt).toBe(later);
});
it('should allow clearing optional properties', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.primaryDriverId = 'driver-123';
entity.lastLoginAt = now;
// Clear
entity.primaryDriverId = undefined;
entity.lastLoginAt = undefined;
// Assert
expect(entity.primaryDriverId).toBeUndefined();
expect(entity.lastLoginAt).toBeUndefined();
});
});
describe('empty entity', () => {
it('should create entity with undefined properties', () => {
// Arrange & Act
const entity = new AdminUserOrmEntity();
// Assert
expect(entity.id).toBeUndefined();
expect(entity.email).toBeUndefined();
expect(entity.displayName).toBeUndefined();
expect(entity.roles).toBeUndefined();
expect(entity.status).toBeUndefined();
expect(entity.primaryDriverId).toBeUndefined();
expect(entity.lastLoginAt).toBeUndefined();
expect(entity.createdAt).toBeUndefined();
expect(entity.updatedAt).toBeUndefined();
});
it('should allow partial initialization', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act
entity.id = 'user-123';
entity.email = 'test@example.com';
// Assert
expect(entity.id).toBe('user-123');
expect(entity.email).toBe('test@example.com');
expect(entity.displayName).toBeUndefined();
expect(entity.roles).toBeUndefined();
});
});
describe('real-world scenarios', () => {
it('should handle complete user entity', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.id = '123e4567-e89b-12d3-a456-426614174000';
entity.email = 'admin@example.com';
entity.displayName = 'Administrator';
entity.roles = ['owner', 'admin'];
entity.status = 'active';
entity.primaryDriverId = 'driver-789';
entity.lastLoginAt = now;
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.id).toBe('123e4567-e89b-12d3-a456-426614174000');
expect(entity.email).toBe('admin@example.com');
expect(entity.displayName).toBe('Administrator');
expect(entity.roles).toEqual(['owner', 'admin']);
expect(entity.status).toBe('active');
expect(entity.primaryDriverId).toBe('driver-789');
expect(entity.lastLoginAt).toBe(now);
expect(entity.createdAt).toBe(now);
expect(entity.updatedAt).toBe(now);
});
it('should handle user without primary driver', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.id = 'user-456';
entity.email = 'user@example.com';
entity.displayName = 'Regular User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.primaryDriverId).toBeUndefined();
expect(entity.lastLoginAt).toBeUndefined();
});
it('should handle suspended user', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.id = 'user-789';
entity.email = 'suspended@example.com';
entity.displayName = 'Suspended User';
entity.roles = ['user'];
entity.status = 'suspended';
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.status).toBe('suspended');
});
it('should handle user with many roles', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
// Act
entity.id = 'user-999';
entity.email = 'multi@example.com';
entity.displayName = 'Multi Role User';
entity.roles = ['owner', 'admin', 'user', 'moderator', 'viewer'];
entity.status = 'active';
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.roles).toHaveLength(5);
expect(entity.roles).toContain('owner');
expect(entity.roles).toContain('admin');
expect(entity.roles).toContain('user');
expect(entity.roles).toContain('moderator');
expect(entity.roles).toContain('viewer');
});
it('should handle user with recent login', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
const recentLogin = new Date(now.getTime() - 60000); // 1 minute ago
// Act
entity.id = 'user-111';
entity.email = 'active@example.com';
entity.displayName = 'Active User';
entity.roles = ['user'];
entity.status = 'active';
entity.primaryDriverId = 'driver-222';
entity.lastLoginAt = recentLogin;
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.lastLoginAt).toBe(recentLogin);
expect(entity.lastLoginAt!.getTime()).toBeLessThan(now.getTime());
});
it('should handle user with old login', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const now = new Date();
const oldLogin = new Date(now.getTime() - 86400000); // 1 day ago
// Act
entity.id = 'user-333';
entity.email = 'old@example.com';
entity.displayName = 'Old Login User';
entity.roles = ['user'];
entity.status = 'active';
entity.lastLoginAt = oldLogin;
entity.createdAt = now;
entity.updatedAt = now;
// Assert
expect(entity.lastLoginAt).toBe(oldLogin);
expect(entity.lastLoginAt!.getTime()).toBeLessThan(now.getTime());
});
});
describe('edge cases', () => {
it('should handle empty string values', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act
entity.id = '';
entity.email = '';
entity.displayName = '';
entity.status = '';
// Assert
expect(entity.id).toBe('');
expect(entity.email).toBe('');
expect(entity.displayName).toBe('');
expect(entity.status).toBe('');
});
it('should handle empty roles array', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act
entity.roles = [];
// Assert
expect(entity.roles).toEqual([]);
expect(entity.roles).toHaveLength(0);
});
it('should handle null values for optional properties', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act
entity.primaryDriverId = null as any;
entity.lastLoginAt = null as any;
// Assert
expect(entity.primaryDriverId).toBeNull();
expect(entity.lastLoginAt).toBeNull();
});
it('should handle very long strings', () => {
// Arrange
const entity = new AdminUserOrmEntity();
const longString = 'a'.repeat(1000);
// Act
entity.email = `${longString}@example.com`;
entity.displayName = longString;
// Assert
expect(entity.email).toBe(`${longString}@example.com`);
expect(entity.displayName).toBe(longString);
});
it('should handle unicode characters', () => {
// Arrange
const entity = new AdminUserOrmEntity();
// Act
entity.email = '用户@例子.测试';
entity.displayName = '用户 例子';
// Assert
expect(entity.email).toBe('用户@例子.测试');
expect(entity.displayName).toBe('用户 例子');
});
});
});
});

View File

@@ -0,0 +1,32 @@
import { Column, CreateDateColumn, Entity, Index, PrimaryColumn, UpdateDateColumn } from 'typeorm';
@Entity({ name: 'admin_users' })
export class AdminUserOrmEntity {
@PrimaryColumn({ type: 'uuid' })
id!: string;
@Index()
@Column({ type: 'text' })
email!: string;
@Column({ type: 'text' })
displayName!: string;
@Column({ type: 'simple-json' })
roles!: string[];
@Column({ type: 'text' })
status!: string;
@Column({ type: 'text', nullable: true })
primaryDriverId?: string;
@Column({ type: (process.env.NODE_ENV === 'test' && process.env.GRIDPILOT_API_PERSISTENCE !== 'postgres') ? 'datetime' : 'timestamptz', nullable: true })
lastLoginAt?: Date;
@CreateDateColumn({ type: (process.env.NODE_ENV === 'test' && process.env.GRIDPILOT_API_PERSISTENCE !== 'postgres') ? 'datetime' : 'timestamptz' })
createdAt!: Date;
@UpdateDateColumn({ type: (process.env.NODE_ENV === 'test' && process.env.GRIDPILOT_API_PERSISTENCE !== 'postgres') ? 'datetime' : 'timestamptz' })
updatedAt!: Date;
}

View File

@@ -0,0 +1,521 @@
import { describe, expect, it } from 'vitest';
import { TypeOrmAdminSchemaError } from './TypeOrmAdminSchemaError';
describe('TypeOrmAdminSchemaError', () => {
describe('TDD - Test First', () => {
describe('constructor', () => {
it('should create an error with all required details', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid format',
message: 'Email must be a valid email address',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.name).toBe('TypeOrmAdminSchemaError');
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Invalid format - Email must be a valid email address');
});
it('should create an error with minimal details', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'id',
reason: 'Missing',
message: 'ID field is required',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.id: Missing - ID field is required');
});
it('should create an error with complex entity name', () => {
// Arrange
const details = {
entityName: 'AdminUserOrmEntity',
fieldName: 'roles',
reason: 'Type mismatch',
message: 'Expected simple-json but got text',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUserOrmEntity.roles: Type mismatch - Expected simple-json but got text');
});
it('should create an error with long field name', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'veryLongFieldNameThatExceedsNormalLength',
reason: 'Constraint violation',
message: 'Field length exceeds maximum allowed',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.veryLongFieldNameThatExceedsNormalLength: Constraint violation - Field length exceeds maximum allowed');
});
it('should create an error with special characters in message', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Validation failed',
message: 'Email "test@example.com" contains invalid characters: @, ., com',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Validation failed - Email "test@example.com" contains invalid characters: @, ., com');
});
it('should create an error with empty reason', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: '',
message: 'Email is required',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: - Email is required');
});
it('should create an error with empty message', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: '',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Invalid - ');
});
it('should create an error with empty reason and message', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: '',
message: '',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: - ');
});
});
describe('error properties', () => {
it('should have correct error name', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.name).toBe('TypeOrmAdminSchemaError');
});
it('should be instance of Error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error instanceof Error).toBe(true);
expect(error instanceof TypeOrmAdminSchemaError).toBe(true);
});
it('should have a stack trace', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.stack).toBeDefined();
expect(typeof error.stack).toBe('string');
expect(error.stack).toContain('TypeOrmAdminSchemaError');
});
it('should preserve details object reference', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toBe(details);
});
it('should allow modification of details after creation', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
const error = new TypeOrmAdminSchemaError(details);
// Act
error.details.reason = 'Updated reason';
// Assert
expect(error.details.reason).toBe('Updated reason');
expect(error.message).toContain('Updated reason');
});
});
describe('message formatting', () => {
it('should format message with all parts', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Validation failed',
message: 'Email must be a valid email address',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Validation failed - Email must be a valid email address');
});
it('should handle multiple words in entity name', () => {
// Arrange
const details = {
entityName: 'Admin User Entity',
fieldName: 'email',
reason: 'Invalid',
message: 'Test',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.message).toBe('[TypeOrmAdminSchemaError] Admin User Entity.email: Invalid - Test');
});
it('should handle multiple words in field name', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email address',
reason: 'Invalid',
message: 'Test',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email address: Invalid - Test');
});
it('should handle multiple words in reason', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Validation failed completely',
message: 'Test',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Validation failed completely - Test');
});
it('should handle multiple words in message', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'This is a very long error message that contains many words',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Invalid - This is a very long error message that contains many words');
});
it('should handle special characters in all parts', () => {
// Arrange
const details = {
entityName: 'Admin_User-Entity',
fieldName: 'email@address',
reason: 'Validation failed: @, ., com',
message: 'Email "test@example.com" is invalid',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.message).toBe('[TypeOrmAdminSchemaError] Admin_User-Entity.email@address: Validation failed: @, ., com - Email "test@example.com" is invalid');
});
});
describe('error inheritance', () => {
it('should be instance of Error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error instanceof Error).toBe(true);
});
it('should be instance of TypeOrmAdminSchemaError', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error instanceof TypeOrmAdminSchemaError).toBe(true);
});
it('should not be instance of other error types', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Invalid',
message: 'Test error',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error instanceof TypeError).toBe(false);
expect(error instanceof RangeError).toBe(false);
expect(error instanceof ReferenceError).toBe(false);
});
});
describe('real-world scenarios', () => {
it('should handle missing column error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'primaryDriverId',
reason: 'Column not found',
message: 'Column "primary_driver_id" does not exist in table "admin_users"',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.primaryDriverId: Column not found - Column "primary_driver_id" does not exist in table "admin_users"');
});
it('should handle type mismatch error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'roles',
reason: 'Type mismatch',
message: 'Expected type "simple-json" but got "text" for column "roles"',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.roles: Type mismatch - Expected type "simple-json" but got "text" for column "roles"');
});
it('should handle constraint violation error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Constraint violation',
message: 'UNIQUE constraint failed: admin_users.email',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Constraint violation - UNIQUE constraint failed: admin_users.email');
});
it('should handle nullable constraint error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'displayName',
reason: 'Constraint violation',
message: 'NOT NULL constraint failed: admin_users.display_name',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.displayName: Constraint violation - NOT NULL constraint failed: admin_users.display_name');
});
it('should handle foreign key constraint error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'primaryDriverId',
reason: 'Constraint violation',
message: 'FOREIGN KEY constraint failed: admin_users.primary_driver_id references drivers.id',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.primaryDriverId: Constraint violation - FOREIGN KEY constraint failed: admin_users.primary_driver_id references drivers.id');
});
it('should handle index creation error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'email',
reason: 'Index creation failed',
message: 'Failed to create unique index on column "email"',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.email: Index creation failed - Failed to create unique index on column "email"');
});
it('should handle default value error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'status',
reason: 'Default value error',
message: 'Default value "active" is not valid for column "status"',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.status: Default value error - Default value "active" is not valid for column "status"');
});
it('should handle timestamp column error', () => {
// Arrange
const details = {
entityName: 'AdminUser',
fieldName: 'createdAt',
reason: 'Type error',
message: 'Column "created_at" has invalid type "datetime" for PostgreSQL',
};
// Act
const error = new TypeOrmAdminSchemaError(details);
// Assert
expect(error.details).toEqual(details);
expect(error.message).toBe('[TypeOrmAdminSchemaError] AdminUser.createdAt: Type error - Column "created_at" has invalid type "datetime" for PostgreSQL');
});
});
});
});

View File

@@ -0,0 +1,13 @@
export class TypeOrmAdminSchemaError extends Error {
constructor(
public details: {
entityName: string;
fieldName: string;
reason: string;
message: string;
},
) {
super(`[TypeOrmAdminSchemaError] ${details.entityName}.${details.fieldName}: ${details.reason} - ${details.message}`);
this.name = 'TypeOrmAdminSchemaError';
}
}

View File

@@ -0,0 +1,408 @@
import { describe, expect, it } from 'vitest';
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
import { AdminUserOrmEntity } from '../entities/AdminUserOrmEntity';
import { AdminUserOrmMapper } from './AdminUserOrmMapper';
import { TypeOrmAdminSchemaError } from '../errors/TypeOrmAdminSchemaError';
describe('AdminUserOrmMapper', () => {
describe('TDD - Test First', () => {
describe('toDomain', () => {
it('should map valid ORM entity to domain entity', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['owner'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act
const domain = mapper.toDomain(entity);
// Assert
expect(domain.id.value).toBe('user-123');
expect(domain.email.value).toBe('test@example.com');
expect(domain.displayName).toBe('Test User');
expect(domain.roles).toHaveLength(1);
expect(domain.roles[0]!.value).toBe('owner');
expect(domain.status.value).toBe('active');
expect(domain.createdAt).toEqual(new Date('2024-01-01'));
expect(domain.updatedAt).toEqual(new Date('2024-01-02'));
});
it('should map entity with optional fields', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
entity.primaryDriverId = 'driver-456';
entity.lastLoginAt = new Date('2024-01-03');
const mapper = new AdminUserOrmMapper();
// Act
const domain = mapper.toDomain(entity);
// Assert
expect(domain.primaryDriverId).toBe('driver-456');
expect(domain.lastLoginAt).toEqual(new Date('2024-01-03'));
});
it('should handle null optional fields', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
entity.primaryDriverId = null;
entity.lastLoginAt = null;
const mapper = new AdminUserOrmMapper();
// Act
const domain = mapper.toDomain(entity);
// Assert
expect(domain.primaryDriverId).toBeUndefined();
expect(domain.lastLoginAt).toBeUndefined();
});
it('should throw error for missing id', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = '';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field id must be a non-empty string');
});
it('should throw error for missing email', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = '';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field email must be a non-empty string');
});
it('should throw error for missing displayName', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = '';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field displayName must be a non-empty string');
});
it('should throw error for invalid roles array', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = null as unknown as string[];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field roles must be an array of strings');
});
it('should throw error for invalid roles array items', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user', 123 as unknown as string];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field roles must be an array of strings');
});
it('should throw error for missing status', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = '';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field status must be a non-empty string');
});
it('should throw error for invalid createdAt', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('invalid') as unknown as Date;
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field createdAt must be a valid Date');
});
it('should throw error for invalid updatedAt', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('invalid') as unknown as Date;
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field updatedAt must be a valid Date');
});
it('should throw error for invalid primaryDriverId type', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
entity.primaryDriverId = 123 as unknown as string;
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field primaryDriverId must be a string or undefined');
});
it('should throw error for invalid lastLoginAt type', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['user'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
entity.lastLoginAt = 'invalid' as unknown as Date;
const mapper = new AdminUserOrmMapper();
// Act & Assert
expect(() => mapper.toDomain(entity)).toThrow(TypeOrmAdminSchemaError);
expect(() => mapper.toDomain(entity)).toThrow('Field lastLoginAt must be a valid Date');
});
it('should handle multiple roles', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['owner', 'admin'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act
const domain = mapper.toDomain(entity);
// Assert
expect(domain.roles).toHaveLength(2);
expect(domain.roles.map(r => r.value)).toContain('owner');
expect(domain.roles.map(r => r.value)).toContain('admin');
});
});
describe('toOrmEntity', () => {
it('should map domain entity to ORM entity', () => {
// Arrange
const domain = AdminUser.create({
id: 'user-123',
email: 'test@example.com',
displayName: 'Test User',
roles: ['owner'],
status: 'active',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-02'),
});
const mapper = new AdminUserOrmMapper();
// Act
const entity = mapper.toOrmEntity(domain);
// Assert
expect(entity.id).toBe('user-123');
expect(entity.email).toBe('test@example.com');
expect(entity.displayName).toBe('Test User');
expect(entity.roles).toEqual(['owner']);
expect(entity.status).toBe('active');
expect(entity.createdAt).toEqual(new Date('2024-01-01'));
expect(entity.updatedAt).toEqual(new Date('2024-01-02'));
});
it('should map domain entity with optional fields', () => {
// Arrange
const domain = AdminUser.create({
id: 'user-123',
email: 'test@example.com',
displayName: 'Test User',
roles: ['user'],
status: 'active',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-02'),
primaryDriverId: 'driver-456',
lastLoginAt: new Date('2024-01-03'),
});
const mapper = new AdminUserOrmMapper();
// Act
const entity = mapper.toOrmEntity(domain);
// Assert
expect(entity.primaryDriverId).toBe('driver-456');
expect(entity.lastLoginAt).toEqual(new Date('2024-01-03'));
});
it('should handle domain entity without optional fields', () => {
// Arrange
const domain = AdminUser.create({
id: 'user-123',
email: 'test@example.com',
displayName: 'Test User',
roles: ['user'],
status: 'active',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-02'),
});
const mapper = new AdminUserOrmMapper();
// Act
const entity = mapper.toOrmEntity(domain);
// Assert
expect(entity.primaryDriverId).toBeUndefined();
expect(entity.lastLoginAt).toBeUndefined();
});
it('should map domain entity with multiple roles', () => {
// Arrange
const domain = AdminUser.create({
id: 'user-123',
email: 'test@example.com',
displayName: 'Test User',
roles: ['owner', 'admin'],
status: 'active',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-02'),
});
const mapper = new AdminUserOrmMapper();
// Act
const entity = mapper.toOrmEntity(domain);
// Assert
expect(entity.roles).toEqual(['owner', 'admin']);
});
});
describe('toStored', () => {
it('should call toDomain for stored entity', () => {
// Arrange
const entity = new AdminUserOrmEntity();
entity.id = 'user-123';
entity.email = 'test@example.com';
entity.displayName = 'Test User';
entity.roles = ['owner'];
entity.status = 'active';
entity.createdAt = new Date('2024-01-01');
entity.updatedAt = new Date('2024-01-02');
const mapper = new AdminUserOrmMapper();
// Act
const domain = mapper.toStored(entity);
// Assert
expect(domain.id.value).toBe('user-123');
expect(domain.email.value).toBe('test@example.com');
expect(domain.displayName).toBe('Test User');
});
});
});
});

View File

@@ -0,0 +1,95 @@
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
import { AdminUserOrmEntity } from '../entities/AdminUserOrmEntity';
import { TypeOrmAdminSchemaError } from '../errors/TypeOrmAdminSchemaError';
import {
assertDate,
assertNonEmptyString,
assertOptionalDate,
assertOptionalString,
assertStringArray,
} from '../schema/TypeOrmAdminSchemaGuards';
export class AdminUserOrmMapper {
toDomain(entity: AdminUserOrmEntity): AdminUser {
const entityName = 'AdminUser';
try {
assertNonEmptyString(entityName, 'id', entity.id);
assertNonEmptyString(entityName, 'email', entity.email);
assertNonEmptyString(entityName, 'displayName', entity.displayName);
assertStringArray(entityName, 'roles', entity.roles);
assertNonEmptyString(entityName, 'status', entity.status);
assertOptionalString(entityName, 'primaryDriverId', entity.primaryDriverId);
assertOptionalDate(entityName, 'lastLoginAt', entity.lastLoginAt);
assertDate(entityName, 'createdAt', entity.createdAt);
assertDate(entityName, 'updatedAt', entity.updatedAt);
} catch (error) {
if (error instanceof TypeOrmAdminSchemaError) {
throw error;
}
const message = error instanceof Error ? error.message : 'Invalid persisted AdminUser';
throw new TypeOrmAdminSchemaError({ entityName, fieldName: 'unknown', reason: 'invalid_shape', message });
}
try {
const domainProps: {
id: string;
email: string;
displayName: string;
roles: string[];
status: string;
createdAt: Date;
updatedAt: Date;
primaryDriverId?: string;
lastLoginAt?: Date;
} = {
id: entity.id,
email: entity.email,
displayName: entity.displayName,
roles: entity.roles,
status: entity.status,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
};
if (entity.primaryDriverId !== null && entity.primaryDriverId !== undefined) {
domainProps.primaryDriverId = entity.primaryDriverId;
}
if (entity.lastLoginAt !== null && entity.lastLoginAt !== undefined) {
domainProps.lastLoginAt = entity.lastLoginAt;
}
return AdminUser.create(domainProps);
} catch (error) {
const message = error instanceof Error ? error.message : 'Invalid persisted AdminUser';
throw new TypeOrmAdminSchemaError({ entityName, fieldName: 'unknown', reason: 'invalid_shape', message });
}
}
toOrmEntity(adminUser: AdminUser): AdminUserOrmEntity {
const entity = new AdminUserOrmEntity();
entity.id = adminUser.id.value;
entity.email = adminUser.email.value;
entity.displayName = adminUser.displayName;
entity.roles = adminUser.roles.map(r => r.value);
entity.status = adminUser.status.value;
entity.createdAt = adminUser.createdAt;
entity.updatedAt = adminUser.updatedAt;
if (adminUser.primaryDriverId !== undefined) {
entity.primaryDriverId = adminUser.primaryDriverId;
}
if (adminUser.lastLoginAt !== undefined) {
entity.lastLoginAt = adminUser.lastLoginAt;
}
return entity;
}
toStored(entity: AdminUserOrmEntity): AdminUser {
return this.toDomain(entity);
}
}

View File

@@ -0,0 +1,188 @@
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
import type { 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';
import type { DataSource, Repository } from 'typeorm';
import { AdminUserOrmEntity } from '../entities/AdminUserOrmEntity';
import { AdminUserOrmMapper } from '../mappers/AdminUserOrmMapper';
export class TypeOrmAdminUserRepository implements AdminUserRepository {
private readonly repository: Repository<AdminUserOrmEntity>;
private readonly mapper: AdminUserOrmMapper;
constructor(
dataSource: DataSource,
mapper?: AdminUserOrmMapper,
) {
this.repository = dataSource.getRepository(AdminUserOrmEntity);
this.mapper = mapper ?? new AdminUserOrmMapper();
}
async save(user: AdminUser): Promise<void> {
const entity = this.mapper.toOrmEntity(user);
await this.repository.save(entity);
}
async findById(id: UserId): Promise<AdminUser | null> {
const entity = await this.repository.findOne({ where: { id: id.value } });
return entity ? this.mapper.toDomain(entity) : null;
}
async findByEmail(email: Email): Promise<AdminUser | null> {
const entity = await this.repository.findOne({ where: { email: email.value } });
return entity ? this.mapper.toDomain(entity) : null;
}
async emailExists(email: Email): Promise<boolean> {
const count = await this.repository.count({ where: { email: email.value } });
return count > 0;
}
async existsById(id: UserId): Promise<boolean> {
const count = await this.repository.count({ where: { id: id.value } });
return count > 0;
}
async existsByEmail(email: Email): Promise<boolean> {
return this.emailExists(email);
}
async list(query?: UserListQuery): Promise<UserListResult> {
const page = query?.pagination?.page ?? 1;
const limit = query?.pagination?.limit ?? 10;
const skip = (page - 1) * limit;
const sortBy = query?.sort?.field ?? 'createdAt';
const sortOrder = query?.sort?.direction ?? 'desc';
const queryBuilder = this.repository.createQueryBuilder('adminUser');
if (query?.filter?.role) {
// SQLite doesn't support ANY, use LIKE for JSON array search
queryBuilder.andWhere('adminUser.roles LIKE :rolePattern', {
rolePattern: `%${query.filter.role.value}%`
});
}
if (query?.filter?.status) {
queryBuilder.andWhere('adminUser.status = :status', { status: query.filter.status.value });
}
if (query?.filter?.search) {
const searchParam = `%${query.filter.search}%`;
queryBuilder.andWhere(
'(adminUser.email LIKE :search OR adminUser.displayName LIKE :search)',
{ search: searchParam }
);
}
queryBuilder.skip(skip).take(limit);
queryBuilder.orderBy(`adminUser.${sortBy}`, sortOrder.toUpperCase() as 'ASC' | 'DESC');
const [entities, total] = await queryBuilder.getManyAndCount();
const users = entities.map(entity => this.mapper.toDomain(entity));
return {
users,
total,
page,
limit,
totalPages: Math.ceil(total / limit),
};
}
async count(filter?: UserFilter): Promise<number> {
const queryBuilder = this.repository.createQueryBuilder('adminUser');
if (filter?.role) {
// SQLite doesn't support ANY, use LIKE for JSON array search
queryBuilder.andWhere('adminUser.roles LIKE :rolePattern', {
rolePattern: `%${filter.role.value}%`
});
}
if (filter?.status) {
queryBuilder.andWhere('adminUser.status = :status', { status: filter.status.value });
}
if (filter?.search) {
const searchParam = `%${filter.search}%`;
queryBuilder.andWhere(
'(adminUser.email LIKE :search OR adminUser.displayName LIKE :search)',
{ search: searchParam }
);
}
return await queryBuilder.getCount();
}
async create(user: AdminUser): Promise<AdminUser> {
const entity = this.mapper.toOrmEntity(user);
const saved = await this.repository.save(entity);
return this.mapper.toDomain(saved);
}
async update(user: AdminUser): Promise<AdminUser> {
const entity = this.mapper.toOrmEntity(user);
const updated = await this.repository.save(entity);
return this.mapper.toDomain(updated);
}
async delete(id: UserId): Promise<void> {
await this.repository.delete({ id: 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);
}
}

View File

@@ -0,0 +1,365 @@
import { describe, expect, it } from 'vitest';
import { TypeOrmAdminSchemaError } from '../errors/TypeOrmAdminSchemaError';
import {
assertNonEmptyString,
assertStringArray,
assertDate,
assertOptionalDate,
assertOptionalString,
} from './TypeOrmAdminSchemaGuards';
describe('TypeOrmAdminSchemaGuards', () => {
describe('TDD - Test First', () => {
describe('assertNonEmptyString', () => {
it('should pass for valid non-empty string', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'email';
const value = 'test@example.com';
// Act & Assert
expect(() => assertNonEmptyString(entityName, fieldName, value)).not.toThrow();
});
it('should throw error for empty string', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'email';
const value = '';
// Act & Assert
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow('Field email must be a non-empty string');
});
it('should throw error for string with only spaces', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'email';
const value = ' ';
// Act & Assert
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow('Field email must be a non-empty string');
});
it('should throw error for non-string value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'email';
const value = 123;
// Act & Assert
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow('Field email must be a non-empty string');
});
it('should throw error for null value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'email';
const value = null;
// Act & Assert
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow('Field email must be a non-empty string');
});
it('should throw error for undefined value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'email';
const value = undefined;
// Act & Assert
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertNonEmptyString(entityName, fieldName, value)).toThrow('Field email must be a non-empty string');
});
});
describe('assertStringArray', () => {
it('should pass for valid string array', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'roles';
const value = ['admin', 'user'];
// Act & Assert
expect(() => assertStringArray(entityName, fieldName, value)).not.toThrow();
});
it('should pass for empty array', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'roles';
const value = [];
// Act & Assert
expect(() => assertStringArray(entityName, fieldName, value)).not.toThrow();
});
it('should throw error for non-array value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'roles';
const value = 'admin';
// Act & Assert
expect(() => assertStringArray(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertStringArray(entityName, fieldName, value)).toThrow('Field roles must be an array of strings');
});
it('should throw error for array with non-string items', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'roles';
const value = ['admin', 123];
// Act & Assert
expect(() => assertStringArray(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertStringArray(entityName, fieldName, value)).toThrow('Field roles must be an array of strings');
});
it('should throw error for null value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'roles';
const value = null;
// Act & Assert
expect(() => assertStringArray(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertStringArray(entityName, fieldName, value)).toThrow('Field roles must be an array of strings');
});
it('should throw error for undefined value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'roles';
const value = undefined;
// Act & Assert
expect(() => assertStringArray(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertStringArray(entityName, fieldName, value)).toThrow('Field roles must be an array of strings');
});
});
describe('assertDate', () => {
it('should pass for valid Date', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'createdAt';
const value = new Date();
// Act & Assert
expect(() => assertDate(entityName, fieldName, value)).not.toThrow();
});
it('should pass for specific date', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'createdAt';
const value = new Date('2024-01-01');
// Act & Assert
expect(() => assertDate(entityName, fieldName, value)).not.toThrow();
});
it('should throw error for invalid date', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'createdAt';
const value = new Date('invalid');
// Act & Assert
expect(() => assertDate(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertDate(entityName, fieldName, value)).toThrow('Field createdAt must be a valid Date');
});
it('should throw error for non-Date value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'createdAt';
const value = '2024-01-01';
// Act & Assert
expect(() => assertDate(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertDate(entityName, fieldName, value)).toThrow('Field createdAt must be a valid Date');
});
it('should throw error for null value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'createdAt';
const value = null;
// Act & Assert
expect(() => assertDate(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertDate(entityName, fieldName, value)).toThrow('Field createdAt must be a valid Date');
});
it('should throw error for undefined value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'createdAt';
const value = undefined;
// Act & Assert
expect(() => assertDate(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertDate(entityName, fieldName, value)).toThrow('Field createdAt must be a valid Date');
});
});
describe('assertOptionalDate', () => {
it('should pass for valid Date', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'lastLoginAt';
const value = new Date();
// Act & Assert
expect(() => assertOptionalDate(entityName, fieldName, value)).not.toThrow();
});
it('should pass for null value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'lastLoginAt';
const value = null;
// Act & Assert
expect(() => assertOptionalDate(entityName, fieldName, value)).not.toThrow();
});
it('should pass for undefined value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'lastLoginAt';
const value = undefined;
// Act & Assert
expect(() => assertOptionalDate(entityName, fieldName, value)).not.toThrow();
});
it('should throw error for invalid date', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'lastLoginAt';
const value = new Date('invalid');
// Act & Assert
expect(() => assertOptionalDate(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertOptionalDate(entityName, fieldName, value)).toThrow('Field lastLoginAt must be a valid Date');
});
it('should throw error for non-Date value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'lastLoginAt';
const value = '2024-01-01';
// Act & Assert
expect(() => assertOptionalDate(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertOptionalDate(entityName, fieldName, value)).toThrow('Field lastLoginAt must be a valid Date');
});
});
describe('assertOptionalString', () => {
it('should pass for valid string', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'primaryDriverId';
const value = 'driver-123';
// Act & Assert
expect(() => assertOptionalString(entityName, fieldName, value)).not.toThrow();
});
it('should pass for null value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'primaryDriverId';
const value = null;
// Act & Assert
expect(() => assertOptionalString(entityName, fieldName, value)).not.toThrow();
});
it('should pass for undefined value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'primaryDriverId';
const value = undefined;
// Act & Assert
expect(() => assertOptionalString(entityName, fieldName, value)).not.toThrow();
});
it('should throw error for non-string value', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'primaryDriverId';
const value = 123;
// Act & Assert
expect(() => assertOptionalString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertOptionalString(entityName, fieldName, value)).toThrow('Field primaryDriverId must be a string or undefined');
});
it('should throw error for empty string', () => {
// Arrange
const entityName = 'AdminUser';
const fieldName = 'primaryDriverId';
const value = '';
// Act & Assert
expect(() => assertOptionalString(entityName, fieldName, value)).toThrow(TypeOrmAdminSchemaError);
expect(() => assertOptionalString(entityName, fieldName, value)).toThrow('Field primaryDriverId must be a string or undefined');
});
});
describe('real-world scenarios', () => {
it('should validate complete admin user entity', () => {
// Arrange
const entityName = 'AdminUser';
const id = 'user-123';
const email = 'admin@example.com';
const displayName = 'Admin User';
const roles = ['owner', 'admin'];
const status = 'active';
const createdAt = new Date();
const updatedAt = new Date();
// Act & Assert
expect(() => assertNonEmptyString(entityName, 'id', id)).not.toThrow();
expect(() => assertNonEmptyString(entityName, 'email', email)).not.toThrow();
expect(() => assertNonEmptyString(entityName, 'displayName', displayName)).not.toThrow();
expect(() => assertStringArray(entityName, 'roles', roles)).not.toThrow();
expect(() => assertNonEmptyString(entityName, 'status', status)).not.toThrow();
expect(() => assertDate(entityName, 'createdAt', createdAt)).not.toThrow();
expect(() => assertDate(entityName, 'updatedAt', updatedAt)).not.toThrow();
});
it('should validate admin user with optional fields', () => {
// Arrange
const entityName = 'AdminUser';
const primaryDriverId = 'driver-456';
const lastLoginAt = new Date();
// Act & Assert
expect(() => assertOptionalString(entityName, 'primaryDriverId', primaryDriverId)).not.toThrow();
expect(() => assertOptionalDate(entityName, 'lastLoginAt', lastLoginAt)).not.toThrow();
});
it('should validate admin user without optional fields', () => {
// Arrange
const entityName = 'AdminUser';
const primaryDriverId = undefined;
const lastLoginAt = null;
// Act & Assert
expect(() => assertOptionalString(entityName, 'primaryDriverId', primaryDriverId)).not.toThrow();
expect(() => assertOptionalDate(entityName, 'lastLoginAt', lastLoginAt)).not.toThrow();
});
});
});
});

View File

@@ -0,0 +1,55 @@
import { TypeOrmAdminSchemaError } from '../errors/TypeOrmAdminSchemaError';
export function assertNonEmptyString(entityName: string, fieldName: string, value: unknown): void {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new TypeOrmAdminSchemaError({
entityName,
fieldName,
reason: 'INVALID_STRING',
message: `Field ${fieldName} must be a non-empty string`,
});
}
}
export function assertStringArray(entityName: string, fieldName: string, value: unknown): void {
if (!Array.isArray(value) || !value.every(item => typeof item === 'string')) {
throw new TypeOrmAdminSchemaError({
entityName,
fieldName,
reason: 'INVALID_STRING_ARRAY',
message: `Field ${fieldName} must be an array of strings`,
});
}
}
export function assertDate(entityName: string, fieldName: string, value: unknown): void {
if (!(value instanceof Date) || isNaN(value.getTime())) {
throw new TypeOrmAdminSchemaError({
entityName,
fieldName,
reason: 'INVALID_DATE',
message: `Field ${fieldName} must be a valid Date`,
});
}
}
export function assertOptionalDate(entityName: string, fieldName: string, value: unknown): void {
if (value === null || value === undefined) {
return;
}
assertDate(entityName, fieldName, value);
}
export function assertOptionalString(entityName: string, fieldName: string, value: unknown): void {
if (value === null || value === undefined) {
return;
}
if (typeof value !== 'string') {
throw new TypeOrmAdminSchemaError({
entityName,
fieldName,
reason: 'INVALID_OPTIONAL_STRING',
message: `Field ${fieldName} must be a string or undefined`,
});
}
}