Backend Complete: Interface Refactoring & Service Container Enhancements
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
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user