# 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)

