234 lines
10 KiB
TypeScript
234 lines
10 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import {
|
|
RewardCalculationService,
|
|
REFERRAL_SERVICE_CLIENT,
|
|
AUTHORIZATION_SERVICE_CLIENT,
|
|
} from '../../src/domain/services/reward-calculation.service';
|
|
import { RightType } from '../../src/domain/value-objects/right-type.enum';
|
|
|
|
describe('RewardCalculationService (Integration)', () => {
|
|
let service: RewardCalculationService;
|
|
let mockReferralService: any;
|
|
let mockAuthorizationService: any;
|
|
|
|
beforeEach(async () => {
|
|
mockReferralService = {
|
|
getReferralChain: jest.fn(),
|
|
};
|
|
|
|
mockAuthorizationService = {
|
|
findNearestAuthorizedProvince: jest.fn(),
|
|
findNearestAuthorizedCity: jest.fn(),
|
|
findNearestCommunity: jest.fn(),
|
|
};
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
RewardCalculationService,
|
|
{
|
|
provide: REFERRAL_SERVICE_CLIENT,
|
|
useValue: mockReferralService,
|
|
},
|
|
{
|
|
provide: AUTHORIZATION_SERVICE_CLIENT,
|
|
useValue: mockAuthorizationService,
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<RewardCalculationService>(RewardCalculationService);
|
|
});
|
|
|
|
describe('calculateRewards', () => {
|
|
const baseParams = {
|
|
sourceOrderNo: 'ORDER001',
|
|
sourceUserId: BigInt(100),
|
|
treeCount: 10,
|
|
provinceCode: '440000',
|
|
cityCode: '440100',
|
|
};
|
|
|
|
it('should calculate all 6 types of rewards', async () => {
|
|
// Setup: referrer with planting status
|
|
mockReferralService.getReferralChain.mockResolvedValue({
|
|
ancestors: [{ userId: BigInt(200), hasPlanted: true }],
|
|
});
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300));
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400));
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500));
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
// Should return 6 rewards (one for each type)
|
|
expect(rewards).toHaveLength(6);
|
|
|
|
// Verify each reward type exists
|
|
const rightTypes = rewards.map((r) => r.rewardSource.rightType);
|
|
expect(rightTypes).toContain(RightType.SHARE_RIGHT);
|
|
expect(rightTypes).toContain(RightType.PROVINCE_TEAM_RIGHT);
|
|
expect(rightTypes).toContain(RightType.PROVINCE_AREA_RIGHT);
|
|
expect(rightTypes).toContain(RightType.CITY_TEAM_RIGHT);
|
|
expect(rightTypes).toContain(RightType.CITY_AREA_RIGHT);
|
|
expect(rightTypes).toContain(RightType.COMMUNITY_RIGHT);
|
|
});
|
|
|
|
it('should calculate share right reward (500 USDT) when referrer has planted', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({
|
|
ancestors: [{ userId: BigInt(200), hasPlanted: true }],
|
|
});
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const shareReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.SHARE_RIGHT,
|
|
);
|
|
expect(shareReward).toBeDefined();
|
|
expect(shareReward?.usdtAmount.amount).toBe(500 * 10); // 500 USDT per tree
|
|
expect(shareReward?.userId).toBe(BigInt(200));
|
|
expect(shareReward?.isSettleable).toBe(true); // Already planted, so settleable
|
|
});
|
|
|
|
it('should create pending share right reward when referrer has not planted', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({
|
|
ancestors: [{ userId: BigInt(200), hasPlanted: false }],
|
|
});
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const shareReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.SHARE_RIGHT,
|
|
);
|
|
expect(shareReward).toBeDefined();
|
|
expect(shareReward?.isPending).toBe(true); // Not planted, so pending with 24h countdown
|
|
expect(shareReward?.expireAt).toBeDefined();
|
|
});
|
|
|
|
it('should assign share right to headquarters when no referrer', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({
|
|
ancestors: [],
|
|
});
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const shareReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.SHARE_RIGHT,
|
|
);
|
|
expect(shareReward).toBeDefined();
|
|
expect(shareReward?.userId).toBe(BigInt(1)); // Headquarters community ID
|
|
});
|
|
|
|
it('should calculate province team right (20 USDT)', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300));
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const provinceTeamReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.PROVINCE_TEAM_RIGHT,
|
|
);
|
|
expect(provinceTeamReward).toBeDefined();
|
|
expect(provinceTeamReward?.usdtAmount.amount).toBe(20 * 10);
|
|
expect(provinceTeamReward?.userId).toBe(BigInt(300));
|
|
});
|
|
|
|
it('should calculate province area right (15 USDT + 1% hashpower)', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const provinceAreaReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.PROVINCE_AREA_RIGHT,
|
|
);
|
|
expect(provinceAreaReward).toBeDefined();
|
|
expect(provinceAreaReward?.usdtAmount.amount).toBe(15 * 10);
|
|
expect(provinceAreaReward?.hashpowerAmount.value).toBe(1 * 10); // 1% * 10 trees = 10
|
|
});
|
|
|
|
it('should calculate city team right (40 USDT)', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400));
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const cityTeamReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.CITY_TEAM_RIGHT,
|
|
);
|
|
expect(cityTeamReward).toBeDefined();
|
|
expect(cityTeamReward?.usdtAmount.amount).toBe(40 * 10);
|
|
expect(cityTeamReward?.userId).toBe(BigInt(400));
|
|
});
|
|
|
|
it('should calculate city area right (35 USDT + 2% hashpower)', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const cityAreaReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.CITY_AREA_RIGHT,
|
|
);
|
|
expect(cityAreaReward).toBeDefined();
|
|
expect(cityAreaReward?.usdtAmount.amount).toBe(35 * 10);
|
|
expect(cityAreaReward?.hashpowerAmount.value).toBe(2 * 10); // 2% * 10 trees = 20
|
|
});
|
|
|
|
it('should calculate community right (80 USDT)', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500));
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
const communityReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.COMMUNITY_RIGHT,
|
|
);
|
|
expect(communityReward).toBeDefined();
|
|
expect(communityReward?.usdtAmount.amount).toBe(80 * 10);
|
|
expect(communityReward?.userId).toBe(BigInt(500));
|
|
});
|
|
|
|
it('should assign to headquarters when no authorized holder found', async () => {
|
|
mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
|
|
mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
|
|
mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);
|
|
|
|
const rewards = await service.calculateRewards(baseParams);
|
|
|
|
// Province team, city team, community should go to headquarters (ID=1)
|
|
const provinceTeamReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.PROVINCE_TEAM_RIGHT,
|
|
);
|
|
const cityTeamReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.CITY_TEAM_RIGHT,
|
|
);
|
|
const communityReward = rewards.find(
|
|
(r) => r.rewardSource.rightType === RightType.COMMUNITY_RIGHT,
|
|
);
|
|
|
|
expect(provinceTeamReward?.userId).toBe(BigInt(1));
|
|
expect(cityTeamReward?.userId).toBe(BigInt(1));
|
|
expect(communityReward?.userId).toBe(BigInt(1));
|
|
});
|
|
});
|
|
});
|