# Test Coverage Quick Reference Guide ## 17 Untested Source Files Overview ### INQUIRIES MODULE (4 files) ``` 1. prisma-inquiry.repository.ts — 6 methods to test + 1 private mapper 2. inquiries.controller.ts — 4 endpoints to test + guard validation 3. create-inquiry.dto.ts — 3 field validations 4. list-inquiries.dto.ts — 2 field validations (pagination) ``` ### LEADS MODULE (6 files) ``` 5. prisma-lead.repository.ts — 6 methods + stats aggregation 6. lead-score.vo.ts — Value object: range 0-100 7. leads.controller.ts — 5 endpoints + class-level role guard 8. create-lead.dto.ts — 6 field validations 9. list-leads.dto.ts — Status enum + pagination 10. update-lead-status.dto.ts — Status enum validation ``` ### REVIEWS MODULE (5 files) ``` 11. prisma-review.repository.ts — 7 methods + stats with distribution 12. rating.vo.ts — Value object: range 1-5 (integers only) 13. reviews.controller.ts — 5 endpoints + mixed auth 14. create-review.dto.ts — 4 field validations 15. list-reviews.dto.ts — 2 DTOs: ListReviewsByTargetDto, ReviewStatsDto ``` ### REFERENCE PATTERNS (2 test files) ``` 16. create-inquiry.handler.spec.ts — Handler test pattern 17. create-lead.handler.spec.ts — Handler test pattern 18. reviews.controller.spec.ts — Controller test pattern ``` --- ## Quick Test Scenarios by Type ### REPOSITORIES (3 files) Test checklist for each repository: - [ ] findById() returns entity or null - [ ] save() creates record with correct data mapping - [ ] Paginated methods respect limit cap (100 max) - [ ] Pagination calculation: skip = (page - 1) * take - [ ] Relationships are joined correctly - [ ] Aggregations calculate correctly (stats methods) - [ ] Optional fields handling (null coercion) - [ ] ISO date formatting in DTOs **Specific to PrismaInquiryRepository:** - countUnreadByAgent() aggregation - findByListing() includes property.title - findByAgent() joins through listing.agentId **Specific to PrismaLeadRepository:** - findByAgent() optional status filter - getStatsByAgent() calculates: - totalLeads count - byStatus object (dict of counts) - conversionRate: (CONVERTED / total) * 100 (2 decimals) - avgScore: average of non-null scores (1 decimal) **Specific to PrismaReviewRepository:** - findByUserAndTarget() unique constraint query - getStats() builds distribution object (keys 1-5) - averageRating calculation: (sum / total) * 10 / 10 (1 decimal) --- ### VALUE OBJECTS (2 files) Test checklist: - [ ] create() with valid value returns Result.ok() - [ ] create() with invalid value returns Result.err() with correct message - [ ] Getter returns props.value - [ ] Invalid cases covered (negative, > max, non-integer, null) **LeadScore validation:** ``` ✓ Valid: 0, 50, 100 ✗ Invalid: -1, 101, 2.5, null, "50" Error: "Điểm lead phải từ 0 đến 100" ``` **Rating validation:** ``` ✓ 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 files) Test checklist: - [ ] Each endpoint dispatches correct command/query type - [ ] Parameters are mapped correctly from DTO - [ ] Optional fields become null (e.g., phone ?? null → null) - [ ] Default pagination values applied (page: 1, limit: 20) - [ ] CurrentUser decorator extracts user.sub correctly - [ ] Guard enforcement (@UseGuards, @Roles) - [ ] Return types match (e.g., { deleted: true }) **InquiriesController specifics:** - POST /inquiries: phone optional → null - GET /inquiries/listing/:listingId: requires JWT - GET /inquiries/agent/me: requires JWT + AGENT role - PATCH /inquiries/:id/read: requires JWT + AGENT role **LeadsController specifics:** - ALL endpoints require JWT + AGENT role (class-level @Roles) - POST /leads: score optional, score range validation in command - GET /leads: status filter optional - GET /leads/stats: aggregation query - PATCH /leads/:id/status: only status field in command - DELETE /leads/:id: agentId verification in command **ReviewsController specifics:** - POST /reviews: requires JWT (AuthGuard) - GET /reviews: NO auth required (stats are public) - GET /reviews/stats: NO auth required - GET /reviews/me: requires JWT - DELETE /reviews/:id: requires JWT + ownership check in command --- ### DTOs (10 files) Test checklist: - [ ] Required fields throw ValidationException if missing - [ ] String max/min length validated - [ ] Number min/max validated - [ ] Enum @IsIn() validates allowed values - [ ] Type transformation (class-transformer @Type) - [ ] Email format validated (@IsEmail) - [ ] Optional fields (@IsOptional) don't throw if omitted **Pagination standard (used in 5 DTOs):** - page: optional, @Min(1), default 1 - limit: optional, @Min(1), @Max(100), default 20 - Both use @Type(() => Number) for string→number transformation **Enum validations:** - LeadStatus: ['NEW', 'CONTACTED', 'QUALIFIED', 'NEGOTIATING', 'CONVERTED', 'LOST'] - Used in: ListLeadsDto, UpdateLeadStatusDto --- ## Test Priority Matrix ### 🔴 CRITICAL (Business Logic) 1. PrismaReviewRepository.getStats() - distribution calculation 2. PrismaLeadRepository.getStatsByAgent() - conversion rate formula 3. Rating.vo - must be 1-5 integers only 4. LeadScore.vo - must be 0-100 range ### 🟡 HIGH (Data Integrity) 1. All repository CRUD methods 2. Pagination calculations 3. Relationship mapping (user joins, listing joins) 4. Controller parameter mapping ### 🟢 MEDIUM (Validation) 1. DTO field validations 2. Enum constraints 3. Optional field handling 4. Guard enforcement --- ## Test Execution Order Recommendation 1. **Value Objects** (2 files) - 2 simple test files 2. **DTOs** (10 files) - Use class-validator testing patterns 3. **Controllers** (2 files) - Command/query dispatch tests 4. **Repositories** (3 files) - Data layer tests with mocked Prisma --- ## Mock Setup Template ### For 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); ``` ### For Controllers: ```typescript const mockCommandBus = { execute: vi.fn() }; const mockQueryBus = { execute: vi.fn() }; const controller = new InquiriesController(mockCommandBus as any, mockQueryBus as any); ``` ### For Value Objects: ```typescript const result = LeadScore.create(75); expect(result.isOk()).toBe(true); expect(result.unwrap().value).toBe(75); ``` ### For DTOs: ```typescript import { validate } from 'class-validator'; const dto = new CreateLeadDto(); dto.name = 'Nguyễn Văn A'; dto.phone = '0901234567'; // ... set other required fields const errors = await validate(dto); expect(errors).toHaveLength(0); ``` --- ## Key Formulas to Verify ### Pagination: ``` skip = (page - 1) * take totalPages = Math.ceil(total / take) take = Math.min(limit, 100) ``` ### Lead Conversion Rate: ``` conversionRate = (convertedCount / totalLeads) * 100 Result: rounded to 2 decimals (e.g., 33.33) ``` ### Lead Average Score: ``` avgScore = scoreSum / scoreCount (where score !== null) Result: rounded to 1 decimal (e.g., 75.5) ``` ### Review Average Rating: ``` averageRating = (sum / totalReviews) Result: rounded to 1 decimal (e.g., 4.5) ``` ### Review Distribution: ``` distribution: { 1: count, 2: count, 3: count, 4: count, 5: count } Must initialize all 5 keys, even if 0 ``` --- ## File Locations for Reference ``` /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 ``` --- ## Next Steps 1. **Copy reference test patterns:** - Create inquiry and lead handlers already have good test patterns - Controller test for reviews shows endpoint testing approach 2. **Start with repositories:** - Most complex (Prisma mocking) - Most critical (data layer) - Established patterns in existing tests 3. **Test DTOs second:** - Quick feedback (class-validator validation) - 10 files but mostly simple 4. **Controllers and VOs last:** - Build on repository/DTO tests - Depend on handler tests (if testing full flow)