86211923db
Repository Interface Optimization: - Created IBaseRepository.ts and IPaginatedRepository.ts - Refactored all 7 repository interfaces to extend base interfaces - Eliminated ~200 lines of redundant code (70% reduction) - Improved type safety and maintainability Dependency Injection Improvements: - Added EmailService and GameTokenService to DIContainer - Updated CreateUserCommandHandler constructor for DI - Updated RequestPasswordResetCommandHandler constructor for DI - Enhanced testability and service consistency Environment Configuration: - Created comprehensive .env.example with 40+ variables - Organized into 12 logical sections (Database, Security, Email, etc.) - Added security guidelines and best practices - Documented all backend environment requirements Documentation: - Added comprehensive codebase review - Created refactoring summary report - Added frontend implementation guide Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
208 lines
6.7 KiB
TypeScript
208 lines
6.7 KiB
TypeScript
import { CreateDeckCommandHandler } from '../../../../src/Application/Deck/commands/CreateDeckCommandHandler';
|
|
import { CreateDeckCommand } from '../../../../src/Application/Deck/commands/CreateDeckCommand';
|
|
import { IDeckRepository } from '../../../../src/Domain/IRepository/IDeckRepository';
|
|
import { IUserRepository } from '../../../../src/Domain/IRepository/IUserRepository';
|
|
import { IOrganizationRepository } from '../../../../src/Domain/IRepository/IOrganizationRepository';
|
|
import { UserState } from '../../../../src/Domain/User/UserAggregate';
|
|
import { Type as DeckType } from '../../../../src/Domain/Deck/DeckAggregate';
|
|
import { createMockDeck, createMockDeckRepository, createMockUserRepository, createMockOrganizationRepository, createMockUser } from '../../../testUtils';
|
|
|
|
describe('CreateDeckCommandHandler', () => {
|
|
let handler: CreateDeckCommandHandler;
|
|
let mockDeckRepository: jest.Mocked<IDeckRepository>;
|
|
let mockUserRepository: jest.Mocked<IUserRepository>;
|
|
let mockOrganizationRepository: jest.Mocked<IOrganizationRepository>;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
mockDeckRepository = createMockDeckRepository();
|
|
mockUserRepository = createMockUserRepository();
|
|
mockOrganizationRepository = createMockOrganizationRepository();
|
|
|
|
handler = new CreateDeckCommandHandler(mockDeckRepository, mockUserRepository, mockOrganizationRepository);
|
|
});
|
|
|
|
describe('execute', () => {
|
|
it('should successfully create a new deck with valid user', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Test Deck',
|
|
type: DeckType.JOKER,
|
|
userid: 'user-123',
|
|
cards: [{ id: 'card-1', name: 'Test Card' }],
|
|
};
|
|
|
|
const mockUser = createMockUser({
|
|
id: command.userid,
|
|
state: UserState.VERIFIED_REGULAR,
|
|
type: 'user'
|
|
});
|
|
|
|
const mockDeck = createMockDeck({
|
|
name: command.name,
|
|
type: command.type,
|
|
userid: command.userid
|
|
});
|
|
|
|
mockUserRepository.findById.mockResolvedValue(mockUser);
|
|
mockDeckRepository.countActiveByUserId.mockResolvedValue(0);
|
|
mockDeckRepository.create.mockResolvedValue(mockDeck);
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(mockUserRepository.findById).toHaveBeenCalledWith(command.userid);
|
|
expect(mockDeckRepository.create).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw error when user not found', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Test Deck',
|
|
type: DeckType.JOKER,
|
|
userid: 'nonexistent-user',
|
|
cards: [],
|
|
};
|
|
|
|
mockUserRepository.findById.mockResolvedValue(null);
|
|
|
|
// Act & Assert
|
|
await expect(handler.execute(command)).rejects.toThrow('User not found');
|
|
expect(mockUserRepository.findById).toHaveBeenCalledWith(command.userid);
|
|
expect(mockDeckRepository.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle admin user creating unlimited decks', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Admin Deck',
|
|
type: DeckType.JOKER,
|
|
userid: 'admin-123',
|
|
cards: [],
|
|
};
|
|
|
|
const mockAdminUser = createMockUser({
|
|
id: command.userid,
|
|
state: UserState.VERIFIED_REGULAR,
|
|
type: 'admin'
|
|
});
|
|
|
|
const mockDeck = createMockDeck({
|
|
name: command.name,
|
|
type: command.type,
|
|
userid: command.userid
|
|
});
|
|
|
|
mockUserRepository.findById.mockResolvedValue(mockAdminUser);
|
|
mockDeckRepository.create.mockResolvedValue(mockDeck);
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(mockDeckRepository.countActiveByUserId).toHaveBeenCalled(); // Admin still checks but bypasses limits
|
|
});
|
|
|
|
it('should handle repository creation errors', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Test Deck',
|
|
type: DeckType.JOKER,
|
|
userid: 'user-123',
|
|
cards: [],
|
|
};
|
|
|
|
const mockUser = createMockUser({ id: command.userid });
|
|
mockUserRepository.findById.mockResolvedValue(mockUser);
|
|
mockDeckRepository.countActiveByUserId.mockResolvedValue(0);
|
|
mockDeckRepository.create.mockRejectedValue(new Error('Database error'));
|
|
|
|
// Act & Assert
|
|
await expect(handler.execute(command)).rejects.toThrow('Database error');
|
|
});
|
|
|
|
it('should create deck with different types', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Question Deck',
|
|
type: DeckType.QUESTION,
|
|
userid: 'user-123',
|
|
cards: [],
|
|
};
|
|
|
|
const mockUser = createMockUser({ id: command.userid });
|
|
const mockDeck = createMockDeck({
|
|
name: command.name,
|
|
type: command.type,
|
|
userid: command.userid
|
|
});
|
|
|
|
mockUserRepository.findById.mockResolvedValue(mockUser);
|
|
mockDeckRepository.countActiveByUserId.mockResolvedValue(0);
|
|
mockDeckRepository.create.mockResolvedValue(mockDeck);
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(mockDeckRepository.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
type: DeckType.QUESTION
|
|
}));
|
|
});
|
|
|
|
it('should handle empty cards array', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Empty Deck',
|
|
type: DeckType.JOKER,
|
|
userid: 'user-123',
|
|
cards: [],
|
|
};
|
|
|
|
const mockUser = createMockUser({ id: command.userid });
|
|
const mockDeck = createMockDeck(command);
|
|
|
|
mockUserRepository.findById.mockResolvedValue(mockUser);
|
|
mockDeckRepository.countActiveByUserId.mockResolvedValue(0);
|
|
mockDeckRepository.create.mockResolvedValue(mockDeck);
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('should check deck limits for regular users', async () => {
|
|
// Arrange
|
|
const command: CreateDeckCommand = {
|
|
name: 'Test Deck',
|
|
type: DeckType.JOKER,
|
|
userid: 'user-123',
|
|
cards: [],
|
|
};
|
|
|
|
const mockUser = createMockUser({
|
|
id: command.userid,
|
|
type: 'user'
|
|
});
|
|
const mockDeck = createMockDeck({ userid: command.userid });
|
|
|
|
mockUserRepository.findById.mockResolvedValue(mockUser);
|
|
mockDeckRepository.countActiveByUserId.mockResolvedValue(0);
|
|
mockDeckRepository.create.mockResolvedValue(mockDeck);
|
|
|
|
// Act
|
|
await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(mockDeckRepository.countActiveByUserId).toHaveBeenCalledWith(command.userid);
|
|
});
|
|
});
|
|
});
|