218 lines
7.8 KiB
TypeScript
218 lines
7.8 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { ReferralService } from '../../../src/application/services/referral.service';
|
|
import {
|
|
REFERRAL_RELATIONSHIP_REPOSITORY,
|
|
TEAM_STATISTICS_REPOSITORY,
|
|
ReferralChainService,
|
|
ReferralRelationship,
|
|
} from '../../../src/domain';
|
|
import { EventPublisherService } from '../../../src/infrastructure';
|
|
import { CreateReferralRelationshipCommand } from '../../../src/application/commands';
|
|
import { GetUserReferralInfoQuery, GetDirectReferralsQuery } from '../../../src/application/queries';
|
|
|
|
// Mock repository that works with domain aggregates
|
|
class MockReferralRepository {
|
|
private data = new Map<bigint, ReferralRelationship>();
|
|
private codeToUserId = new Map<string, bigint>();
|
|
|
|
async save(relationship: ReferralRelationship): Promise<ReferralRelationship> {
|
|
this.data.set(relationship.userId, relationship);
|
|
this.codeToUserId.set(relationship.referralCode, relationship.userId);
|
|
return relationship;
|
|
}
|
|
|
|
async findByUserId(userId: bigint): Promise<ReferralRelationship | null> {
|
|
return this.data.get(userId) ?? null;
|
|
}
|
|
|
|
async findByReferralCode(code: string): Promise<ReferralRelationship | null> {
|
|
const userId = this.codeToUserId.get(code);
|
|
if (!userId) return null;
|
|
return this.findByUserId(userId);
|
|
}
|
|
|
|
async findDirectReferrals(userId: bigint): Promise<ReferralRelationship[]> {
|
|
const results: ReferralRelationship[] = [];
|
|
for (const record of this.data.values()) {
|
|
if (record.referrerId === userId) {
|
|
results.push(record);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
async existsByReferralCode(code: string): Promise<boolean> {
|
|
return this.codeToUserId.has(code);
|
|
}
|
|
|
|
async existsByUserId(userId: bigint): Promise<boolean> {
|
|
return this.data.has(userId);
|
|
}
|
|
|
|
async getReferralChain(userId: bigint): Promise<bigint[]> {
|
|
const record = this.data.get(userId);
|
|
return record?.referralChain ?? [];
|
|
}
|
|
}
|
|
|
|
class MockTeamStatsRepository {
|
|
private data = new Map<bigint, { userId: bigint; directReferralCount: number; totalTeamCount: number; personalPlantingCount: number; teamPlantingCount: number; leaderboardScore: number; addDirectReferral: (id: bigint) => void; getDirectReferralStats: () => Map<bigint, number> }>();
|
|
|
|
async create(userId: bigint) {
|
|
const stats = {
|
|
userId,
|
|
directReferralCount: 0,
|
|
totalTeamCount: 0,
|
|
personalPlantingCount: 0,
|
|
teamPlantingCount: 0,
|
|
leaderboardScore: 0,
|
|
addDirectReferral: function(id: bigint) { this.directReferralCount++; },
|
|
getDirectReferralStats: () => new Map<bigint, number>(),
|
|
};
|
|
this.data.set(userId, stats);
|
|
return stats;
|
|
}
|
|
|
|
async save(stats: { userId: bigint }) {
|
|
const existing = this.data.get(stats.userId);
|
|
if (existing) {
|
|
Object.assign(existing, stats);
|
|
}
|
|
return existing;
|
|
}
|
|
|
|
async findByUserId(userId: bigint) {
|
|
return this.data.get(userId) ?? null;
|
|
}
|
|
}
|
|
|
|
class MockEventPublisher {
|
|
async publishDomainEvents() {}
|
|
async publishEvent() {}
|
|
}
|
|
|
|
|
|
describe('ReferralService (Integration)', () => {
|
|
let service: ReferralService;
|
|
let referralRepo: MockReferralRepository;
|
|
let teamStatsRepo: MockTeamStatsRepository;
|
|
|
|
beforeEach(async () => {
|
|
referralRepo = new MockReferralRepository();
|
|
teamStatsRepo = new MockTeamStatsRepository();
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
ReferralService,
|
|
ReferralChainService,
|
|
{
|
|
provide: REFERRAL_RELATIONSHIP_REPOSITORY,
|
|
useValue: referralRepo,
|
|
},
|
|
{
|
|
provide: TEAM_STATISTICS_REPOSITORY,
|
|
useValue: teamStatsRepo,
|
|
},
|
|
{
|
|
provide: EventPublisherService,
|
|
useClass: MockEventPublisher,
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<ReferralService>(ReferralService);
|
|
});
|
|
|
|
describe('createReferralRelationship', () => {
|
|
it('should create referral relationship without referrer', async () => {
|
|
const command = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
|
const result = await service.createReferralRelationship(command);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.referralCode).toBeDefined();
|
|
expect(result.referralCode.length).toBeGreaterThanOrEqual(6);
|
|
});
|
|
|
|
it('should create referral relationship with valid referrer code', async () => {
|
|
// First create a referrer
|
|
const referrerCommand = new CreateReferralRelationshipCommand(50n, 11111111, null);
|
|
const referrerResult = await service.createReferralRelationship(referrerCommand);
|
|
|
|
// Then create with referrer code
|
|
const command = new CreateReferralRelationshipCommand(100n, 12345678, referrerResult.referralCode);
|
|
const result = await service.createReferralRelationship(command);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.referralCode).toBeDefined();
|
|
});
|
|
|
|
it('should throw error if user already has referral relationship', async () => {
|
|
const command = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
|
await service.createReferralRelationship(command);
|
|
|
|
await expect(service.createReferralRelationship(command)).rejects.toThrow('用户已存在推荐关系');
|
|
});
|
|
|
|
it('should throw error for invalid referral code', async () => {
|
|
const command = new CreateReferralRelationshipCommand(100n, 12345678, 'INVALID_CODE');
|
|
|
|
await expect(service.createReferralRelationship(command)).rejects.toThrow('推荐码不存在');
|
|
});
|
|
});
|
|
|
|
describe('getUserReferralInfo', () => {
|
|
it('should return user referral info', async () => {
|
|
// Create user first
|
|
const createCommand = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
|
await service.createReferralRelationship(createCommand);
|
|
|
|
const query = new GetUserReferralInfoQuery(12345678);
|
|
const result = await service.getUserReferralInfo(query);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.userId).toBe('100');
|
|
expect(result.referralCode).toBeDefined();
|
|
expect(result.directReferralCount).toBe(0);
|
|
});
|
|
|
|
it('should throw error for non-existent user', async () => {
|
|
const query = new GetUserReferralInfoQuery(99999999);
|
|
|
|
await expect(service.getUserReferralInfo(query)).rejects.toThrow('用户推荐关系不存在');
|
|
});
|
|
});
|
|
|
|
describe('getDirectReferrals', () => {
|
|
it('should return direct referrals list', async () => {
|
|
// Create referrer
|
|
const referrerCommand = new CreateReferralRelationshipCommand(50n, 11111111, null);
|
|
const referrerResult = await service.createReferralRelationship(referrerCommand);
|
|
|
|
// Create direct referrals
|
|
await service.createReferralRelationship(new CreateReferralRelationshipCommand(100n, 12345678, referrerResult.referralCode));
|
|
await service.createReferralRelationship(new CreateReferralRelationshipCommand(101n, 12345679, referrerResult.referralCode));
|
|
|
|
const query = new GetDirectReferralsQuery(50n);
|
|
const result = await service.getDirectReferrals(query);
|
|
|
|
expect(result.referrals.length).toBe(2);
|
|
expect(result.total).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('validateReferralCode', () => {
|
|
it('should return true for valid code', async () => {
|
|
const createCommand = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
|
const { referralCode } = await service.createReferralRelationship(createCommand);
|
|
|
|
const isValid = await service.validateReferralCode(referralCode);
|
|
expect(isValid).toBe(true);
|
|
});
|
|
|
|
it('should return false for invalid code', async () => {
|
|
const isValid = await service.validateReferralCode('INVALID');
|
|
expect(isValid).toBe(false);
|
|
});
|
|
});
|
|
});
|