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
287 lines
12 KiB
TypeScript
287 lines
12 KiB
TypeScript
// Comprehensive test coverage for Repository layer
|
|
import { IUserRepository } from '../src/Domain/IRepository/IUserRepository';
|
|
import { IDeckRepository } from '../src/Domain/IRepository/IDeckRepository';
|
|
import { IOrganizationRepository } from '../src/Domain/IRepository/IOrganizationRepository';
|
|
import { IContactRepository } from '../src/Domain/IRepository/IContactRepository';
|
|
import { UserAggregate, UserState } from '../src/Domain/User/UserAggregate';
|
|
import { DeckAggregate, Type as DeckType } from '../src/Domain/Deck/DeckAggregate';
|
|
import { OrganizationAggregate } from '../src/Domain/Organization/OrganizationAggregate';
|
|
import { ContactAggregate } from '../src/Domain/Contact/ContactAggregate';
|
|
import {
|
|
createMockUser,
|
|
createMockDeck,
|
|
createMockOrganization,
|
|
createMockContact,
|
|
createMockUserRepository,
|
|
createMockDeckRepository,
|
|
createMockOrganizationRepository,
|
|
createMockContactRepository
|
|
} from './testUtils';
|
|
|
|
describe('Repository Layer - Comprehensive Coverage', () => {
|
|
describe('IUserRepository Interface Coverage', () => {
|
|
let mockUserRepository: jest.Mocked<IUserRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockUserRepository = createMockUserRepository();
|
|
});
|
|
|
|
it('should implement all required methods', () => {
|
|
expect(mockUserRepository.create).toBeDefined();
|
|
expect(mockUserRepository.findByPage).toBeDefined();
|
|
expect(mockUserRepository.findByPageIncludingDeleted).toBeDefined();
|
|
expect(mockUserRepository.findById).toBeDefined();
|
|
expect(mockUserRepository.findByIdIncludingDeleted).toBeDefined();
|
|
expect(mockUserRepository.findByUsername).toBeDefined();
|
|
expect(mockUserRepository.findByEmail).toBeDefined();
|
|
expect(mockUserRepository.findByToken).toBeDefined();
|
|
expect(mockUserRepository.search).toBeDefined();
|
|
expect(mockUserRepository.searchIncludingDeleted).toBeDefined();
|
|
expect(mockUserRepository.update).toBeDefined();
|
|
expect(mockUserRepository.delete).toBeDefined();
|
|
expect(mockUserRepository.softDelete).toBeDefined();
|
|
expect(mockUserRepository.deactivate).toBeDefined();
|
|
});
|
|
|
|
it('should handle user creation', async () => {
|
|
const userData = { username: 'testuser', email: 'test@example.com' };
|
|
const mockUser = createMockUser(userData);
|
|
mockUserRepository.create.mockResolvedValue(mockUser);
|
|
|
|
const result = await mockUserRepository.create(userData);
|
|
expect(result).toEqual(mockUser);
|
|
expect(mockUserRepository.create).toHaveBeenCalledWith(userData);
|
|
});
|
|
|
|
it('should handle paginated user retrieval', async () => {
|
|
const mockUsers = [createMockUser(), createMockUser({ id: 'user2' })];
|
|
mockUserRepository.findByPage.mockResolvedValue({ users: mockUsers, totalCount: 2 });
|
|
|
|
const result = await mockUserRepository.findByPage(0, 10);
|
|
expect(result.users).toHaveLength(2);
|
|
expect(result.totalCount).toBe(2);
|
|
});
|
|
|
|
it('should handle user search operations', async () => {
|
|
const mockUsers = [createMockUser({ username: 'searchtest' })];
|
|
mockUserRepository.search.mockResolvedValue({ users: mockUsers, totalCount: 1 });
|
|
|
|
const result = await mockUserRepository.search('searchtest');
|
|
expect(result.users).toHaveLength(1);
|
|
expect(result.users[0].username).toBe('searchtest');
|
|
});
|
|
|
|
it('should handle user state transitions', async () => {
|
|
const mockUser = createMockUser({ state: UserState.VERIFIED_REGULAR });
|
|
mockUserRepository.deactivate.mockResolvedValue(mockUser);
|
|
|
|
const result = await mockUserRepository.deactivate('user-id');
|
|
expect(result).toEqual(mockUser);
|
|
});
|
|
});
|
|
|
|
describe('IDeckRepository Interface Coverage', () => {
|
|
let mockDeckRepository: jest.Mocked<IDeckRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockDeckRepository = createMockDeckRepository();
|
|
});
|
|
|
|
it('should implement all required methods including new ones', () => {
|
|
expect(mockDeckRepository.create).toBeDefined();
|
|
expect(mockDeckRepository.findByPage).toBeDefined();
|
|
expect(mockDeckRepository.findByPageIncludingDeleted).toBeDefined();
|
|
expect(mockDeckRepository.findById).toBeDefined();
|
|
expect(mockDeckRepository.findByIdIncludingDeleted).toBeDefined();
|
|
expect(mockDeckRepository.search).toBeDefined();
|
|
expect(mockDeckRepository.searchIncludingDeleted).toBeDefined();
|
|
expect(mockDeckRepository.update).toBeDefined();
|
|
expect(mockDeckRepository.delete).toBeDefined();
|
|
expect(mockDeckRepository.softDelete).toBeDefined();
|
|
expect(mockDeckRepository.countActiveByUserId).toBeDefined();
|
|
expect(mockDeckRepository.countOrganizationalByUserId).toBeDefined();
|
|
expect(mockDeckRepository.findFilteredDecks).toBeDefined();
|
|
});
|
|
|
|
it('should handle deck counting operations', async () => {
|
|
mockDeckRepository.countActiveByUserId.mockResolvedValue(5);
|
|
mockDeckRepository.countOrganizationalByUserId.mockResolvedValue(3);
|
|
|
|
const activeCount = await mockDeckRepository.countActiveByUserId('user-id');
|
|
const orgCount = await mockDeckRepository.countOrganizationalByUserId('user-id');
|
|
|
|
expect(activeCount).toBe(5);
|
|
expect(orgCount).toBe(3);
|
|
});
|
|
|
|
it('should handle filtered deck retrieval', async () => {
|
|
const mockDecks = [createMockDeck(), createMockDeck({ id: 'deck2' })];
|
|
mockDeckRepository.findFilteredDecks.mockResolvedValue({ decks: mockDecks, totalCount: 2 });
|
|
|
|
const result = await mockDeckRepository.findFilteredDecks('user-id', 'org-id', false, 0, 10);
|
|
expect(result.decks).toHaveLength(2);
|
|
expect(result.totalCount).toBe(2);
|
|
});
|
|
|
|
it('should handle different deck types', async () => {
|
|
const jokerDeck = createMockDeck({ type: DeckType.JOKER });
|
|
const luckDeck = createMockDeck({ type: DeckType.LUCK });
|
|
const questionDeck = createMockDeck({ type: DeckType.QUESTION });
|
|
|
|
mockDeckRepository.create.mockResolvedValueOnce(jokerDeck);
|
|
mockDeckRepository.create.mockResolvedValueOnce(luckDeck);
|
|
mockDeckRepository.create.mockResolvedValueOnce(questionDeck);
|
|
|
|
const result1 = await mockDeckRepository.create({ type: DeckType.JOKER });
|
|
const result2 = await mockDeckRepository.create({ type: DeckType.LUCK });
|
|
const result3 = await mockDeckRepository.create({ type: DeckType.QUESTION });
|
|
|
|
expect(result1.type).toBe(DeckType.JOKER);
|
|
expect(result2.type).toBe(DeckType.LUCK);
|
|
expect(result3.type).toBe(DeckType.QUESTION);
|
|
});
|
|
});
|
|
|
|
describe('IOrganizationRepository Interface Coverage', () => {
|
|
let mockOrgRepository: jest.Mocked<IOrganizationRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockOrgRepository = createMockOrganizationRepository();
|
|
});
|
|
|
|
it('should implement all required methods', () => {
|
|
expect(mockOrgRepository.create).toBeDefined();
|
|
expect(mockOrgRepository.findByPage).toBeDefined();
|
|
expect(mockOrgRepository.findByPageIncludingDeleted).toBeDefined();
|
|
expect(mockOrgRepository.findById).toBeDefined();
|
|
expect(mockOrgRepository.findByIdIncludingDeleted).toBeDefined();
|
|
expect(mockOrgRepository.search).toBeDefined();
|
|
expect(mockOrgRepository.searchIncludingDeleted).toBeDefined();
|
|
expect(mockOrgRepository.update).toBeDefined();
|
|
expect(mockOrgRepository.delete).toBeDefined();
|
|
expect(mockOrgRepository.softDelete).toBeDefined();
|
|
});
|
|
|
|
it('should handle organization CRUD operations', async () => {
|
|
const orgData = { name: 'Test Org', contactemail: 'test@org.com' };
|
|
const mockOrg = createMockOrganization(orgData);
|
|
|
|
mockOrgRepository.create.mockResolvedValue(mockOrg);
|
|
mockOrgRepository.findById.mockResolvedValue(mockOrg);
|
|
mockOrgRepository.update.mockResolvedValue(mockOrg);
|
|
mockOrgRepository.softDelete.mockResolvedValue(mockOrg);
|
|
|
|
const created = await mockOrgRepository.create(orgData);
|
|
const found = await mockOrgRepository.findById('org-id');
|
|
const updated = await mockOrgRepository.update('org-id', { name: 'Updated Org' });
|
|
const deleted = await mockOrgRepository.softDelete('org-id');
|
|
|
|
expect(created.name).toBe('Test Org');
|
|
expect(found).toEqual(mockOrg);
|
|
expect(updated).toEqual(mockOrg);
|
|
expect(deleted).toEqual(mockOrg);
|
|
});
|
|
});
|
|
|
|
describe('IContactRepository Interface Coverage', () => {
|
|
let mockContactRepository: jest.Mocked<IContactRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockContactRepository = createMockContactRepository();
|
|
});
|
|
|
|
it('should implement all required methods', () => {
|
|
expect(mockContactRepository.create).toBeDefined();
|
|
expect(mockContactRepository.findById).toBeDefined();
|
|
expect(mockContactRepository.findByPage).toBeDefined();
|
|
expect(mockContactRepository.findByPageIncludingDeleted).toBeDefined();
|
|
expect(mockContactRepository.findByIdIncludingDeleted).toBeDefined();
|
|
expect(mockContactRepository.search).toBeDefined();
|
|
expect(mockContactRepository.searchIncludingDeleted).toBeDefined();
|
|
expect(mockContactRepository.update).toBeDefined();
|
|
expect(mockContactRepository.delete).toBeDefined();
|
|
expect(mockContactRepository.softDelete).toBeDefined();
|
|
});
|
|
|
|
it('should handle contact search operations', async () => {
|
|
const mockContacts = [createMockContact({ email: 'test@example.com' })];
|
|
mockContactRepository.search.mockResolvedValue(mockContacts);
|
|
mockContactRepository.searchIncludingDeleted.mockResolvedValue(mockContacts);
|
|
|
|
const activeResults = await mockContactRepository.search('test');
|
|
const allResults = await mockContactRepository.searchIncludingDeleted('test');
|
|
|
|
expect(activeResults).toHaveLength(1);
|
|
expect(allResults).toHaveLength(1);
|
|
});
|
|
|
|
it('should handle contact lifecycle', async () => {
|
|
const contactData = { email: 'user@example.com', message: 'Help Request' };
|
|
const mockContact = createMockContact(contactData);
|
|
|
|
mockContactRepository.create.mockResolvedValue(mockContact);
|
|
mockContactRepository.findById.mockResolvedValue(mockContact);
|
|
mockContactRepository.findByIdIncludingDeleted.mockResolvedValue(mockContact);
|
|
|
|
const created = await mockContactRepository.create(contactData);
|
|
const found = await mockContactRepository.findById('contact-id');
|
|
const foundWithDeleted = await mockContactRepository.findByIdIncludingDeleted('contact-id');
|
|
|
|
expect(created.email).toBe('user@example.com');
|
|
expect(found).toEqual(mockContact);
|
|
expect(foundWithDeleted).toEqual(mockContact);
|
|
});
|
|
});
|
|
|
|
describe('Cross-Repository Integration Tests', () => {
|
|
let userRepo: jest.Mocked<IUserRepository>;
|
|
let deckRepo: jest.Mocked<IDeckRepository>;
|
|
let orgRepo: jest.Mocked<IOrganizationRepository>;
|
|
|
|
beforeEach(() => {
|
|
userRepo = createMockUserRepository();
|
|
deckRepo = createMockDeckRepository();
|
|
orgRepo = createMockOrganizationRepository();
|
|
});
|
|
|
|
it('should simulate user-deck relationship operations', async () => {
|
|
const mockUser = createMockUser({ id: 'user-123' });
|
|
const mockDecks = [
|
|
createMockDeck({ userid: 'user-123', name: 'Deck 1' }),
|
|
createMockDeck({ userid: 'user-123', name: 'Deck 2' })
|
|
];
|
|
|
|
userRepo.findById.mockResolvedValue(mockUser);
|
|
deckRepo.findFilteredDecks.mockResolvedValue({ decks: mockDecks, totalCount: 2 });
|
|
deckRepo.countActiveByUserId.mockResolvedValue(2);
|
|
|
|
const user = await userRepo.findById('user-123');
|
|
const userDecks = await deckRepo.findFilteredDecks('user-123');
|
|
const deckCount = await deckRepo.countActiveByUserId('user-123');
|
|
|
|
expect(user).toBeDefined();
|
|
expect(userDecks.decks).toHaveLength(2);
|
|
expect(deckCount).toBe(2);
|
|
expect(userDecks.decks.every(deck => deck.userid === 'user-123')).toBe(true);
|
|
});
|
|
|
|
it('should simulate organization-user relationship operations', async () => {
|
|
const mockOrg = createMockOrganization({ id: 'org-123', name: 'Test Organization' });
|
|
const mockUsers = [
|
|
createMockUser({ orgid: 'org-123' }),
|
|
createMockUser({ orgid: 'org-123', id: 'user-2' })
|
|
];
|
|
|
|
orgRepo.findById.mockResolvedValue(mockOrg);
|
|
userRepo.findByPage.mockResolvedValue({ users: mockUsers, totalCount: 2 });
|
|
|
|
const org = await orgRepo.findById('org-123');
|
|
const orgUsers = await userRepo.findByPage(0, 10);
|
|
|
|
expect(org).toBeDefined();
|
|
expect(orgUsers.users).toHaveLength(2);
|
|
expect(orgUsers.users.every(user => user.orgid === 'org-123')).toBe(true);
|
|
});
|
|
});
|
|
});
|