fix: resolve all ESLint errors and TypeScript compilation errors

- Auto-fixed 712 import ordering errors via `pnpm lint --fix`
- Manually fixed 13 remaining ESLint errors:
  - Prefixed unused vars with _ (mockAdminUser, params)
  - Removed unused imports (UnauthorizedException, vi, screen)
  - Moved imports above vi.mock() calls to fix import group ordering
  - Removed eslint-disable for non-existent rules
  - Fixed empty object pattern in Playwright fixture
- Fixed ~40 TypeScript TS4111 index signature errors in test files:
  - Used bracket notation for Record<string, unknown> property access
  - Added missing PropertyMedia fields (id, order, caption) to test data
- Fixed pre-existing test failures in rate-limit guard specs:
  - Added NODE_ENV override to bypass test-mode skip in guard

Both `pnpm lint` and `pnpm typecheck` now exit 0 cleanly.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-11 23:12:08 +07:00
parent 9409706c58
commit 154aed5440
15 changed files with 1111 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
import type { AgentPublicProfileData, IAgentRepository } from '../../domain/repositories/agent.repository';
import { GetAgentPublicProfileHandler } from '../queries/get-agent-public-profile/get-agent-public-profile.handler';
import { GetAgentPublicProfileQuery } from '../queries/get-agent-public-profile/get-agent-public-profile.query';
describe('GetAgentPublicProfileHandler', () => {
let handler: GetAgentPublicProfileHandler;
let mockAgentRepo: { [K in keyof IAgentRepository]: ReturnType<typeof vi.fn> };
const mockProfile: AgentPublicProfileData = {
id: 'agent-1',
fullName: 'Nguyễn Văn A',
avatarUrl: 'https://example.com/avatar.jpg',
phone: '0901234567',
email: 'agent@example.com',
agency: 'Công ty BĐS ABC',
licenseNumber: 'BDS-001',
bio: 'Chuyên viên bất động sản khu vực Quận 7',
qualityScore: 85,
totalDeals: 50,
isVerified: true,
serviceAreas: ['Quận 7', 'Quận 2', 'Thủ Đức'],
memberSince: '2024-01-15',
activeListings: [
{
id: 'listing-1',
transactionType: 'SALE',
priceVND: '5000000000',
status: 'ACTIVE',
property: {
id: 'prop-1',
title: 'Căn hộ cao cấp Quận 7',
propertyType: 'APARTMENT',
address: '123 Nguyễn Hữu Thọ',
district: 'Quận 7',
city: 'TP.HCM',
areaM2: 75,
bedrooms: 2,
bathrooms: 2,
imageUrl: 'https://example.com/image.jpg',
},
},
],
avgReviewRating: 4.5,
totalReviews: 20,
};
beforeEach(() => {
mockAgentRepo = {
findByUserId: vi.fn(),
findById: vi.fn(),
save: vi.fn(),
getDashboard: vi.fn(),
getPublicProfile: vi.fn(),
getQualityScoreInputs: vi.fn(),
};
handler = new GetAgentPublicProfileHandler(mockAgentRepo as any);
});
it('returns full public profile for an agent', async () => {
mockAgentRepo.getPublicProfile.mockResolvedValue(mockProfile);
const query = new GetAgentPublicProfileQuery('agent-1');
const result = await handler.execute(query);
expect(result).toEqual(mockProfile);
expect(result!.id).toBe('agent-1');
expect(result!.fullName).toBe('Nguyễn Văn A');
expect(result!.qualityScore).toBe(85);
expect(result!.activeListings).toHaveLength(1);
expect(result!.serviceAreas).toContain('Quận 7');
expect(mockAgentRepo.getPublicProfile).toHaveBeenCalledWith('agent-1');
});
it('returns null when agent does not exist', async () => {
mockAgentRepo.getPublicProfile.mockResolvedValue(null);
const query = new GetAgentPublicProfileQuery('nonexistent-agent');
const result = await handler.execute(query);
expect(result).toBeNull();
expect(mockAgentRepo.getPublicProfile).toHaveBeenCalledWith('nonexistent-agent');
});
it('returns profile with empty active listings', async () => {
const profileNoListings: AgentPublicProfileData = {
...mockProfile,
activeListings: [],
totalDeals: 0,
};
mockAgentRepo.getPublicProfile.mockResolvedValue(profileNoListings);
const query = new GetAgentPublicProfileQuery('agent-1');
const result = await handler.execute(query);
expect(result!.activeListings).toHaveLength(0);
expect(result!.totalDeals).toBe(0);
});
it('returns profile with null optional fields', async () => {
const profileMinimal: AgentPublicProfileData = {
...mockProfile,
avatarUrl: null,
email: null,
agency: null,
licenseNumber: null,
bio: null,
};
mockAgentRepo.getPublicProfile.mockResolvedValue(profileMinimal);
const query = new GetAgentPublicProfileQuery('agent-1');
const result = await handler.execute(query);
expect(result!.avatarUrl).toBeNull();
expect(result!.email).toBeNull();
expect(result!.agency).toBeNull();
expect(result!.licenseNumber).toBeNull();
expect(result!.bio).toBeNull();
expect(result!.fullName).toBe('Nguyễn Văn A');
});
it('returns profile for unverified agent with zero reviews', async () => {
const unverifiedProfile: AgentPublicProfileData = {
...mockProfile,
isVerified: false,
avgReviewRating: 0,
totalReviews: 0,
qualityScore: 0,
};
mockAgentRepo.getPublicProfile.mockResolvedValue(unverifiedProfile);
const query = new GetAgentPublicProfileQuery('agent-new');
const result = await handler.execute(query);
expect(result!.isVerified).toBe(false);
expect(result!.avgReviewRating).toBe(0);
expect(result!.totalReviews).toBe(0);
expect(result!.qualityScore).toBe(0);
});
});

View File

@@ -0,0 +1,110 @@
import { NotFoundException } from '@nestjs/common';
import { RecalculateQualityScoreCommand } from '../../application/commands/recalculate-quality-score/recalculate-quality-score.command';
import { GetAgentDashboardQuery } from '../../application/queries/get-agent-dashboard/get-agent-dashboard.query';
import { GetAgentPublicProfileQuery } from '../../application/queries/get-agent-public-profile/get-agent-public-profile.query';
import { AgentsController } from '../controllers/agents.controller';
describe('AgentsController', () => {
let controller: AgentsController;
let mockCommandBus: { execute: ReturnType<typeof vi.fn> };
let mockQueryBus: { execute: ReturnType<typeof vi.fn> };
const mockAgentUser = { sub: 'user-1', phone: '0901234567', role: 'AGENT' };
const _mockAdminUser = { sub: 'admin-1', phone: '0901234568', role: 'ADMIN' };
beforeEach(() => {
mockCommandBus = { execute: vi.fn() };
mockQueryBus = { execute: vi.fn() };
controller = new AgentsController(mockCommandBus as any, mockQueryBus as any);
});
describe('GET /agents/me/dashboard — getDashboard', () => {
it('dispatches GetAgentDashboardQuery with current user', async () => {
const mockDashboard = {
agentId: 'agent-1',
qualityScore: 85,
totalDeals: 12,
responseTimeAvg: 600,
isVerified: true,
totalLeads: 30,
leadsByStatus: { NEW: 5, CONTACTED: 10 },
conversionRate: 0.167,
totalInquiries: 45,
unreadInquiries: 3,
totalListings: 15,
activeListings: 10,
avgReviewRating: 4.5,
totalReviews: 20,
};
mockQueryBus.execute.mockResolvedValue(mockDashboard);
const result = await controller.getDashboard(mockAgentUser as any);
expect(mockQueryBus.execute).toHaveBeenCalledWith(
expect.any(GetAgentDashboardQuery),
);
const query = mockQueryBus.execute.mock.calls[0]![0] as GetAgentDashboardQuery;
expect(query.userId).toBe('user-1');
expect(result).toEqual(mockDashboard);
});
});
describe('GET /agents/:agentId/profile — getPublicProfile', () => {
it('dispatches GetAgentPublicProfileQuery and returns profile', async () => {
const mockProfile = {
id: 'agent-1',
fullName: 'Nguyễn Văn A',
avatarUrl: null,
phone: '0901234567',
email: null,
agency: null,
licenseNumber: null,
bio: null,
qualityScore: 85,
totalDeals: 12,
isVerified: true,
serviceAreas: ['Quận 7'],
memberSince: '2024-01-15',
activeListings: [],
avgReviewRating: 4.5,
totalReviews: 20,
};
mockQueryBus.execute.mockResolvedValue(mockProfile);
const result = await controller.getPublicProfile('agent-1');
expect(mockQueryBus.execute).toHaveBeenCalledWith(
expect.any(GetAgentPublicProfileQuery),
);
const query = mockQueryBus.execute.mock.calls[0]![0] as GetAgentPublicProfileQuery;
expect(query.agentId).toBe('agent-1');
expect(result).toEqual(mockProfile);
});
it('throws NotFoundException when profile is null', async () => {
mockQueryBus.execute.mockResolvedValue(null);
await expect(controller.getPublicProfile('nonexistent')).rejects.toThrow(
NotFoundException,
);
await expect(controller.getPublicProfile('nonexistent')).rejects.toThrow(
'Không tìm thấy môi giới',
);
});
});
describe('POST /agents/:agentId/recalculate-score — recalculateScore', () => {
it('dispatches RecalculateQualityScoreCommand and returns success message', async () => {
mockCommandBus.execute.mockResolvedValue(undefined);
const result = await controller.recalculateScore('agent-1');
expect(mockCommandBus.execute).toHaveBeenCalledWith(
expect.any(RecalculateQualityScoreCommand),
);
const cmd = mockCommandBus.execute.mock.calls[0]![0] as RecalculateQualityScoreCommand;
expect(cmd.agentId).toBe('agent-1');
expect(result).toEqual({ message: 'Quality score recalculated' });
});
});
});

View File

@@ -0,0 +1,5 @@
export { CreateReviewCommand } from './commands/create-review/create-review.command';
export { DeleteReviewCommand } from './commands/delete-review/delete-review.command';
export { GetReviewsByTargetQuery } from './queries/get-reviews-by-target/get-reviews-by-target.query';
export { GetReviewsByUserQuery } from './queries/get-reviews-by-user/get-reviews-by-user.query';
export { GetAverageRatingQuery } from './queries/get-average-rating/get-average-rating.query';

View File

@@ -0,0 +1 @@
export { ReviewEntity, type ReviewProps } from './review.entity';

View File

@@ -0,0 +1,2 @@
export { ReviewCreatedEvent } from './review-created.event';
export { ReviewDeletedEvent } from './review-deleted.event';

View File

@@ -0,0 +1,4 @@
export * from './entities';
export * from './value-objects';
export * from './events';
export * from './repositories';

View File

@@ -0,0 +1,6 @@
export {
REVIEW_REPOSITORY,
type IReviewRepository,
type PaginatedResult,
} from './review.repository';
export { type ReviewItemData, type ReviewStatsData } from './review-read.dto';

View File

@@ -0,0 +1 @@
export { Rating } from './rating.vo';