test(P2): Add controller tests for sales, commissions, and portfolio

- Add 12 controller test files (117 tests total):
  - sales: leads, opportunities, activities, pipeline, dashboard
  - commissions: schemes, assignments, entries, periods, dashboard
  - portfolio: categories, products
- Fix flaky test in assignments.service.spec.ts (timestamp issue)
- All 441 tests passing for these modules

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 13:25:42 -06:00
parent b1ee86e0e5
commit 61d7d29212
13 changed files with 2577 additions and 3 deletions

View File

@ -0,0 +1,185 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AssignmentsController } from '../controllers/assignments.controller';
import { AssignmentsService } from '../services/assignments.service';
import { CreateAssignmentDto, UpdateAssignmentDto, AssignmentListQueryDto } from '../dto';
describe('AssignmentsController', () => {
let controller: AssignmentsController;
let assignmentsService: jest.Mocked<AssignmentsService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockAssignmentId = '550e8400-e29b-41d4-a716-446655440003';
const mockSchemeId = '550e8400-e29b-41d4-a716-446655440004';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockAssignment = {
id: mockAssignmentId,
tenantId: mockTenantId,
userId: mockUserId,
schemeId: mockSchemeId,
isActive: true,
customRate: null,
effectiveFrom: new Date('2026-01-01'),
effectiveTo: null,
notes: 'Standard assignment',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedAssignments = {
data: [mockAssignment],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockAssignmentsService = {
findAll: jest.fn(),
findOne: jest.fn(),
findActiveForUser: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [AssignmentsController],
providers: [
{
provide: AssignmentsService,
useValue: mockAssignmentsService,
},
],
}).compile();
controller = module.get<AssignmentsController>(AssignmentsController);
assignmentsService = module.get(AssignmentsService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated assignments', async () => {
const query: AssignmentListQueryDto = { page: 1, limit: 20 };
assignmentsService.findAll.mockResolvedValue(mockPaginatedAssignments);
const result = await controller.findAll(mockRequestUser, query);
expect(assignmentsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedAssignments);
});
it('should filter by userId', async () => {
const query: AssignmentListQueryDto = { page: 1, limit: 20, userId: mockUserId };
assignmentsService.findAll.mockResolvedValue(mockPaginatedAssignments);
await controller.findAll(mockRequestUser, query);
expect(assignmentsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('findMyAssignments', () => {
it('should return current user active assignments', async () => {
assignmentsService.findActiveForUser.mockResolvedValue([mockAssignment]);
const result = await controller.findMyAssignments(mockRequestUser);
expect(assignmentsService.findActiveForUser).toHaveBeenCalledWith(mockTenantId, mockUserId);
expect(result).toEqual([mockAssignment]);
});
});
describe('findOne', () => {
it('should return an assignment by id', async () => {
assignmentsService.findOne.mockResolvedValue(mockAssignment);
const result = await controller.findOne(mockRequestUser, mockAssignmentId);
expect(assignmentsService.findOne).toHaveBeenCalledWith(mockTenantId, mockAssignmentId);
expect(result).toEqual(mockAssignment);
});
});
describe('create', () => {
it('should create a new assignment', async () => {
const createDto: CreateAssignmentDto = {
userId: 'another-user-id',
schemeId: mockSchemeId,
effectiveFrom: '2026-02-01',
};
const createdAssignment = { ...mockAssignment, ...createDto, id: 'new-assignment-id' };
assignmentsService.create.mockResolvedValue(createdAssignment);
const result = await controller.create(mockRequestUser, createDto);
expect(assignmentsService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdAssignment);
});
it('should create assignment with custom rate', async () => {
const createDto: CreateAssignmentDto = {
userId: mockUserId,
schemeId: mockSchemeId,
customRate: 12.5,
};
assignmentsService.create.mockResolvedValue({ ...mockAssignment, customRate: 12.5 });
const result = await controller.create(mockRequestUser, createDto);
expect(result.customRate).toBe(12.5);
});
});
describe('update', () => {
it('should update an assignment', async () => {
const updateDto: UpdateAssignmentDto = {
customRate: 15,
notes: 'Updated notes',
};
const updatedAssignment = { ...mockAssignment, ...updateDto };
assignmentsService.update.mockResolvedValue(updatedAssignment);
const result = await controller.update(mockRequestUser, mockAssignmentId, updateDto);
expect(assignmentsService.update).toHaveBeenCalledWith(mockTenantId, mockAssignmentId, updateDto);
expect(result.customRate).toBe(15);
});
it('should deactivate an assignment', async () => {
const updateDto: UpdateAssignmentDto = {
isActive: false,
effectiveTo: '2026-03-01',
};
const deactivatedAssignment = { ...mockAssignment, isActive: false };
assignmentsService.update.mockResolvedValue(deactivatedAssignment);
const result = await controller.update(mockRequestUser, mockAssignmentId, updateDto);
expect(result.isActive).toBe(false);
});
});
describe('remove', () => {
it('should delete an assignment', async () => {
assignmentsService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockAssignmentId);
expect(assignmentsService.remove).toHaveBeenCalledWith(mockTenantId, mockAssignmentId);
expect(result).toEqual({ message: 'Commission assignment deleted successfully' });
});
});
});

View File

@ -380,12 +380,13 @@ describe('AssignmentsService', () => {
describe('remove', () => {
it('should delete an assignment', async () => {
assignmentRepo.findOne.mockResolvedValue(createMockAssignment() as CommissionAssignmentEntity);
assignmentRepo.remove.mockResolvedValue(createMockAssignment() as CommissionAssignmentEntity);
const mockAssignment = createMockAssignment() as CommissionAssignmentEntity;
assignmentRepo.findOne.mockResolvedValue(mockAssignment);
assignmentRepo.remove.mockResolvedValue(mockAssignment);
await service.remove(mockTenantId, mockAssignmentId);
expect(assignmentRepo.remove).toHaveBeenCalledWith(createMockAssignment());
expect(assignmentRepo.remove).toHaveBeenCalledWith(mockAssignment);
});
it('should throw NotFoundException when assignment not found', async () => {

View File

@ -0,0 +1,177 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommissionsDashboardController } from '../controllers/dashboard.controller';
import { CommissionsDashboardService } from '../services/commissions-dashboard.service';
import { EntryStatus } from '../entities/commission-entry.entity';
describe('CommissionsDashboardController', () => {
let controller: CommissionsDashboardController;
let dashboardService: jest.Mocked<CommissionsDashboardService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockDashboardSummary = {
totalSchemes: 5,
activeSchemes: 4,
totalAssignments: 25,
activeAssignments: 20,
totalEntries: 150,
pendingEntries: 30,
approvedEntries: 100,
paidEntries: 20,
totalPendingAmount: 15000,
totalApprovedAmount: 50000,
totalPaidAmount: 10000,
currentPeriodId: 'period-123',
currentPeriodName: 'January 2026',
};
const mockUserEarnings = {
userId: mockUserId,
userName: 'John Doe',
totalEarnings: 25000,
pendingAmount: 5000,
approvedAmount: 15000,
paidAmount: 5000,
totalEntries: 50,
averageCommission: 500,
lastEntryDate: new Date('2026-01-15'),
};
const mockEntriesByStatus = [
{ status: EntryStatus.PENDING, count: 30, amount: 15000 },
{ status: EntryStatus.APPROVED, count: 100, amount: 50000 },
{ status: EntryStatus.PAID, count: 20, amount: 10000 },
];
const mockEntriesByScheme = [
{ schemeId: 'scheme-1', schemeName: 'Sales Commission', count: 80, amount: 40000 },
{ schemeId: 'scheme-2', schemeName: 'Referral Bonus', count: 50, amount: 25000 },
{ schemeId: 'scheme-3', schemeName: 'Performance Bonus', count: 20, amount: 10000 },
];
const mockEntriesByUser = [
{ userId: mockUserId, userName: 'John Doe', count: 50, amount: 25000 },
{ userId: 'user-2', userName: 'Jane Smith', count: 40, amount: 20000 },
{ userId: 'user-3', userName: 'Bob Johnson', count: 30, amount: 15000 },
];
const mockTopEarners = [
{ userId: mockUserId, userName: 'John Doe', count: 50, amount: 25000 },
{ userId: 'user-2', userName: 'Jane Smith', count: 40, amount: 20000 },
];
beforeEach(async () => {
const mockDashboardService = {
getDashboardSummary: jest.fn(),
getUserEarnings: jest.fn(),
getEntriesByStatus: jest.fn(),
getEntriesByScheme: jest.fn(),
getEntriesByUser: jest.fn(),
getTopEarners: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [CommissionsDashboardController],
providers: [
{
provide: CommissionsDashboardService,
useValue: mockDashboardService,
},
],
}).compile();
controller = module.get<CommissionsDashboardController>(CommissionsDashboardController);
dashboardService = module.get(CommissionsDashboardService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getSummary', () => {
it('should return dashboard summary', async () => {
dashboardService.getDashboardSummary.mockResolvedValue(mockDashboardSummary);
const result = await controller.getSummary(mockRequestUser);
expect(dashboardService.getDashboardSummary).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockDashboardSummary);
expect(result.totalSchemes).toBe(5);
});
});
describe('getMyEarnings', () => {
it('should return current user earnings', async () => {
dashboardService.getUserEarnings.mockResolvedValue(mockUserEarnings);
const result = await controller.getMyEarnings(mockRequestUser);
expect(dashboardService.getUserEarnings).toHaveBeenCalledWith(mockTenantId, mockUserId);
expect(result).toEqual(mockUserEarnings);
expect(result.totalEarnings).toBe(25000);
});
});
describe('getEntriesByStatus', () => {
it('should return entries grouped by status', async () => {
dashboardService.getEntriesByStatus.mockResolvedValue(mockEntriesByStatus);
const result = await controller.getEntriesByStatus(mockRequestUser);
expect(dashboardService.getEntriesByStatus).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockEntriesByStatus);
expect(result.length).toBe(3);
});
});
describe('getEntriesByScheme', () => {
it('should return entries grouped by scheme', async () => {
dashboardService.getEntriesByScheme.mockResolvedValue(mockEntriesByScheme);
const result = await controller.getEntriesByScheme(mockRequestUser);
expect(dashboardService.getEntriesByScheme).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockEntriesByScheme);
expect(result.length).toBe(3);
});
});
describe('getEntriesByUser', () => {
it('should return entries grouped by user', async () => {
dashboardService.getEntriesByUser.mockResolvedValue(mockEntriesByUser);
const result = await controller.getEntriesByUser(mockRequestUser);
expect(dashboardService.getEntriesByUser).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockEntriesByUser);
expect(result.length).toBe(3);
});
});
describe('getTopEarners', () => {
it('should return top earners with default limit', async () => {
dashboardService.getTopEarners.mockResolvedValue(mockTopEarners);
const result = await controller.getTopEarners(mockRequestUser, undefined);
expect(dashboardService.getTopEarners).toHaveBeenCalledWith(mockTenantId, 10);
expect(result).toEqual(mockTopEarners);
});
it('should return top earners with custom limit', async () => {
dashboardService.getTopEarners.mockResolvedValue(mockTopEarners.slice(0, 1));
const result = await controller.getTopEarners(mockRequestUser, 5);
expect(dashboardService.getTopEarners).toHaveBeenCalledWith(mockTenantId, 5);
});
});
});

View File

@ -0,0 +1,243 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EntriesController } from '../controllers/entries.controller';
import { EntriesService } from '../services/entries.service';
import {
CreateEntryDto,
UpdateEntryDto,
ApproveEntryDto,
RejectEntryDto,
EntryListQueryDto,
CalculateCommissionDto,
} from '../dto';
import { EntryStatus } from '../entities/commission-entry.entity';
describe('EntriesController', () => {
let controller: EntriesController;
let entriesService: jest.Mocked<EntriesService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockEntryId = '550e8400-e29b-41d4-a716-446655440003';
const mockSchemeId = '550e8400-e29b-41d4-a716-446655440004';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockEntry = {
id: mockEntryId,
tenantId: mockTenantId,
userId: mockUserId,
schemeId: mockSchemeId,
referenceType: 'opportunity',
referenceId: 'opp-123',
baseAmount: 10000,
rateApplied: 10,
commissionAmount: 1000,
currency: 'USD',
status: EntryStatus.PENDING,
periodId: null,
notes: 'Commission for closed deal',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
};
const mockPaginatedEntries = {
data: [mockEntry],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockEntriesService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
approve: jest.fn(),
reject: jest.fn(),
calculateCommission: jest.fn(),
bulkApprove: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [EntriesController],
providers: [
{
provide: EntriesService,
useValue: mockEntriesService,
},
],
}).compile();
controller = module.get<EntriesController>(EntriesController);
entriesService = module.get(EntriesService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated entries', async () => {
const query: EntryListQueryDto = { page: 1, limit: 20 };
entriesService.findAll.mockResolvedValue(mockPaginatedEntries);
const result = await controller.findAll(mockRequestUser, query);
expect(entriesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedEntries);
});
it('should filter by status', async () => {
const query: EntryListQueryDto = { page: 1, limit: 20, status: EntryStatus.APPROVED };
entriesService.findAll.mockResolvedValue({ ...mockPaginatedEntries, data: [] });
await controller.findAll(mockRequestUser, query);
expect(entriesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('findMyEntries', () => {
it('should return current user entries', async () => {
const query: EntryListQueryDto = { page: 1, limit: 20 };
entriesService.findAll.mockResolvedValue(mockPaginatedEntries);
const result = await controller.findMyEntries(mockRequestUser, query);
expect(entriesService.findAll).toHaveBeenCalledWith(mockTenantId, { ...query, userId: mockUserId });
expect(result).toEqual(mockPaginatedEntries);
});
});
describe('findOne', () => {
it('should return an entry by id', async () => {
entriesService.findOne.mockResolvedValue(mockEntry);
const result = await controller.findOne(mockRequestUser, mockEntryId);
expect(entriesService.findOne).toHaveBeenCalledWith(mockTenantId, mockEntryId);
expect(result).toEqual(mockEntry);
});
});
describe('create', () => {
it('should create a new entry', async () => {
const createDto: CreateEntryDto = {
userId: mockUserId,
schemeId: mockSchemeId,
referenceType: 'opportunity',
referenceId: 'new-opp-id',
baseAmount: 5000,
};
const createdEntry = { ...mockEntry, ...createDto, id: 'new-entry-id', commissionAmount: 500 };
entriesService.create.mockResolvedValue(createdEntry);
const result = await controller.create(mockRequestUser, createDto);
expect(entriesService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdEntry);
});
});
describe('update', () => {
it('should update an entry', async () => {
const updateDto: UpdateEntryDto = {
notes: 'Updated notes',
};
const updatedEntry = { ...mockEntry, ...updateDto };
entriesService.update.mockResolvedValue(updatedEntry);
const result = await controller.update(mockRequestUser, mockEntryId, updateDto);
expect(entriesService.update).toHaveBeenCalledWith(mockTenantId, mockEntryId, updateDto);
expect(result.notes).toBe('Updated notes');
});
});
describe('approve', () => {
it('should approve an entry', async () => {
const approveDto: ApproveEntryDto = {
notes: 'Approved by manager',
};
const approvedEntry = {
...mockEntry,
status: EntryStatus.APPROVED,
approvedBy: mockUserId,
approvedAt: new Date(),
};
entriesService.approve.mockResolvedValue(approvedEntry);
const result = await controller.approve(mockRequestUser, mockEntryId, approveDto);
expect(entriesService.approve).toHaveBeenCalledWith(mockTenantId, mockEntryId, mockUserId, approveDto);
expect(result.status).toBe(EntryStatus.APPROVED);
});
});
describe('reject', () => {
it('should reject an entry', async () => {
const rejectDto: RejectEntryDto = {
reason: 'Invalid reference',
};
const rejectedEntry = {
...mockEntry,
status: EntryStatus.REJECTED,
};
entriesService.reject.mockResolvedValue(rejectedEntry);
const result = await controller.reject(mockRequestUser, mockEntryId, rejectDto);
expect(entriesService.reject).toHaveBeenCalledWith(mockTenantId, mockEntryId, mockUserId, rejectDto);
expect(result.status).toBe(EntryStatus.REJECTED);
});
});
describe('calculateCommission', () => {
it('should calculate commission', async () => {
const calculateDto: CalculateCommissionDto = {
schemeId: mockSchemeId,
baseAmount: 10000,
};
const calculationResult = {
baseAmount: 10000,
rate: 10,
commissionAmount: 1000,
currency: 'USD',
};
entriesService.calculateCommission.mockResolvedValue(calculationResult);
const result = await controller.calculateCommission(mockRequestUser, calculateDto);
expect(entriesService.calculateCommission).toHaveBeenCalledWith(mockTenantId, calculateDto);
expect(result.commissionAmount).toBe(1000);
});
});
describe('bulkApprove', () => {
it('should bulk approve entries', async () => {
const entryIds = [mockEntryId, 'entry-2', 'entry-3'];
entriesService.bulkApprove.mockResolvedValue(3);
const result = await controller.bulkApprove(mockRequestUser, { entryIds });
expect(entriesService.bulkApprove).toHaveBeenCalledWith(mockTenantId, entryIds, mockUserId);
expect(result).toEqual({ approved: 3 });
});
it('should return count of approved entries', async () => {
const entryIds = [mockEntryId, 'entry-2'];
entriesService.bulkApprove.mockResolvedValue(1); // Only 1 was valid
const result = await controller.bulkApprove(mockRequestUser, { entryIds });
expect(result.approved).toBe(1);
});
});
});

View File

@ -0,0 +1,235 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PeriodsController } from '../controllers/periods.controller';
import { PeriodsService } from '../services/periods.service';
import { CreatePeriodDto, UpdatePeriodDto, ClosePeriodDto, MarkPaidDto, PeriodListQueryDto } from '../dto';
import { PeriodStatus } from '../entities/commission-period.entity';
describe('PeriodsController', () => {
let controller: PeriodsController;
let periodsService: jest.Mocked<PeriodsService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockPeriodId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockPeriod = {
id: mockPeriodId,
tenantId: mockTenantId,
name: 'January 2026',
startsAt: new Date('2026-01-01'),
endsAt: new Date('2026-01-31'),
totalEntries: 10,
totalAmount: 5000,
currency: 'USD',
status: PeriodStatus.OPEN,
closedAt: null,
closedBy: null,
paidAt: null,
paidBy: null,
createdAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedPeriods = {
data: [mockPeriod],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockPeriodsService = {
findAll: jest.fn(),
findOne: jest.fn(),
getCurrentPeriod: jest.fn(),
create: jest.fn(),
update: jest.fn(),
close: jest.fn(),
markAsPaid: jest.fn(),
remove: jest.fn(),
assignEntriesToPeriod: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [PeriodsController],
providers: [
{
provide: PeriodsService,
useValue: mockPeriodsService,
},
],
}).compile();
controller = module.get<PeriodsController>(PeriodsController);
periodsService = module.get(PeriodsService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated periods', async () => {
const query: PeriodListQueryDto = { page: 1, limit: 20 };
periodsService.findAll.mockResolvedValue(mockPaginatedPeriods);
const result = await controller.findAll(mockRequestUser, query);
expect(periodsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedPeriods);
});
it('should filter by status', async () => {
const query: PeriodListQueryDto = { page: 1, limit: 20, status: PeriodStatus.CLOSED };
periodsService.findAll.mockResolvedValue({ ...mockPaginatedPeriods, data: [] });
await controller.findAll(mockRequestUser, query);
expect(periodsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('getCurrentPeriod', () => {
it('should return the current open period', async () => {
periodsService.getCurrentPeriod.mockResolvedValue(mockPeriod);
const result = await controller.getCurrentPeriod(mockRequestUser);
expect(periodsService.getCurrentPeriod).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockPeriod);
});
it('should return null when no current period exists', async () => {
periodsService.getCurrentPeriod.mockResolvedValue(null);
const result = await controller.getCurrentPeriod(mockRequestUser);
expect(result).toBeNull();
});
});
describe('findOne', () => {
it('should return a period by id', async () => {
periodsService.findOne.mockResolvedValue(mockPeriod);
const result = await controller.findOne(mockRequestUser, mockPeriodId);
expect(periodsService.findOne).toHaveBeenCalledWith(mockTenantId, mockPeriodId);
expect(result).toEqual(mockPeriod);
});
});
describe('create', () => {
it('should create a new period', async () => {
const createDto: CreatePeriodDto = {
name: 'February 2026',
startsAt: '2026-02-01',
endsAt: '2026-02-28',
};
const createdPeriod = { ...mockPeriod, ...createDto, id: 'new-period-id' };
periodsService.create.mockResolvedValue(createdPeriod);
const result = await controller.create(mockRequestUser, createDto);
expect(periodsService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdPeriod);
});
});
describe('update', () => {
it('should update a period', async () => {
const updateDto: UpdatePeriodDto = {
name: 'Updated Period Name',
};
const updatedPeriod = { ...mockPeriod, ...updateDto };
periodsService.update.mockResolvedValue(updatedPeriod);
const result = await controller.update(mockRequestUser, mockPeriodId, updateDto);
expect(periodsService.update).toHaveBeenCalledWith(mockTenantId, mockPeriodId, updateDto);
expect(result.name).toBe('Updated Period Name');
});
});
describe('close', () => {
it('should close a period', async () => {
const closeDto: ClosePeriodDto = {
notes: 'Period closed for payroll',
};
const closedPeriod = {
...mockPeriod,
status: PeriodStatus.CLOSED,
closedAt: new Date(),
closedBy: mockUserId,
};
periodsService.close.mockResolvedValue(closedPeriod);
const result = await controller.close(mockRequestUser, mockPeriodId, closeDto);
expect(periodsService.close).toHaveBeenCalledWith(mockTenantId, mockPeriodId, mockUserId, closeDto);
expect(result.status).toBe(PeriodStatus.CLOSED);
});
});
describe('markPaid', () => {
it('should mark a period as paid', async () => {
const markPaidDto: MarkPaidDto = {
paymentReference: 'PAY-2026-001',
paymentNotes: 'Bank transfer completed',
};
const paidPeriod = {
...mockPeriod,
status: PeriodStatus.PAID,
paidAt: new Date(),
paidBy: mockUserId,
paymentReference: markPaidDto.paymentReference,
};
periodsService.markAsPaid.mockResolvedValue(paidPeriod);
const result = await controller.markPaid(mockRequestUser, mockPeriodId, markPaidDto);
expect(periodsService.markAsPaid).toHaveBeenCalledWith(mockTenantId, mockPeriodId, mockUserId, markPaidDto);
expect(result.status).toBe(PeriodStatus.PAID);
});
});
describe('remove', () => {
it('should delete a period', async () => {
periodsService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockPeriodId);
expect(periodsService.remove).toHaveBeenCalledWith(mockTenantId, mockPeriodId);
expect(result).toEqual({ message: 'Commission period deleted successfully' });
});
});
describe('assignEntries', () => {
it('should assign entries to a period', async () => {
const entryIds = ['entry-1', 'entry-2', 'entry-3'];
periodsService.assignEntriesToPeriod.mockResolvedValue(3);
const result = await controller.assignEntries(mockRequestUser, mockPeriodId, { entryIds });
expect(periodsService.assignEntriesToPeriod).toHaveBeenCalledWith(mockTenantId, mockPeriodId, entryIds);
expect(result).toEqual({ assigned: 3 });
});
it('should return count of assigned entries', async () => {
const entryIds = ['entry-1', 'entry-2'];
periodsService.assignEntriesToPeriod.mockResolvedValue(1);
const result = await controller.assignEntries(mockRequestUser, mockPeriodId, { entryIds });
expect(result.assigned).toBe(1);
});
});
});

View File

@ -0,0 +1,196 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SchemesController } from '../controllers/schemes.controller';
import { SchemesService } from '../services/schemes.service';
import { CreateSchemeDto, UpdateSchemeDto, SchemeListQueryDto } from '../dto';
import { SchemeType, AppliesTo } from '../entities/commission-scheme.entity';
describe('SchemesController', () => {
let controller: SchemesController;
let schemesService: jest.Mocked<SchemesService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockSchemeId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockScheme = {
id: mockSchemeId,
tenantId: mockTenantId,
name: 'Sales Commission',
description: '10% on all sales',
type: SchemeType.PERCENTAGE,
rate: 10,
fixedAmount: 0,
tiers: [],
appliesTo: AppliesTo.ALL,
productIds: [],
categoryIds: [],
minAmount: 0,
maxAmount: null,
isActive: true,
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedSchemes = {
data: [mockScheme],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockSchemesService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
activate: jest.fn(),
deactivate: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [SchemesController],
providers: [
{
provide: SchemesService,
useValue: mockSchemesService,
},
],
}).compile();
controller = module.get<SchemesController>(SchemesController);
schemesService = module.get(SchemesService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated schemes', async () => {
const query: SchemeListQueryDto = { page: 1, limit: 20 };
schemesService.findAll.mockResolvedValue(mockPaginatedSchemes);
const result = await controller.findAll(mockRequestUser, query);
expect(schemesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedSchemes);
});
it('should filter by type', async () => {
const query: SchemeListQueryDto = { page: 1, limit: 20, type: SchemeType.TIERED };
schemesService.findAll.mockResolvedValue({ ...mockPaginatedSchemes, data: [] });
await controller.findAll(mockRequestUser, query);
expect(schemesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('findOne', () => {
it('should return a scheme by id', async () => {
schemesService.findOne.mockResolvedValue(mockScheme);
const result = await controller.findOne(mockRequestUser, mockSchemeId);
expect(schemesService.findOne).toHaveBeenCalledWith(mockTenantId, mockSchemeId);
expect(result).toEqual(mockScheme);
});
});
describe('create', () => {
it('should create a new scheme', async () => {
const createDto: CreateSchemeDto = {
name: 'Premium Commission',
type: SchemeType.PERCENTAGE,
rate: 15,
appliesTo: AppliesTo.PRODUCTS,
};
const createdScheme = { ...mockScheme, ...createDto, id: 'new-scheme-id' };
schemesService.create.mockResolvedValue(createdScheme);
const result = await controller.create(mockRequestUser, createDto);
expect(schemesService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdScheme);
});
it('should create a tiered scheme', async () => {
const createDto: CreateSchemeDto = {
name: 'Tiered Commission',
type: SchemeType.TIERED,
tiers: [
{ minAmount: 0, maxAmount: 1000, rate: 5 },
{ minAmount: 1001, maxAmount: 5000, rate: 10 },
{ minAmount: 5001, rate: 15 },
],
};
schemesService.create.mockResolvedValue({ ...mockScheme, ...createDto });
await controller.create(mockRequestUser, createDto);
expect(schemesService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
});
});
describe('update', () => {
it('should update a scheme', async () => {
const updateDto: UpdateSchemeDto = {
rate: 12,
description: 'Updated commission description',
};
const updatedScheme = { ...mockScheme, ...updateDto };
schemesService.update.mockResolvedValue(updatedScheme);
const result = await controller.update(mockRequestUser, mockSchemeId, updateDto);
expect(schemesService.update).toHaveBeenCalledWith(mockTenantId, mockSchemeId, updateDto);
expect(result.rate).toBe(12);
});
});
describe('remove', () => {
it('should delete a scheme', async () => {
schemesService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockSchemeId);
expect(schemesService.remove).toHaveBeenCalledWith(mockTenantId, mockSchemeId);
expect(result).toEqual({ message: 'Commission scheme deleted successfully' });
});
});
describe('activate', () => {
it('should activate a scheme', async () => {
const activatedScheme = { ...mockScheme, isActive: true };
schemesService.activate.mockResolvedValue(activatedScheme);
const result = await controller.activate(mockRequestUser, mockSchemeId);
expect(schemesService.activate).toHaveBeenCalledWith(mockTenantId, mockSchemeId);
expect(result.isActive).toBe(true);
});
});
describe('deactivate', () => {
it('should deactivate a scheme', async () => {
const deactivatedScheme = { ...mockScheme, isActive: false };
schemesService.deactivate.mockResolvedValue(deactivatedScheme);
const result = await controller.deactivate(mockRequestUser, mockSchemeId);
expect(schemesService.deactivate).toHaveBeenCalledWith(mockTenantId, mockSchemeId);
expect(result.isActive).toBe(false);
});
});
});

View File

@ -0,0 +1,235 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CategoriesController } from '../controllers/categories.controller';
import { CategoriesService } from '../services/categories.service';
import { CreateCategoryDto, UpdateCategoryDto, CategoryListQueryDto } from '../dto';
describe('CategoriesController', () => {
let controller: CategoriesController;
let categoriesService: jest.Mocked<CategoriesService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockCategoryId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockCategory = {
id: mockCategoryId,
tenantId: mockTenantId,
parentId: null,
name: 'Electronics',
slug: 'electronics',
description: 'Electronic products',
position: 1,
imageUrl: null,
color: '#3B82F6',
icon: 'laptop',
isActive: true,
metaTitle: null,
metaDescription: null,
customFields: {},
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedCategories = {
data: [mockCategory],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
const mockCategoryTree = [
{
...mockCategory,
children: [
{
id: 'child-1',
name: 'Phones',
slug: 'phones',
parentId: mockCategoryId,
children: [],
},
{
id: 'child-2',
name: 'Laptops',
slug: 'laptops',
parentId: mockCategoryId,
children: [],
},
],
},
];
beforeEach(async () => {
const mockCategoriesService = {
findAll: jest.fn(),
findOne: jest.fn(),
getTree: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [CategoriesController],
providers: [
{
provide: CategoriesService,
useValue: mockCategoriesService,
},
],
}).compile();
controller = module.get<CategoriesController>(CategoriesController);
categoriesService = module.get(CategoriesService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated categories', async () => {
const query: CategoryListQueryDto = { page: 1, limit: 20 };
categoriesService.findAll.mockResolvedValue(mockPaginatedCategories);
const result = await controller.findAll(mockRequestUser, query);
expect(categoriesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedCategories);
});
it('should filter by active status', async () => {
const query: CategoryListQueryDto = { page: 1, limit: 20, isActive: true };
categoriesService.findAll.mockResolvedValue(mockPaginatedCategories);
await controller.findAll(mockRequestUser, query);
expect(categoriesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
it('should search by name', async () => {
const query: CategoryListQueryDto = { page: 1, limit: 20, search: 'electronics' };
categoriesService.findAll.mockResolvedValue(mockPaginatedCategories);
await controller.findAll(mockRequestUser, query);
expect(categoriesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('getTree', () => {
it('should return category tree structure', async () => {
categoriesService.getTree.mockResolvedValue(mockCategoryTree);
const result = await controller.getTree(mockRequestUser);
expect(categoriesService.getTree).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockCategoryTree);
expect(result[0].children.length).toBe(2);
});
});
describe('findOne', () => {
it('should return a category by id', async () => {
categoriesService.findOne.mockResolvedValue(mockCategory);
const result = await controller.findOne(mockRequestUser, mockCategoryId);
expect(categoriesService.findOne).toHaveBeenCalledWith(mockTenantId, mockCategoryId);
expect(result).toEqual(mockCategory);
});
});
describe('create', () => {
it('should create a new category', async () => {
const createDto: CreateCategoryDto = {
name: 'Clothing',
slug: 'clothing',
description: 'Fashion and clothing',
};
const createdCategory = { ...mockCategory, ...createDto, id: 'new-category-id' };
categoriesService.create.mockResolvedValue(createdCategory);
const result = await controller.create(mockRequestUser, createDto);
expect(categoriesService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdCategory);
});
it('should create a subcategory', async () => {
const createDto: CreateCategoryDto = {
name: 'Smartphones',
slug: 'smartphones',
parentId: mockCategoryId,
};
const createdSubcategory = { ...mockCategory, ...createDto, id: 'sub-category-id' };
categoriesService.create.mockResolvedValue(createdSubcategory);
const result = await controller.create(mockRequestUser, createDto);
expect(result.parentId).toBe(mockCategoryId);
});
});
describe('update', () => {
it('should update a category', async () => {
const updateDto: UpdateCategoryDto = {
name: 'Updated Electronics',
color: '#EF4444',
};
const updatedCategory = { ...mockCategory, ...updateDto };
categoriesService.update.mockResolvedValue(updatedCategory);
const result = await controller.update(mockRequestUser, mockCategoryId, updateDto);
expect(categoriesService.update).toHaveBeenCalledWith(mockTenantId, mockCategoryId, updateDto);
expect(result.name).toBe('Updated Electronics');
expect(result.color).toBe('#EF4444');
});
it('should deactivate a category', async () => {
const updateDto: UpdateCategoryDto = {
isActive: false,
};
const deactivatedCategory = { ...mockCategory, isActive: false };
categoriesService.update.mockResolvedValue(deactivatedCategory);
const result = await controller.update(mockRequestUser, mockCategoryId, updateDto);
expect(result.isActive).toBe(false);
});
it('should move category to different parent', async () => {
const newParentId = 'new-parent-id';
const updateDto: UpdateCategoryDto = {
parentId: newParentId,
};
const movedCategory = { ...mockCategory, parentId: newParentId };
categoriesService.update.mockResolvedValue(movedCategory);
const result = await controller.update(mockRequestUser, mockCategoryId, updateDto);
expect(result.parentId).toBe(newParentId);
});
});
describe('remove', () => {
it('should delete a category', async () => {
categoriesService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockCategoryId);
expect(categoriesService.remove).toHaveBeenCalledWith(mockTenantId, mockCategoryId);
expect(result).toEqual({ message: 'Category deleted successfully' });
});
});
});

View File

@ -0,0 +1,367 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ProductsController } from '../controllers/products.controller';
import { ProductsService } from '../services/products.service';
import {
CreateProductDto,
UpdateProductDto,
UpdateProductStatusDto,
ProductListQueryDto,
CreateVariantDto,
UpdateVariantDto,
CreatePriceDto,
UpdatePriceDto,
} from '../dto';
import { ProductType, ProductStatus } from '../entities/product.entity';
describe('ProductsController', () => {
let controller: ProductsController;
let productsService: jest.Mocked<ProductsService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockProductId = '550e8400-e29b-41d4-a716-446655440003';
const mockVariantId = '550e8400-e29b-41d4-a716-446655440004';
const mockPriceId = '550e8400-e29b-41d4-a716-446655440005';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockProduct = {
id: mockProductId,
tenantId: mockTenantId,
categoryId: 'cat-123',
name: 'Wireless Headphones',
slug: 'wireless-headphones',
sku: 'WH-001',
barcode: '1234567890123',
description: 'Premium wireless headphones',
shortDescription: 'High quality wireless headphones',
productType: ProductType.PHYSICAL,
status: ProductStatus.ACTIVE,
basePrice: 149.99,
costPrice: 80,
compareAtPrice: 199.99,
currency: 'USD',
trackInventory: true,
stockQuantity: 100,
lowStockThreshold: 10,
allowBackorder: false,
weight: 0.5,
images: [],
tags: ['audio', 'wireless'],
isVisible: true,
isFeatured: false,
hasVariants: true,
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockVariant = {
id: mockVariantId,
productId: mockProductId,
name: 'Black',
sku: 'WH-001-BLK',
price: 149.99,
stockQuantity: 50,
attributes: { color: 'Black' },
isDefault: true,
createdAt: new Date('2026-01-01'),
};
const mockPrice = {
id: mockPriceId,
productId: mockProductId,
name: 'Retail',
amount: 149.99,
currency: 'USD',
isDefault: true,
minQuantity: 1,
createdAt: new Date('2026-01-01'),
};
const mockPaginatedProducts = {
data: [mockProduct],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockProductsService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
updateStatus: jest.fn(),
duplicate: jest.fn(),
remove: jest.fn(),
getVariants: jest.fn(),
createVariant: jest.fn(),
updateVariant: jest.fn(),
removeVariant: jest.fn(),
getPrices: jest.fn(),
createPrice: jest.fn(),
updatePrice: jest.fn(),
removePrice: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [ProductsController],
providers: [
{
provide: ProductsService,
useValue: mockProductsService,
},
],
}).compile();
controller = module.get<ProductsController>(ProductsController);
productsService = module.get(ProductsService);
});
afterEach(() => {
jest.clearAllMocks();
});
// ============================================
// Products Tests
// ============================================
describe('findAll', () => {
it('should return paginated products', async () => {
const query: ProductListQueryDto = { page: 1, limit: 20 };
productsService.findAll.mockResolvedValue(mockPaginatedProducts);
const result = await controller.findAll(mockRequestUser, query);
expect(productsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedProducts);
});
it('should filter by status', async () => {
const query: ProductListQueryDto = { page: 1, limit: 20, status: ProductStatus.ACTIVE };
productsService.findAll.mockResolvedValue(mockPaginatedProducts);
await controller.findAll(mockRequestUser, query);
expect(productsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
it('should filter by category', async () => {
const query: ProductListQueryDto = { page: 1, limit: 20, categoryId: 'cat-123' };
productsService.findAll.mockResolvedValue(mockPaginatedProducts);
await controller.findAll(mockRequestUser, query);
expect(productsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('findOne', () => {
it('should return a product by id', async () => {
productsService.findOne.mockResolvedValue(mockProduct);
const result = await controller.findOne(mockRequestUser, mockProductId);
expect(productsService.findOne).toHaveBeenCalledWith(mockTenantId, mockProductId);
expect(result).toEqual(mockProduct);
});
});
describe('create', () => {
it('should create a new product', async () => {
const createDto: CreateProductDto = {
name: 'New Headphones',
slug: 'new-headphones',
basePrice: 99.99,
productType: ProductType.PHYSICAL,
};
const createdProduct = { ...mockProduct, ...createDto, id: 'new-product-id' };
productsService.create.mockResolvedValue(createdProduct);
const result = await controller.create(mockRequestUser, createDto);
expect(productsService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdProduct);
});
});
describe('update', () => {
it('should update a product', async () => {
const updateDto: UpdateProductDto = {
name: 'Updated Headphones',
basePrice: 129.99,
};
const updatedProduct = { ...mockProduct, ...updateDto };
productsService.update.mockResolvedValue(updatedProduct);
const result = await controller.update(mockRequestUser, mockProductId, updateDto);
expect(productsService.update).toHaveBeenCalledWith(mockTenantId, mockProductId, updateDto);
expect(result.name).toBe('Updated Headphones');
});
});
describe('updateStatus', () => {
it('should update product status', async () => {
const statusDto: UpdateProductStatusDto = {
status: ProductStatus.INACTIVE,
};
const updatedProduct = { ...mockProduct, status: ProductStatus.INACTIVE };
productsService.updateStatus.mockResolvedValue(updatedProduct);
const result = await controller.updateStatus(mockRequestUser, mockProductId, statusDto);
expect(productsService.updateStatus).toHaveBeenCalledWith(mockTenantId, mockProductId, statusDto);
expect(result.status).toBe(ProductStatus.INACTIVE);
});
});
describe('duplicate', () => {
it('should duplicate a product', async () => {
const duplicatedProduct = { ...mockProduct, id: 'duplicated-id', name: 'Wireless Headphones (Copy)' };
productsService.duplicate.mockResolvedValue(duplicatedProduct);
const result = await controller.duplicate(mockRequestUser, mockProductId);
expect(productsService.duplicate).toHaveBeenCalledWith(mockTenantId, mockUserId, mockProductId);
expect(result.id).toBe('duplicated-id');
});
});
describe('remove', () => {
it('should delete a product', async () => {
productsService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockProductId);
expect(productsService.remove).toHaveBeenCalledWith(mockTenantId, mockProductId);
expect(result).toEqual({ message: 'Product deleted successfully' });
});
});
// ============================================
// Variants Tests
// ============================================
describe('getVariants', () => {
it('should return product variants', async () => {
productsService.getVariants.mockResolvedValue([mockVariant]);
const result = await controller.getVariants(mockRequestUser, mockProductId);
expect(productsService.getVariants).toHaveBeenCalledWith(mockTenantId, mockProductId);
expect(result).toEqual([mockVariant]);
});
});
describe('createVariant', () => {
it('should create a product variant', async () => {
const createDto: CreateVariantDto = {
name: 'White',
sku: 'WH-001-WHT',
price: 149.99,
attributes: { color: 'White' },
};
const createdVariant = { ...mockVariant, ...createDto, id: 'new-variant-id' };
productsService.createVariant.mockResolvedValue(createdVariant);
const result = await controller.createVariant(mockRequestUser, mockProductId, createDto);
expect(productsService.createVariant).toHaveBeenCalledWith(mockTenantId, mockProductId, createDto);
expect(result).toEqual(createdVariant);
});
});
describe('updateVariant', () => {
it('should update a product variant', async () => {
const updateDto: UpdateVariantDto = {
price: 139.99,
stockQuantity: 75,
};
const updatedVariant = { ...mockVariant, ...updateDto };
productsService.updateVariant.mockResolvedValue(updatedVariant);
const result = await controller.updateVariant(mockRequestUser, mockProductId, mockVariantId, updateDto);
expect(productsService.updateVariant).toHaveBeenCalledWith(mockTenantId, mockProductId, mockVariantId, updateDto);
expect(result.price).toBe(139.99);
});
});
describe('removeVariant', () => {
it('should delete a product variant', async () => {
productsService.removeVariant.mockResolvedValue(undefined);
const result = await controller.removeVariant(mockRequestUser, mockProductId, mockVariantId);
expect(productsService.removeVariant).toHaveBeenCalledWith(mockTenantId, mockProductId, mockVariantId);
expect(result).toEqual({ message: 'Variant deleted successfully' });
});
});
// ============================================
// Prices Tests
// ============================================
describe('getPrices', () => {
it('should return product prices', async () => {
productsService.getPrices.mockResolvedValue([mockPrice]);
const result = await controller.getPrices(mockRequestUser, mockProductId);
expect(productsService.getPrices).toHaveBeenCalledWith(mockTenantId, mockProductId);
expect(result).toEqual([mockPrice]);
});
});
describe('createPrice', () => {
it('should create a product price', async () => {
const createDto: CreatePriceDto = {
name: 'Wholesale',
amount: 99.99,
currency: 'USD',
minQuantity: 10,
};
const createdPrice = { ...mockPrice, ...createDto, id: 'new-price-id' };
productsService.createPrice.mockResolvedValue(createdPrice);
const result = await controller.createPrice(mockRequestUser, mockProductId, createDto);
expect(productsService.createPrice).toHaveBeenCalledWith(mockTenantId, mockProductId, createDto);
expect(result).toEqual(createdPrice);
});
});
describe('updatePrice', () => {
it('should update a product price', async () => {
const updateDto: UpdatePriceDto = {
amount: 159.99,
};
const updatedPrice = { ...mockPrice, ...updateDto };
productsService.updatePrice.mockResolvedValue(updatedPrice);
const result = await controller.updatePrice(mockRequestUser, mockProductId, mockPriceId, updateDto);
expect(productsService.updatePrice).toHaveBeenCalledWith(mockTenantId, mockProductId, mockPriceId, updateDto);
expect(result.amount).toBe(159.99);
});
});
describe('removePrice', () => {
it('should delete a product price', async () => {
productsService.removePrice.mockResolvedValue(undefined);
const result = await controller.removePrice(mockRequestUser, mockProductId, mockPriceId);
expect(productsService.removePrice).toHaveBeenCalledWith(mockTenantId, mockProductId, mockPriceId);
expect(result).toEqual({ message: 'Price deleted successfully' });
});
});
});

View File

@ -0,0 +1,179 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ActivitiesController } from '../controllers/activities.controller';
import { ActivitiesService } from '../services/activities.service';
import { CreateActivityDto, UpdateActivityDto, CompleteActivityDto, ActivityListQueryDto } from '../dto';
import { ActivityType, ActivityStatus } from '../entities/activity.entity';
describe('ActivitiesController', () => {
let controller: ActivitiesController;
let activitiesService: jest.Mocked<ActivitiesService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockActivityId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockActivity = {
id: mockActivityId,
tenantId: mockTenantId,
type: ActivityType.CALL,
status: ActivityStatus.PENDING,
subject: 'Follow up call',
description: 'Discuss proposal',
dueDate: new Date('2026-02-10'),
assignedTo: mockUserId,
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedActivities = {
data: [mockActivity],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockActivitiesService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
complete: jest.fn(),
remove: jest.fn(),
getUpcoming: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [ActivitiesController],
providers: [
{
provide: ActivitiesService,
useValue: mockActivitiesService,
},
],
}).compile();
controller = module.get<ActivitiesController>(ActivitiesController);
activitiesService = module.get(ActivitiesService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated activities', async () => {
const query: ActivityListQueryDto = { page: 1, limit: 20 };
activitiesService.findAll.mockResolvedValue(mockPaginatedActivities);
const result = await controller.findAll(mockRequestUser, query);
expect(activitiesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedActivities);
});
it('should filter by type', async () => {
const query: ActivityListQueryDto = { page: 1, limit: 20, type: ActivityType.MEETING };
activitiesService.findAll.mockResolvedValue({ ...mockPaginatedActivities, data: [] });
await controller.findAll(mockRequestUser, query);
expect(activitiesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('getUpcoming', () => {
it('should return upcoming activities', async () => {
activitiesService.getUpcoming.mockResolvedValue([mockActivity]);
const result = await controller.getUpcoming(mockRequestUser);
expect(activitiesService.getUpcoming).toHaveBeenCalledWith(mockTenantId, mockUserId);
expect(result).toEqual([mockActivity]);
});
});
describe('findOne', () => {
it('should return an activity by id', async () => {
activitiesService.findOne.mockResolvedValue(mockActivity);
const result = await controller.findOne(mockRequestUser, mockActivityId);
expect(activitiesService.findOne).toHaveBeenCalledWith(mockTenantId, mockActivityId);
expect(result).toEqual(mockActivity);
});
});
describe('create', () => {
it('should create a new activity', async () => {
const createDto: CreateActivityDto = {
type: ActivityType.CALL,
subject: 'Initial call',
dueDate: '2026-02-15',
};
const createdActivity = { ...mockActivity, ...createDto, id: 'new-activity-id' };
activitiesService.create.mockResolvedValue(createdActivity);
const result = await controller.create(mockRequestUser, createDto);
expect(activitiesService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdActivity);
});
});
describe('update', () => {
it('should update an activity', async () => {
const updateDto: UpdateActivityDto = {
subject: 'Updated call subject',
status: ActivityStatus.COMPLETED,
};
const updatedActivity = { ...mockActivity, ...updateDto };
activitiesService.update.mockResolvedValue(updatedActivity);
const result = await controller.update(mockRequestUser, mockActivityId, updateDto);
expect(activitiesService.update).toHaveBeenCalledWith(mockTenantId, mockActivityId, updateDto);
expect(result.subject).toBe('Updated call subject');
});
});
describe('complete', () => {
it('should complete an activity', async () => {
const completeDto: CompleteActivityDto = {
outcome: 'Discussed proposal details',
};
const completedActivity = {
...mockActivity,
status: ActivityStatus.COMPLETED,
completedAt: new Date(),
outcome: completeDto.outcome,
};
activitiesService.complete.mockResolvedValue(completedActivity);
const result = await controller.complete(mockRequestUser, mockActivityId, completeDto);
expect(activitiesService.complete).toHaveBeenCalledWith(mockTenantId, mockActivityId, completeDto);
expect(result.status).toBe(ActivityStatus.COMPLETED);
});
});
describe('remove', () => {
it('should delete an activity', async () => {
activitiesService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockActivityId);
expect(activitiesService.remove).toHaveBeenCalledWith(mockTenantId, mockActivityId);
expect(result).toEqual({ message: 'Activity deleted successfully' });
});
});
});

View File

@ -0,0 +1,171 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SalesDashboardController } from '../controllers/dashboard.controller';
import { SalesDashboardService } from '../services/sales-dashboard.service';
import { LeadStatus, LeadSource } from '../entities/lead.entity';
import { OpportunityStage } from '../entities/opportunity.entity';
describe('SalesDashboardController', () => {
let controller: SalesDashboardController;
let dashboardService: jest.Mocked<SalesDashboardService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockDashboardSummary = {
totalLeads: 150,
newLeadsThisMonth: 25,
qualifiedLeads: 45,
convertedLeads: 30,
totalOpportunities: 80,
openOpportunities: 50,
wonOpportunities: 25,
lostOpportunities: 5,
totalPipelineValue: 500000,
wonValue: 150000,
averageDealSize: 6000,
winRate: 83.33,
};
const mockLeadsByStatus = [
{ status: LeadStatus.NEW, count: 50 },
{ status: LeadStatus.CONTACTED, count: 35 },
{ status: LeadStatus.QUALIFIED, count: 45 },
{ status: LeadStatus.CONVERTED, count: 20 },
];
const mockLeadsBySource = [
{ source: LeadSource.WEBSITE, count: 60 },
{ source: LeadSource.REFERRAL, count: 40 },
{ source: LeadSource.SOCIAL_MEDIA, count: 25 },
{ source: LeadSource.EVENT, count: 25 },
];
const mockOpportunitiesByStage = [
{ stage: OpportunityStage.PROSPECTING, count: 15, value: 75000 },
{ stage: OpportunityStage.QUALIFICATION, count: 12, value: 60000 },
{ stage: OpportunityStage.PROPOSAL, count: 10, value: 100000 },
{ stage: OpportunityStage.NEGOTIATION, count: 8, value: 120000 },
{ stage: OpportunityStage.CLOSED_WON, count: 25, value: 150000 },
];
const mockConversionFunnel = [
{ stage: 'Lead Created', count: 150, percentage: 100 },
{ stage: 'Qualified', count: 45, percentage: 30 },
{ stage: 'Opportunity Created', count: 35, percentage: 23 },
{ stage: 'Proposal Sent', count: 25, percentage: 17 },
{ stage: 'Won', count: 20, percentage: 13 },
];
const mockSalesPerformance = [
{ userId: mockUserId, name: 'John Doe', leadsConverted: 10, opportunitiesWon: 5, totalValue: 50000 },
{ userId: 'user-2', name: 'Jane Smith', leadsConverted: 8, opportunitiesWon: 4, totalValue: 40000 },
];
beforeEach(async () => {
const mockDashboardService = {
getDashboardSummary: jest.fn(),
getLeadsByStatus: jest.fn(),
getLeadsBySource: jest.fn(),
getOpportunitiesByStage: jest.fn(),
getConversionFunnel: jest.fn(),
getSalesPerformance: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [SalesDashboardController],
providers: [
{
provide: SalesDashboardService,
useValue: mockDashboardService,
},
],
}).compile();
controller = module.get<SalesDashboardController>(SalesDashboardController);
dashboardService = module.get(SalesDashboardService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getSummary', () => {
it('should return dashboard summary', async () => {
dashboardService.getDashboardSummary.mockResolvedValue(mockDashboardSummary);
const result = await controller.getSummary(mockRequestUser);
expect(dashboardService.getDashboardSummary).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockDashboardSummary);
expect(result.totalLeads).toBe(150);
expect(result.winRate).toBe(83.33);
});
});
describe('getLeadsByStatus', () => {
it('should return leads grouped by status', async () => {
dashboardService.getLeadsByStatus.mockResolvedValue(mockLeadsByStatus);
const result = await controller.getLeadsByStatus(mockRequestUser);
expect(dashboardService.getLeadsByStatus).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockLeadsByStatus);
expect(result.length).toBe(4);
});
});
describe('getLeadsBySource', () => {
it('should return leads grouped by source', async () => {
dashboardService.getLeadsBySource.mockResolvedValue(mockLeadsBySource);
const result = await controller.getLeadsBySource(mockRequestUser);
expect(dashboardService.getLeadsBySource).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockLeadsBySource);
expect(result.length).toBe(4);
});
});
describe('getOpportunitiesByStage', () => {
it('should return opportunities grouped by stage', async () => {
dashboardService.getOpportunitiesByStage.mockResolvedValue(mockOpportunitiesByStage);
const result = await controller.getOpportunitiesByStage(mockRequestUser);
expect(dashboardService.getOpportunitiesByStage).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockOpportunitiesByStage);
expect(result.length).toBe(5);
});
});
describe('getConversionFunnel', () => {
it('should return conversion funnel data', async () => {
dashboardService.getConversionFunnel.mockResolvedValue(mockConversionFunnel);
const result = await controller.getConversionFunnel(mockRequestUser);
expect(dashboardService.getConversionFunnel).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockConversionFunnel);
expect(result[0].percentage).toBe(100);
});
});
describe('getSalesPerformance', () => {
it('should return sales performance by user', async () => {
dashboardService.getSalesPerformance.mockResolvedValue(mockSalesPerformance);
const result = await controller.getSalesPerformance(mockRequestUser);
expect(dashboardService.getSalesPerformance).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockSalesPerformance);
expect(result.length).toBe(2);
});
});
});

View File

@ -0,0 +1,219 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LeadsController } from '../controllers/leads.controller';
import { LeadsService } from '../services/leads.service';
import { CreateLeadDto, UpdateLeadDto, ConvertLeadDto, LeadListQueryDto } from '../dto';
import { LeadStatus, LeadSource } from '../entities/lead.entity';
describe('LeadsController', () => {
let controller: LeadsController;
let leadsService: jest.Mocked<LeadsService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockLeadId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockLead = {
id: mockLeadId,
tenantId: mockTenantId,
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
phone: '+1234567890',
company: 'Acme Corp',
jobTitle: 'CEO',
source: LeadSource.WEBSITE,
status: LeadStatus.NEW,
score: 50,
assignedTo: mockUserId,
notes: 'Interested in enterprise plan',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedLeads = {
data: [mockLead],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
beforeEach(async () => {
const mockLeadsService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
convert: jest.fn(),
calculateScore: jest.fn(),
assign: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [LeadsController],
providers: [
{
provide: LeadsService,
useValue: mockLeadsService,
},
],
}).compile();
controller = module.get<LeadsController>(LeadsController);
leadsService = module.get(LeadsService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated leads', async () => {
const query: LeadListQueryDto = { page: 1, limit: 20 };
leadsService.findAll.mockResolvedValue(mockPaginatedLeads);
const result = await controller.findAll(mockRequestUser, query);
expect(leadsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedLeads);
});
it('should filter by status', async () => {
const query: LeadListQueryDto = { page: 1, limit: 20, status: LeadStatus.QUALIFIED };
leadsService.findAll.mockResolvedValue({ ...mockPaginatedLeads, data: [] });
await controller.findAll(mockRequestUser, query);
expect(leadsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
it('should filter by search term', async () => {
const query: LeadListQueryDto = { page: 1, limit: 20, search: 'john' };
leadsService.findAll.mockResolvedValue(mockPaginatedLeads);
await controller.findAll(mockRequestUser, query);
expect(leadsService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('findOne', () => {
it('should return a lead by id', async () => {
leadsService.findOne.mockResolvedValue(mockLead);
const result = await controller.findOne(mockRequestUser, mockLeadId);
expect(leadsService.findOne).toHaveBeenCalledWith(mockTenantId, mockLeadId);
expect(result).toEqual(mockLead);
});
});
describe('create', () => {
it('should create a new lead', async () => {
const createDto: CreateLeadDto = {
firstName: 'Jane',
lastName: 'Smith',
email: 'jane.smith@example.com',
source: LeadSource.REFERRAL,
};
const createdLead = { ...mockLead, ...createDto, id: 'new-lead-id' };
leadsService.create.mockResolvedValue(createdLead);
const result = await controller.create(mockRequestUser, createDto);
expect(leadsService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdLead);
});
it('should create lead with all fields', async () => {
const createDto: CreateLeadDto = {
firstName: 'Jane',
lastName: 'Smith',
email: 'jane.smith@example.com',
phone: '+1987654321',
company: 'Tech Corp',
jobTitle: 'CTO',
source: LeadSource.EVENT,
notes: 'Met at conference',
};
leadsService.create.mockResolvedValue({ ...mockLead, ...createDto });
await controller.create(mockRequestUser, createDto);
expect(leadsService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
});
});
describe('update', () => {
it('should update a lead', async () => {
const updateDto: UpdateLeadDto = {
status: LeadStatus.CONTACTED,
notes: 'Followed up via phone',
};
const updatedLead = { ...mockLead, ...updateDto };
leadsService.update.mockResolvedValue(updatedLead);
const result = await controller.update(mockRequestUser, mockLeadId, updateDto);
expect(leadsService.update).toHaveBeenCalledWith(mockTenantId, mockLeadId, updateDto);
expect(result.status).toBe(LeadStatus.CONTACTED);
});
it('should update lead score', async () => {
const updateDto: UpdateLeadDto = { score: 85 };
leadsService.update.mockResolvedValue({ ...mockLead, score: 85 });
const result = await controller.update(mockRequestUser, mockLeadId, updateDto);
expect(result.score).toBe(85);
});
});
describe('remove', () => {
it('should delete a lead', async () => {
leadsService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockLeadId);
expect(leadsService.remove).toHaveBeenCalledWith(mockTenantId, mockLeadId);
expect(result).toEqual({ message: 'Lead deleted successfully' });
});
});
describe('convert', () => {
it('should convert lead to opportunity', async () => {
const convertDto: ConvertLeadDto = {
opportunityName: 'Enterprise Deal',
amount: 50000,
expectedCloseDate: '2026-03-01',
};
const opportunityId = 'new-opportunity-id';
leadsService.convert.mockResolvedValue({ opportunityId });
const result = await controller.convert(mockRequestUser, mockLeadId, convertDto);
expect(leadsService.convert).toHaveBeenCalledWith(mockTenantId, mockLeadId, convertDto);
expect(result).toEqual({ opportunityId });
});
});
describe('calculateScore', () => {
it('should calculate lead score', async () => {
leadsService.calculateScore.mockResolvedValue({ score: 75 });
const result = await controller.calculateScore(mockRequestUser, mockLeadId);
expect(leadsService.calculateScore).toHaveBeenCalledWith(mockTenantId, mockLeadId);
expect(result).toEqual({ score: 75 });
});
});
});

View File

@ -0,0 +1,200 @@
import { Test, TestingModule } from '@nestjs/testing';
import { OpportunitiesController } from '../controllers/opportunities.controller';
import { OpportunitiesService } from '../services/opportunities.service';
import { CreateOpportunityDto, UpdateOpportunityDto, UpdateOpportunityStageDto, OpportunityListQueryDto } from '../dto';
import { OpportunityStage } from '../entities/opportunity.entity';
describe('OpportunitiesController', () => {
let controller: OpportunitiesController;
let opportunitiesService: jest.Mocked<OpportunitiesService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockOpportunityId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockOpportunity = {
id: mockOpportunityId,
tenantId: mockTenantId,
name: 'Enterprise Deal',
description: 'Large enterprise contract',
stage: OpportunityStage.PROSPECTING,
amount: 50000,
currency: 'USD',
probability: 25,
expectedCloseDate: new Date('2026-03-01'),
assignedTo: mockUserId,
contactName: 'John Doe',
contactEmail: 'john@example.com',
companyName: 'Acme Corp',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
createdBy: mockUserId,
};
const mockPaginatedOpportunities = {
data: [mockOpportunity],
total: 1,
page: 1,
limit: 20,
totalPages: 1,
};
const mockPipelineSummary = [
{ stage: OpportunityStage.PROSPECTING, count: 10, value: 100000 },
{ stage: OpportunityStage.QUALIFICATION, count: 8, value: 80000 },
{ stage: OpportunityStage.PROPOSAL, count: 5, value: 75000 },
{ stage: OpportunityStage.NEGOTIATION, count: 3, value: 90000 },
{ stage: OpportunityStage.CLOSED_WON, count: 2, value: 60000 },
];
beforeEach(async () => {
const mockOpportunitiesService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
updateStage: jest.fn(),
remove: jest.fn(),
getPipelineSummary: jest.fn(),
assignTo: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [OpportunitiesController],
providers: [
{
provide: OpportunitiesService,
useValue: mockOpportunitiesService,
},
],
}).compile();
controller = module.get<OpportunitiesController>(OpportunitiesController);
opportunitiesService = module.get(OpportunitiesService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated opportunities', async () => {
const query: OpportunityListQueryDto = { page: 1, limit: 20 };
opportunitiesService.findAll.mockResolvedValue(mockPaginatedOpportunities);
const result = await controller.findAll(mockRequestUser, query);
expect(opportunitiesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
expect(result).toEqual(mockPaginatedOpportunities);
});
it('should filter by stage', async () => {
const query: OpportunityListQueryDto = { page: 1, limit: 20, stage: OpportunityStage.PROPOSAL };
opportunitiesService.findAll.mockResolvedValue({ ...mockPaginatedOpportunities, data: [] });
await controller.findAll(mockRequestUser, query);
expect(opportunitiesService.findAll).toHaveBeenCalledWith(mockTenantId, query);
});
});
describe('getPipelineSummary', () => {
it('should return pipeline summary', async () => {
opportunitiesService.getPipelineSummary.mockResolvedValue(mockPipelineSummary);
const result = await controller.getPipelineSummary(mockRequestUser);
expect(opportunitiesService.getPipelineSummary).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockPipelineSummary);
expect(result.length).toBe(5);
});
});
describe('findOne', () => {
it('should return an opportunity by id', async () => {
opportunitiesService.findOne.mockResolvedValue(mockOpportunity);
const result = await controller.findOne(mockRequestUser, mockOpportunityId);
expect(opportunitiesService.findOne).toHaveBeenCalledWith(mockTenantId, mockOpportunityId);
expect(result).toEqual(mockOpportunity);
});
});
describe('create', () => {
it('should create a new opportunity', async () => {
const createDto: CreateOpportunityDto = {
name: 'New Deal',
amount: 25000,
expectedCloseDate: '2026-04-01',
};
const createdOpportunity = { ...mockOpportunity, ...createDto, id: 'new-opp-id' };
opportunitiesService.create.mockResolvedValue(createdOpportunity);
const result = await controller.create(mockRequestUser, createDto);
expect(opportunitiesService.create).toHaveBeenCalledWith(mockTenantId, mockUserId, createDto);
expect(result).toEqual(createdOpportunity);
});
});
describe('update', () => {
it('should update an opportunity', async () => {
const updateDto: UpdateOpportunityDto = {
amount: 75000,
probability: 50,
};
const updatedOpportunity = { ...mockOpportunity, ...updateDto };
opportunitiesService.update.mockResolvedValue(updatedOpportunity);
const result = await controller.update(mockRequestUser, mockOpportunityId, updateDto);
expect(opportunitiesService.update).toHaveBeenCalledWith(mockTenantId, mockOpportunityId, updateDto);
expect(result.amount).toBe(75000);
});
});
describe('updateStage', () => {
it('should update opportunity stage', async () => {
const stageDto: UpdateOpportunityStageDto = { stage: OpportunityStage.NEGOTIATION };
const updatedOpportunity = { ...mockOpportunity, stage: OpportunityStage.NEGOTIATION, probability: 60 };
opportunitiesService.updateStage.mockResolvedValue(updatedOpportunity);
const result = await controller.updateStage(mockRequestUser, mockOpportunityId, stageDto);
expect(opportunitiesService.updateStage).toHaveBeenCalledWith(mockTenantId, mockOpportunityId, stageDto);
expect(result.stage).toBe(OpportunityStage.NEGOTIATION);
});
});
describe('remove', () => {
it('should delete an opportunity', async () => {
opportunitiesService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockOpportunityId);
expect(opportunitiesService.remove).toHaveBeenCalledWith(mockTenantId, mockOpportunityId);
expect(result).toEqual({ message: 'Opportunity deleted successfully' });
});
});
describe('assign', () => {
it('should assign opportunity to a user', async () => {
const newUserId = 'new-user-id';
const assignedOpportunity = { ...mockOpportunity, assignedTo: newUserId };
opportunitiesService.assignTo.mockResolvedValue(assignedOpportunity);
const result = await controller.assign(mockRequestUser, mockOpportunityId, newUserId);
expect(opportunitiesService.assignTo).toHaveBeenCalledWith(mockTenantId, mockOpportunityId, newUserId);
expect(result.assignedTo).toBe(newUserId);
});
});
});

View File

@ -0,0 +1,166 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PipelineController } from '../controllers/pipeline.controller';
import { PipelineService } from '../services/pipeline.service';
import { CreatePipelineStageDto, UpdatePipelineStageDto, ReorderStagesDto } from '../dto';
describe('PipelineController', () => {
let controller: PipelineController;
let pipelineService: jest.Mocked<PipelineService>;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockStageId = '550e8400-e29b-41d4-a716-446655440003';
const mockRequestUser = {
id: mockUserId,
email: 'test@example.com',
tenant_id: mockTenantId,
role: 'admin',
};
const mockPipelineStage = {
id: mockStageId,
tenantId: mockTenantId,
name: 'Qualification',
code: 'qualification',
description: 'Qualification stage',
displayOrder: 1,
color: '#3498db',
isActive: true,
isDefault: false,
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
};
const mockPipelineStages = [
mockPipelineStage,
{ ...mockPipelineStage, id: 'stage-2', name: 'Proposal', code: 'proposal', displayOrder: 2 },
];
beforeEach(async () => {
const mockPipelineService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
reorder: jest.fn(),
initializeDefaults: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [PipelineController],
providers: [
{
provide: PipelineService,
useValue: mockPipelineService,
},
],
}).compile();
controller = module.get<PipelineController>(PipelineController);
pipelineService = module.get(PipelineService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return all pipeline stages', async () => {
pipelineService.findAll.mockResolvedValue(mockPipelineStages);
const result = await controller.findAll(mockRequestUser);
expect(pipelineService.findAll).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual(mockPipelineStages);
expect(result.length).toBe(2);
});
});
describe('findOne', () => {
it('should return a pipeline stage by id', async () => {
pipelineService.findOne.mockResolvedValue(mockPipelineStage);
const result = await controller.findOne(mockRequestUser, mockStageId);
expect(pipelineService.findOne).toHaveBeenCalledWith(mockTenantId, mockStageId);
expect(result).toEqual(mockPipelineStage);
});
});
describe('create', () => {
it('should create a new pipeline stage', async () => {
const createDto: CreatePipelineStageDto = {
name: 'New Stage',
code: 'new_stage',
displayOrder: 3,
};
const createdStage = { ...mockPipelineStage, ...createDto, id: 'new-stage-id' };
pipelineService.create.mockResolvedValue(createdStage);
const result = await controller.create(mockRequestUser, createDto);
expect(pipelineService.create).toHaveBeenCalledWith(mockTenantId, createDto);
expect(result).toEqual(createdStage);
});
});
describe('update', () => {
it('should update a pipeline stage', async () => {
const updateDto: UpdatePipelineStageDto = {
name: 'Updated Stage Name',
color: '#e74c3c',
};
const updatedStage = { ...mockPipelineStage, ...updateDto };
pipelineService.update.mockResolvedValue(updatedStage);
const result = await controller.update(mockRequestUser, mockStageId, updateDto);
expect(pipelineService.update).toHaveBeenCalledWith(mockTenantId, mockStageId, updateDto);
expect(result.name).toBe('Updated Stage Name');
expect(result.color).toBe('#e74c3c');
});
});
describe('remove', () => {
it('should delete a pipeline stage', async () => {
pipelineService.remove.mockResolvedValue(undefined);
const result = await controller.remove(mockRequestUser, mockStageId);
expect(pipelineService.remove).toHaveBeenCalledWith(mockTenantId, mockStageId);
expect(result).toEqual({ message: 'Pipeline stage deleted successfully' });
});
});
describe('reorder', () => {
it('should reorder pipeline stages', async () => {
const reorderDto: ReorderStagesDto = {
stageIds: ['stage-2', mockStageId],
};
const reorderedStages = [
{ ...mockPipelineStages[1], displayOrder: 1 },
{ ...mockPipelineStages[0], displayOrder: 2 },
];
pipelineService.reorder.mockResolvedValue(reorderedStages);
const result = await controller.reorder(mockRequestUser, reorderDto);
expect(pipelineService.reorder).toHaveBeenCalledWith(mockTenantId, reorderDto);
expect(result[0].displayOrder).toBe(1);
expect(result[1].displayOrder).toBe(2);
});
});
describe('initializeDefaults', () => {
it('should initialize default pipeline stages', async () => {
pipelineService.initializeDefaults.mockResolvedValue(undefined);
const result = await controller.initializeDefaults(mockRequestUser);
expect(pipelineService.initializeDefaults).toHaveBeenCalledWith(mockTenantId);
expect(result).toEqual({ message: 'Default pipeline stages initialized' });
});
});
});