test: increase test coverage for listings, auth, and search modules
Add 33 new test files to reach coverage targets: - Listings: 13 → 28 test files (50%+) - Auth: 21 → 36 test files (50%+) - Search: 10 → 13 test files (59%+) New tests cover domain entities, value objects, services, guards, decorators, DTOs, repositories, controllers, and event handlers. Total: 204 test files, 1178 tests passing. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ListingEntity } from '../entities/listing.entity';
|
||||
import { ModerationService } from '../services/moderation.service';
|
||||
import { Price } from '../value-objects/price.vo';
|
||||
|
||||
describe('ModerationService', () => {
|
||||
const service = new ModerationService();
|
||||
|
||||
const makeListingInReview = () => {
|
||||
const price = Price.create(5_000_000_000n).unwrap();
|
||||
const listing = ListingEntity.createNew(
|
||||
'listing-1',
|
||||
'property-1',
|
||||
'seller-1',
|
||||
'SALE',
|
||||
price,
|
||||
100,
|
||||
'agent-1',
|
||||
);
|
||||
listing.submitForReview();
|
||||
listing.clearDomainEvents();
|
||||
return listing;
|
||||
};
|
||||
|
||||
describe('applyModeration', () => {
|
||||
it('should approve a listing', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyModeration(listing, { action: 'approve' });
|
||||
|
||||
expect(listing.status).toBe('ACTIVE');
|
||||
expect(listing.publishedAt).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should reject a listing with default notes', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyModeration(listing, { action: 'reject' });
|
||||
|
||||
expect(listing.status).toBe('REJECTED');
|
||||
expect(listing.moderationNotes).toBe('Bị từ chối bởi moderator');
|
||||
});
|
||||
|
||||
it('should reject a listing with custom notes', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyModeration(listing, {
|
||||
action: 'reject',
|
||||
notes: 'Ảnh không rõ ràng',
|
||||
});
|
||||
|
||||
expect(listing.status).toBe('REJECTED');
|
||||
expect(listing.moderationNotes).toBe('Ảnh không rõ ràng');
|
||||
});
|
||||
|
||||
it('should set moderation score when provided on approve', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyModeration(listing, {
|
||||
action: 'approve',
|
||||
moderationScore: 95,
|
||||
notes: 'Tin đăng chất lượng',
|
||||
});
|
||||
|
||||
expect(listing.status).toBe('ACTIVE');
|
||||
expect(listing.moderationScore).toBe(95);
|
||||
expect(listing.moderationNotes).toBe('Tin đăng chất lượng');
|
||||
});
|
||||
|
||||
it('should set moderation score when provided on reject', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyModeration(listing, {
|
||||
action: 'reject',
|
||||
moderationScore: 20,
|
||||
notes: 'Nội dung vi phạm',
|
||||
});
|
||||
|
||||
expect(listing.status).toBe('REJECTED');
|
||||
expect(listing.moderationScore).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyStatusTransition', () => {
|
||||
it('should reject with moderation notes', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyStatusTransition(listing, 'REJECTED', 'Vi phạm chính sách');
|
||||
|
||||
expect(listing.status).toBe('REJECTED');
|
||||
expect(listing.moderationNotes).toBe('Vi phạm chính sách');
|
||||
});
|
||||
|
||||
it('should approve a PENDING_REVIEW listing transitioning to ACTIVE', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
service.applyStatusTransition(listing, 'ACTIVE');
|
||||
|
||||
expect(listing.status).toBe('ACTIVE');
|
||||
expect(listing.publishedAt).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should use generic transitionTo for non-moderation transitions', () => {
|
||||
const price = Price.create(5_000_000_000n).unwrap();
|
||||
const listing = ListingEntity.createNew(
|
||||
'listing-2',
|
||||
'property-2',
|
||||
'seller-2',
|
||||
'SALE',
|
||||
price,
|
||||
100,
|
||||
);
|
||||
listing.submitForReview();
|
||||
listing.approve();
|
||||
listing.clearDomainEvents();
|
||||
|
||||
service.applyStatusTransition(listing, 'SOLD');
|
||||
|
||||
expect(listing.status).toBe('SOLD');
|
||||
});
|
||||
|
||||
it('should use generic transitionTo for REJECTED without notes', () => {
|
||||
const listing = makeListingInReview();
|
||||
|
||||
// REJECTED without moderationNotes falls into the generic transitionTo branch
|
||||
service.applyStatusTransition(listing, 'REJECTED');
|
||||
|
||||
expect(listing.status).toBe('REJECTED');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { PropertyMediaEntity } from '../entities/property-media.entity';
|
||||
|
||||
describe('PropertyMediaEntity', () => {
|
||||
it('should create media via constructor with all properties', () => {
|
||||
const media = new PropertyMediaEntity('media-1', {
|
||||
propertyId: 'prop-1',
|
||||
url: 'https://cdn.example.com/photo.jpg',
|
||||
type: 'image',
|
||||
order: 0,
|
||||
caption: 'Mặt tiền nhà',
|
||||
aiTags: { tags: ['exterior'] },
|
||||
});
|
||||
|
||||
expect(media.id).toBe('media-1');
|
||||
expect(media.propertyId).toBe('prop-1');
|
||||
expect(media.url).toBe('https://cdn.example.com/photo.jpg');
|
||||
expect(media.type).toBe('image');
|
||||
expect(media.order).toBe(0);
|
||||
expect(media.caption).toBe('Mặt tiền nhà');
|
||||
expect(media.aiTags).toEqual({ tags: ['exterior'] });
|
||||
expect(media.createdAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it('should create media via createNew factory with caption', () => {
|
||||
const media = PropertyMediaEntity.createNew(
|
||||
'media-2',
|
||||
'prop-1',
|
||||
'https://cdn.example.com/interior.jpg',
|
||||
'image',
|
||||
1,
|
||||
'Phòng khách',
|
||||
);
|
||||
|
||||
expect(media.id).toBe('media-2');
|
||||
expect(media.propertyId).toBe('prop-1');
|
||||
expect(media.url).toBe('https://cdn.example.com/interior.jpg');
|
||||
expect(media.type).toBe('image');
|
||||
expect(media.order).toBe(1);
|
||||
expect(media.caption).toBe('Phòng khách');
|
||||
expect(media.aiTags).toBeNull();
|
||||
});
|
||||
|
||||
it('should create media via createNew factory without caption', () => {
|
||||
const media = PropertyMediaEntity.createNew(
|
||||
'media-3',
|
||||
'prop-2',
|
||||
'https://cdn.example.com/video.mp4',
|
||||
'video',
|
||||
0,
|
||||
);
|
||||
|
||||
expect(media.type).toBe('video');
|
||||
expect(media.caption).toBeNull();
|
||||
expect(media.aiTags).toBeNull();
|
||||
});
|
||||
|
||||
it('should preserve order values', () => {
|
||||
const media0 = PropertyMediaEntity.createNew('m-0', 'p-1', 'http://a.com/0.jpg', 'image', 0);
|
||||
const media5 = PropertyMediaEntity.createNew('m-5', 'p-1', 'http://a.com/5.jpg', 'image', 5);
|
||||
|
||||
expect(media0.order).toBe(0);
|
||||
expect(media5.order).toBe(5);
|
||||
});
|
||||
|
||||
it('should accept custom createdAt via constructor', () => {
|
||||
const pastDate = new Date('2024-01-01T00:00:00Z');
|
||||
const media = new PropertyMediaEntity(
|
||||
'media-old',
|
||||
{
|
||||
propertyId: 'prop-1',
|
||||
url: 'https://cdn.example.com/old.jpg',
|
||||
type: 'image',
|
||||
order: 0,
|
||||
caption: null,
|
||||
aiTags: null,
|
||||
},
|
||||
pastDate,
|
||||
);
|
||||
|
||||
expect(media.createdAt).toBe(pastDate);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user