import { BadRequestException, ConflictException, NotFoundException, } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import type { AuthUser } from '../../../common/auth/auth-user'; import { DateTime } from '../../../common/value-objects/date-time/date-time'; import { Status } from '../../../common/value-objects/status/status'; import { BranchesService } from '../../configuration/branches/branches.service'; import { CustomersService } from '../../configuration/customers/customers.service'; import { EmployeesService } from '../../configuration/employees/employees.service'; import { PrivilegesService } from '../../privileges/privileges.service'; import type { Cycle } from './cycle'; import { CyclesRepository } from './cycles.repository'; import { CyclesService } from './cycles.service'; describe('CyclesService', () => { let service: CyclesService; let repository: jest.Mocked< Pick< CyclesRepository, | 'list' | 'findById' | 'findLiveByKey' | 'create' | 'update' | 'updateStatus' | 'bulkUpdateStatus' | 'archive' | 'bulkArchive' > >; const employeesService = { findById: jest.fn(), findByCode: jest.fn() }; const branchesService = { findById: jest.fn(), findByCode: jest.fn() }; const customersService = { findById: jest.fn(), findByCode: jest.fn() }; const privilegesService = { checkPermission: jest.fn(), checkAnyPermission: jest.fn() }; const now = DateTime.fromUnixMs(1_700_000_000_000); const user: AuthUser = { id: 'user-1', username: 'alice', jti: 'jti-1', isSuperadmin: true, }; const geometry = { type: 'LineString' as const, coordinates: [ [106.8, -6.2], [106.9, -6.3], [107.0, -6.4], ] as const, }; const sample: Cycle = { id: 'cyc-1', employeeId: 'emp-1', purpose: 'sales', cycleNumber: 1, weekdays: [ { id: 'wd-1', weekday: 'monday', startBranchId: 'br-1', endBranchId: 'br-2', startBranch: { id: 'br-1', code: 'B1', name: 'Start' }, endBranch: { id: 'br-2', code: 'B2', name: 'End' }, routeGeometry: geometry, destinations: [ { id: 'dest-1', customerId: 'cus-1', customer: { id: 'cus-1', code: 'C1', name: 'Acme' }, sortOrder: 0, }, ], }, ], status: Status.create('draft'), createdAt: now, updatedAt: now, createdBy: 'user-1', updatedBy: 'user-1', employee: { id: 'emp-1', code: 'E1', name: 'Ada' }, createdByUser: { id: 'user-1', username: 'admin' }, updatedByUser: { id: 'user-1', username: 'admin' }, }; const located = { id: 'x', latitude: -6.2, longitude: 106.8 }; beforeEach(async () => { jest.clearAllMocks(); repository = { list: jest.fn(), findById: jest.fn(), findLiveByKey: jest.fn(), create: jest.fn(), update: jest.fn(), updateStatus: jest.fn(), bulkUpdateStatus: jest.fn(), archive: jest.fn(), bulkArchive: jest.fn(), }; employeesService.findById.mockResolvedValue({ id: 'emp-1' }); branchesService.findById.mockResolvedValue(located); customersService.findById.mockResolvedValue(located); repository.findLiveByKey.mockResolvedValue(null); repository.create.mockResolvedValue(sample); const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ CyclesService, { provide: CyclesRepository, useValue: repository }, { provide: EmployeesService, useValue: employeesService }, { provide: BranchesService, useValue: branchesService }, { provide: CustomersService, useValue: customersService }, { provide: PrivilegesService, useValue: privilegesService }, ], }).compile(); service = moduleRef.get(CyclesService); }); it('create rejects incomplete weekdays', async () => { await expect( service.create({ employeeId: 'emp-1', purpose: 'sales', cycleNumber: 1, weekdays: { monday: { startBranchId: 'br-1', endBranchId: 'br-2', customerIds: [], }, }, userId: 'user-1', }), ).rejects.toBeInstanceOf(BadRequestException); }); it('create rejects duplicate live keys', async () => { repository.findLiveByKey.mockResolvedValue(sample); await expect( service.create({ employeeId: 'emp-1', purpose: 'sales', cycleNumber: 1, weekdays: { monday: { startBranchId: 'br-1', endBranchId: 'br-2', customerIds: ['cus-1'], }, }, userId: 'user-1', }), ).rejects.toBeInstanceOf(ConflictException); }); it('create persists a complete weekday and builds geometry', async () => { await service.create({ employeeId: 'emp-1', purpose: 'sales', cycleNumber: 1, weekdays: { monday: { startBranchId: 'br-1', endBranchId: 'br-2', customerIds: ['cus-1'], }, }, userId: 'user-1', }); expect(repository.create).toHaveBeenCalled(); const arg = repository.create.mock.calls[0][0]; expect(arg.weekdays[0].weekday).toBe('monday'); expect(arg.weekdays[0].routeGeometry.type).toBe('LineString'); }); it('delete archives instead of hard-deleting', async () => { repository.findById.mockResolvedValue(sample); repository.archive.mockResolvedValue(undefined); await service.delete('cyc-1', user); expect(repository.archive).toHaveBeenCalledWith('cyc-1', 'user-1'); }); it('findById throws when missing', async () => { repository.findById.mockResolvedValue(null); await expect(service.findById('missing', user)).rejects.toBeInstanceOf( NotFoundException, ); }); });