// File: src/modules/review-team/services/task-creation.service.spec.ts // Change Log: // - 2026-05-21: เพิ่ม unit tests สำหรับ TaskCreationService import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { EntityManager } from 'typeorm'; import { TaskCreationService } from './task-creation.service'; import { ReviewTeam } from '../entities/review-team.entity'; import { ReviewTeamMember } from '../entities/review-team-member.entity'; import { ReviewTask } from '../entities/review-task.entity'; import { DelegationService } from '../../delegation/delegation.service'; import { SchedulerService } from '../../reminder/services/scheduler.service'; import { ReviewTeamMemberRole, ReviewTaskStatus, ReminderType, } from '../../common/enums/review.enums'; describe('TaskCreationService', () => { let service: TaskCreationService; let mockReviewTeamRepo: Record; let mockMemberRepo: Record; let mockReviewTaskRepo: Record; let mockDelegationService: Record; let mockSchedulerService: Record; let mockEntityManager: Record; beforeEach(async () => { mockReviewTeamRepo = { findOne: jest.fn(), }; mockMemberRepo = {}; mockReviewTaskRepo = { find: jest.fn(), }; mockDelegationService = { findActiveDelegate: jest.fn(), }; mockSchedulerService = { scheduleForTask: jest.fn(), }; mockEntityManager = { create: jest.fn(), save: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ TaskCreationService, { provide: getRepositoryToken(ReviewTeam), useValue: mockReviewTeamRepo, }, { provide: getRepositoryToken(ReviewTeamMember), useValue: mockMemberRepo, }, { provide: getRepositoryToken(ReviewTask), useValue: mockReviewTaskRepo, }, { provide: DelegationService, useValue: mockDelegationService, }, { provide: SchedulerService, useValue: mockSchedulerService, }, ], }).compile(); service = module.get(TaskCreationService); }); afterEach(() => { jest.clearAllMocks(); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('createParallelTasks', () => { const mockDueDate = new Date(); const rfaRevisionId = 100; const rfaPublicId = 'rfa-uuid-001'; const reviewTeamPublicId = 'team-uuid-001'; it('ควรคืนค่า empty array เมื่อไม่พบ ReviewTeam', async () => { mockReviewTeamRepo.findOne.mockResolvedValueOnce(null); const result = await service.createParallelTasks( rfaRevisionId, rfaPublicId, reviewTeamPublicId, mockDueDate, mockEntityManager as unknown as EntityManager ); expect(result).toEqual([]); expect(mockReviewTeamRepo.findOne).toHaveBeenCalledWith({ where: { publicId: reviewTeamPublicId }, relations: ['members'], }); }); it('ควรคืนค่า empty array เมื่อ ReviewTeam is_active=false', async () => { mockReviewTeamRepo.findOne.mockResolvedValueOnce({ id: 1, publicId: reviewTeamPublicId, isActive: false, }); const result = await service.createParallelTasks( rfaRevisionId, rfaPublicId, reviewTeamPublicId, mockDueDate, mockEntityManager as unknown as EntityManager ); expect(result).toEqual([]); }); it('ควรคืนค่า empty array เมื่อ ReviewTeam ไม่มี members', async () => { mockReviewTeamRepo.findOne.mockResolvedValueOnce({ id: 1, publicId: reviewTeamPublicId, isActive: true, members: [], }); const result = await service.createParallelTasks( rfaRevisionId, rfaPublicId, reviewTeamPublicId, mockDueDate, mockEntityManager as unknown as EntityManager ); expect(result).toEqual([]); }); it('ควรสร้าง parallel review tasks ตาม disciplines และกรองลำดับ LEAD/REVIEWER (Happy Path)', async () => { const mockMembers: Partial[] = [ { id: 10, userId: 1, disciplineId: 101, role: ReviewTeamMemberRole.REVIEWER, }, { id: 11, userId: 2, disciplineId: 101, role: ReviewTeamMemberRole.LEAD, }, { id: 12, userId: 3, disciplineId: 102, role: ReviewTeamMemberRole.REVIEWER, }, ]; mockReviewTeamRepo.findOne.mockResolvedValueOnce({ id: 5, projectId: 50, publicId: reviewTeamPublicId, isActive: true, members: mockMembers as ReviewTeamMember[], }); mockDelegationService.findActiveDelegate.mockResolvedValue(null); const createdTask1: Partial = { id: 201, publicId: 'task-uuid-201', rfaRevisionId, teamId: 5, disciplineId: 101, assignedToUserId: 2, // หยิบคนที่เป็น LEAD ใน discipline 101 status: ReviewTaskStatus.PENDING, dueDate: mockDueDate, }; const createdTask2: Partial = { id: 202, publicId: 'task-uuid-202', rfaRevisionId, teamId: 5, disciplineId: 102, assignedToUserId: 3, status: ReviewTaskStatus.PENDING, dueDate: mockDueDate, }; mockEntityManager.create .mockReturnValueOnce(createdTask1) .mockReturnValueOnce(createdTask2); mockEntityManager.save .mockResolvedValueOnce(createdTask1) .mockResolvedValueOnce(createdTask2); const result = await service.createParallelTasks( rfaRevisionId, rfaPublicId, reviewTeamPublicId, mockDueDate, mockEntityManager as unknown as EntityManager, 50, 'SDW' ); expect(result).toHaveLength(2); expect(mockEntityManager.create).toHaveBeenCalledTimes(2); expect(mockEntityManager.save).toHaveBeenCalledTimes(2); expect(mockSchedulerService.scheduleForTask).toHaveBeenCalledTimes(2); expect(mockSchedulerService.scheduleForTask).toHaveBeenNthCalledWith(1, { taskPublicId: 'task-uuid-201', rfaPublicId: rfaPublicId, assigneeUserId: 2, dueDate: mockDueDate, reminderType: ReminderType.DUE_SOON, projectId: 50, documentTypeCode: 'SDW', }); }); it('ควรดึงข้อมูล delegation เมื่อสมาชิกคนนั้นมี active delegate และเซ็ต assignedToUserId เป็นผู้รับมอบสิทธิ์', async () => { const mockMembers: Partial[] = [ { id: 10, userId: 1, disciplineId: 101, role: ReviewTeamMemberRole.LEAD, }, ]; mockReviewTeamRepo.findOne.mockResolvedValueOnce({ id: 5, projectId: 50, publicId: reviewTeamPublicId, isActive: true, members: mockMembers as ReviewTeamMember[], }); mockDelegationService.findActiveDelegate.mockResolvedValueOnce({ user_id: 99, }); const createdTask: Partial = { id: 201, publicId: 'task-uuid-201', rfaRevisionId, teamId: 5, disciplineId: 101, assignedToUserId: 99, delegatedFromUserId: 1, status: ReviewTaskStatus.PENDING, dueDate: mockDueDate, }; mockEntityManager.create.mockReturnValueOnce(createdTask); mockEntityManager.save.mockResolvedValueOnce(createdTask); const result = await service.createParallelTasks( rfaRevisionId, rfaPublicId, reviewTeamPublicId, mockDueDate, mockEntityManager as unknown as EntityManager ); expect(result).toHaveLength(1); expect(result[0].assignedToUserId).toBe(99); expect(mockDelegationService.findActiveDelegate).toHaveBeenCalledWith( 1, mockDueDate, expect.arrayContaining(['ALL', 'RFA_ONLY']) ); }); }); describe('areAllTasksCompleted', () => { const rfaRevisionId = 100; it('ควรคืนค่า false เมื่อไม่มี review tasks ในระบบสำหรับ revision นั้น', async () => { mockReviewTaskRepo.find.mockResolvedValueOnce([]); const result = await service.areAllTasksCompleted(rfaRevisionId); expect(result).toBe(false); expect(mockReviewTaskRepo.find).toHaveBeenCalledWith({ where: { rfaRevisionId }, }); }); it('ควรคืนค่า false เมื่อบาง task ยังมีสถานะ PENDING', async () => { const mockTasks: Partial[] = [ { id: 1, status: ReviewTaskStatus.COMPLETED }, { id: 2, status: ReviewTaskStatus.PENDING }, ]; mockReviewTaskRepo.find.mockResolvedValueOnce(mockTasks as ReviewTask[]); const result = await service.areAllTasksCompleted(rfaRevisionId); expect(result).toBe(false); }); it('ควรคืนค่า true เมื่อทุก tasks มีสถานะ COMPLETED หรือ CANCELLED', async () => { const mockTasks: Partial[] = [ { id: 1, status: ReviewTaskStatus.COMPLETED }, { id: 2, status: ReviewTaskStatus.CANCELLED }, ]; mockReviewTaskRepo.find.mockResolvedValueOnce(mockTasks as ReviewTask[]); const result = await service.areAllTasksCompleted(rfaRevisionId); expect(result).toBe(true); }); }); });