# Hướng Dẫn Tham Khảo Nhanh về Độ Phủ Kiểm Thử ## Tổng Quan 17 Tệp Nguồn Chưa Được Kiểm Thử ### MODULE INQUIRIES (4 tệp) ``` 1. prisma-inquiry.repository.ts — 6 phương thức cần kiểm thử + 1 mapper riêng 2. inquiries.controller.ts — 4 endpoint cần kiểm thử + xác thực guard 3. create-inquiry.dto.ts — 3 xác thực trường dữ liệu 4. list-inquiries.dto.ts — 2 xác thực trường dữ liệu (phân trang) ``` ### MODULE LEADS (6 tệp) ``` 5. prisma-lead.repository.ts — 6 phương thức + tổng hợp thống kê 6. lead-score.vo.ts — Value object: phạm vi 0-100 7. leads.controller.ts — 5 endpoint + role guard cấp class 8. create-lead.dto.ts — 6 xác thực trường dữ liệu 9. list-leads.dto.ts — Enum trạng thái + phân trang 10. update-lead-status.dto.ts — Xác thực enum trạng thái ``` ### MODULE REVIEWS (5 tệp) ``` 11. prisma-review.repository.ts — 7 phương thức + thống kê với phân phối 12. rating.vo.ts — Value object: phạm vi 1-5 (chỉ số nguyên) 13. reviews.controller.ts — 5 endpoint + xác thực hỗn hợp 14. create-review.dto.ts — 4 xác thực trường dữ liệu 15. list-reviews.dto.ts — 2 DTO: ListReviewsByTargetDto, ReviewStatsDto ``` ### MẪU THAM KHẢO (2 tệp kiểm thử) ``` 16. create-inquiry.handler.spec.ts — Mẫu kiểm thử handler 17. create-lead.handler.spec.ts — Mẫu kiểm thử handler 18. reviews.controller.spec.ts — Mẫu kiểm thử controller ``` --- ## Kịch Bản Kiểm Thử Nhanh Theo Loại ### REPOSITORIES (3 tệp) Danh sách kiểm tra cho từng repository: - [ ] findById() trả về entity hoặc null - [ ] save() tạo bản ghi với ánh xạ dữ liệu đúng - [ ] Các phương thức phân trang tuân thủ giới hạn tối đa (tối đa 100) - [ ] Tính toán phân trang: skip = (page - 1) * take - [ ] Các quan hệ được nối đúng - [ ] Các phép tổng hợp tính toán đúng (các phương thức thống kê) - [ ] Xử lý trường tùy chọn (ép kiểu null) - [ ] Định dạng ngày ISO trong DTO **Riêng cho PrismaInquiryRepository:** - countUnreadByAgent() tổng hợp - findByListing() bao gồm property.title - findByAgent() nối qua listing.agentId **Riêng cho PrismaLeadRepository:** - findByAgent() bộ lọc trạng thái tùy chọn - getStatsByAgent() tính toán: - totalLeads count - byStatus object (dict of counts) - conversionRate: (CONVERTED / total) * 100 (2 chữ số thập phân) - avgScore: trung bình của các điểm khác null (1 chữ số thập phân) **Riêng cho PrismaReviewRepository:** - findByUserAndTarget() truy vấn ràng buộc duy nhất - getStats() xây dựng đối tượng phân phối (khóa 1-5) - Tính toán averageRating: (sum / total) * 10 / 10 (1 chữ số thập phân) --- ### VALUE OBJECTS (2 tệp) Danh sách kiểm tra: - [ ] create() với giá trị hợp lệ trả về Result.ok() - [ ] create() với giá trị không hợp lệ trả về Result.err() với thông báo đúng - [ ] Getter trả về props.value - [ ] Các trường hợp không hợp lệ được bao phủ (âm, > max, không phải số nguyên, null) **Xác thực LeadScore:** ``` ✓ Valid: 0, 50, 100 ✗ Invalid: -1, 101, 2.5, null, "50" Error: "Điểm lead phải từ 0 đến 100" ``` **Xác thực Rating:** ``` ✓ Valid: 1, 2, 3, 4, 5 ✗ Invalid: 0, 6, 2.5, null, "5" Error: "Đánh giá phải từ 1 đến 5 sao" ``` --- ### CONTROLLERS (2 tệp) Danh sách kiểm tra: - [ ] Mỗi endpoint điều phối đúng loại command/query - [ ] Các tham số được ánh xạ đúng từ DTO - [ ] Các trường tùy chọn trở thành null (ví dụ: phone ?? null → null) - [ ] Các giá trị phân trang mặc định được áp dụng (page: 1, limit: 20) - [ ] Decorator CurrentUser trích xuất user.sub đúng - [ ] Thực thi guard (@UseGuards, @Roles) - [ ] Kiểu trả về khớp (ví dụ: { deleted: true }) **Đặc điểm riêng của InquiriesController:** - POST /inquiries: phone tùy chọn → null - GET /inquiries/listing/:listingId: yêu cầu JWT - GET /inquiries/agent/me: yêu cầu JWT + vai trò AGENT - PATCH /inquiries/:id/read: yêu cầu JWT + vai trò AGENT **Đặc điểm riêng của LeadsController:** - TẤT CẢ endpoint yêu cầu JWT + vai trò AGENT (cấp class @Roles) - POST /leads: score tùy chọn, xác thực phạm vi score trong command - GET /leads: bộ lọc trạng thái tùy chọn - GET /leads/stats: truy vấn tổng hợp - PATCH /leads/:id/status: chỉ trường status trong command - DELETE /leads/:id: xác thực agentId trong command **Đặc điểm riêng của ReviewsController:** - POST /reviews: yêu cầu JWT (AuthGuard) - GET /reviews: KHÔNG yêu cầu xác thực (thống kê là công khai) - GET /reviews/stats: KHÔNG yêu cầu xác thực - GET /reviews/me: yêu cầu JWT - DELETE /reviews/:id: yêu cầu JWT + kiểm tra quyền sở hữu trong command --- ### DTOs (10 tệp) Danh sách kiểm tra: - [ ] Các trường bắt buộc ném ValidationException nếu thiếu - [ ] Độ dài tối đa/tối thiểu của chuỗi được xác thực - [ ] Giá trị tối thiểu/tối đa của số được xác thực - [ ] Enum @IsIn() xác thực các giá trị được phép - [ ] Chuyển đổi kiểu (class-transformer @Type) - [ ] Định dạng email được xác thực (@IsEmail) - [ ] Các trường tùy chọn (@IsOptional) không ném lỗi nếu bỏ qua **Chuẩn phân trang (dùng trong 5 DTO):** - page: tùy chọn, @Min(1), mặc định 1 - limit: tùy chọn, @Min(1), @Max(100), mặc định 20 - Cả hai dùng @Type(() => Number) để chuyển đổi chuỗi→số **Xác thực enum:** - LeadStatus: ['NEW', 'CONTACTED', 'QUALIFIED', 'NEGOTIATING', 'CONVERTED', 'LOST'] - Dùng trong: ListLeadsDto, UpdateLeadStatusDto --- ## Ma Trận Ưu Tiên Kiểm Thử ### 🔴 QUAN TRỌNG (Logic Nghiệp Vụ) 1. PrismaReviewRepository.getStats() - tính toán phân phối 2. PrismaLeadRepository.getStatsByAgent() - công thức tỷ lệ chuyển đổi 3. Rating.vo - phải là số nguyên từ 1-5 4. LeadScore.vo - phải trong phạm vi 0-100 ### 🟡 CAO (Tính Toàn Vẹn Dữ Liệu) 1. Tất cả phương thức CRUD của repository 2. Tính toán phân trang 3. Ánh xạ quan hệ (nối user, nối listing) 4. Ánh xạ tham số controller ### 🟢 TRUNG BÌNH (Xác Thực) 1. Xác thực trường DTO 2. Ràng buộc enum 3. Xử lý trường tùy chọn 4. Thực thi guard --- ## Khuyến Nghị Thứ Tự Thực Thi Kiểm Thử 1. **Value Objects** (2 tệp) - 2 tệp kiểm thử đơn giản 2. **DTOs** (10 tệp) - Dùng mẫu kiểm thử class-validator 3. **Controllers** (2 tệp) - Kiểm thử điều phối command/query 4. **Repositories** (3 tệp) - Kiểm thử tầng dữ liệu với Prisma được giả lập --- ## Mẫu Thiết Lập Mock ### Cho Repositories: ```typescript const mockPrisma = { inquiry: { findUnique: vi.fn(), findMany: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), count: vi.fn() }, }; const repo = new PrismaInquiryRepository(mockPrisma as any); ``` ### Cho Controllers: ```typescript const mockCommandBus = { execute: vi.fn() }; const mockQueryBus = { execute: vi.fn() }; const controller = new InquiriesController(mockCommandBus as any, mockQueryBus as any); ``` ### Cho Value Objects: ```typescript const result = LeadScore.create(75); expect(result.isOk()).toBe(true); expect(result.unwrap().value).toBe(75); ``` ### Cho DTOs: ```typescript import { validate } from 'class-validator'; const dto = new CreateLeadDto(); dto.name = 'Nguyễn Văn A'; dto.phone = '0901234567'; // ... đặt các trường bắt buộc khác const errors = await validate(dto); expect(errors).toHaveLength(0); ``` --- ## Các Công Thức Cần Xác Minh ### Phân trang: ``` skip = (page - 1) * take totalPages = Math.ceil(total / take) take = Math.min(limit, 100) ``` ### Tỷ Lệ Chuyển Đổi Lead: ``` conversionRate = (convertedCount / totalLeads) * 100 Kết quả: làm tròn đến 2 chữ số thập phân (ví dụ: 33.33) ``` ### Điểm Trung Bình Lead: ``` avgScore = scoreSum / scoreCount (where score !== null) Kết quả: làm tròn đến 1 chữ số thập phân (ví dụ: 75.5) ``` ### Đánh Giá Trung Bình Review: ``` averageRating = (sum / totalReviews) Kết quả: làm tròn đến 1 chữ số thập phân (ví dụ: 4.5) ``` ### Phân Phối Review: ``` distribution: { 1: count, 2: count, 3: count, 4: count, 5: count } Phải khởi tạo tất cả 5 khóa, kể cả khi bằng 0 ``` --- ## Vị Trí Tệp Để Tham Khảo ``` /Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/api/ Inquiries: src/modules/inquiries/infrastructure/repositories/prisma-inquiry.repository.ts src/modules/inquiries/presentation/controllers/inquiries.controller.ts src/modules/inquiries/presentation/dto/create-inquiry.dto.ts src/modules/inquiries/presentation/dto/list-inquiries.dto.ts Leads: src/modules/leads/infrastructure/repositories/prisma-lead.repository.ts src/modules/leads/domain/value-objects/lead-score.vo.ts src/modules/leads/presentation/controllers/leads.controller.ts src/modules/leads/presentation/dto/create-lead.dto.ts src/modules/leads/presentation/dto/list-leads.dto.ts src/modules/leads/presentation/dto/update-lead-status.dto.ts Reviews: src/modules/reviews/infrastructure/repositories/prisma-review.repository.ts src/modules/reviews/domain/value-objects/rating.vo.ts src/modules/reviews/presentation/controllers/reviews.controller.ts src/modules/reviews/presentation/dto/create-review.dto.ts src/modules/reviews/presentation/dto/list-reviews.dto.ts ``` --- ## Các Bước Tiếp Theo 1. **Sao chép các mẫu kiểm thử tham khảo:** - Các handler tạo inquiry và lead đã có mẫu kiểm thử tốt - Kiểm thử controller cho reviews cho thấy cách tiếp cận kiểm thử endpoint 2. **Bắt đầu với repositories:** - Phức tạp nhất (giả lập Prisma) - Quan trọng nhất (tầng dữ liệu) - Các mẫu đã được thiết lập trong các kiểm thử hiện có 3. **Kiểm thử DTO thứ hai:** - Phản hồi nhanh (xác thực class-validator) - 10 tệp nhưng hầu hết đơn giản 4. **Controllers và VO sau cùng:** - Xây dựng trên các kiểm thử repository/DTO - Phụ thuộc vào các kiểm thử handler (nếu kiểm thử toàn bộ luồng)