import { RewardLedgerEntry } from './reward-ledger-entry.aggregate'; import { RewardSource } from '../../value-objects/reward-source.vo'; import { RightType } from '../../value-objects/right-type.enum'; import { RewardStatus } from '../../value-objects/reward-status.enum'; import { Money } from '../../value-objects/money.vo'; import { Hashpower } from '../../value-objects/hashpower.vo'; describe('RewardLedgerEntry', () => { const createRewardSource = () => RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(2)); describe('createPending', () => { it('should create a pending reward with 24h expiration', () => { const entry = RewardLedgerEntry.createPending({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), memo: 'Test reward', }); expect(entry.isPending).toBe(true); expect(entry.rewardStatus).toBe(RewardStatus.PENDING); expect(entry.usdtAmount.amount).toBe(500); expect(entry.expireAt).toBeDefined(); expect(entry.domainEvents.length).toBe(1); expect(entry.domainEvents[0].eventType).toBe('RewardCreated'); }); it('should set expireAt to 24 hours from now', () => { const before = Date.now(); const entry = RewardLedgerEntry.createPending({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); const after = Date.now(); const expectedExpireMin = before + 24 * 60 * 60 * 1000; const expectedExpireMax = after + 24 * 60 * 60 * 1000; expect(entry.expireAt!.getTime()).toBeGreaterThanOrEqual(expectedExpireMin); expect(entry.expireAt!.getTime()).toBeLessThanOrEqual(expectedExpireMax); }); }); describe('createSettleable', () => { it('should create a settleable reward without expiration', () => { const entry = RewardLedgerEntry.createSettleable({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.create(5), memo: 'Direct reward', }); expect(entry.isSettleable).toBe(true); expect(entry.rewardStatus).toBe(RewardStatus.SETTLEABLE); expect(entry.expireAt).toBeNull(); expect(entry.hashpowerAmount.value).toBe(5); }); }); describe('claim', () => { it('should transition pending to settleable', () => { const entry = RewardLedgerEntry.createPending({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); entry.clearDomainEvents(); entry.claim(); expect(entry.isSettleable).toBe(true); expect(entry.claimedAt).toBeDefined(); expect(entry.expireAt).toBeNull(); expect(entry.domainEvents.length).toBe(1); expect(entry.domainEvents[0].eventType).toBe('RewardClaimed'); }); it('should throw error when not pending', () => { const entry = RewardLedgerEntry.createSettleable({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); expect(() => entry.claim()).toThrow('只有待领取状态才能领取'); }); }); describe('expire', () => { it('should transition pending to expired', () => { const entry = RewardLedgerEntry.createPending({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); entry.clearDomainEvents(); entry.expire(); expect(entry.isExpired).toBe(true); expect(entry.expiredAt).toBeDefined(); expect(entry.domainEvents.length).toBe(1); expect(entry.domainEvents[0].eventType).toBe('RewardExpired'); }); it('should throw error when not pending', () => { const entry = RewardLedgerEntry.createSettleable({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); expect(() => entry.expire()).toThrow('只有待领取状态才能过期'); }); }); describe('settle', () => { it('should transition settleable to settled', () => { const entry = RewardLedgerEntry.createSettleable({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); entry.clearDomainEvents(); entry.settle('BNB', 0.25); expect(entry.isSettled).toBe(true); expect(entry.settledAt).toBeDefined(); expect(entry.domainEvents.length).toBe(1); expect(entry.domainEvents[0].eventType).toBe('RewardSettled'); }); it('should throw error when not settleable', () => { const entry = RewardLedgerEntry.createPending({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); expect(() => entry.settle('BNB', 0.25)).toThrow('只有可结算状态才能结算'); }); }); describe('getRemainingTimeMs', () => { it('should return remaining time for pending rewards', () => { const entry = RewardLedgerEntry.createPending({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); const remaining = entry.getRemainingTimeMs(); const expected24h = 24 * 60 * 60 * 1000; expect(remaining).toBeGreaterThan(expected24h - 1000); // Allow 1 second tolerance expect(remaining).toBeLessThanOrEqual(expected24h); }); it('should return 0 for settleable rewards', () => { const entry = RewardLedgerEntry.createSettleable({ userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: Money.USDT(500), hashpowerAmount: Hashpower.zero(), }); expect(entry.getRemainingTimeMs()).toBe(0); }); }); describe('reconstitute', () => { it('should rebuild aggregate from persistence data', () => { const data = { id: BigInt(1), userId: BigInt(100), rewardSource: createRewardSource(), usdtAmount: 500, hashpowerAmount: 5, rewardStatus: RewardStatus.SETTLEABLE, createdAt: new Date(), expireAt: null, claimedAt: new Date(), settledAt: null, expiredAt: null, memo: 'Test', }; const entry = RewardLedgerEntry.reconstitute(data); expect(entry.id).toBe(BigInt(1)); expect(entry.userId).toBe(BigInt(100)); expect(entry.usdtAmount.amount).toBe(500); expect(entry.hashpowerAmount.value).toBe(5); expect(entry.isSettleable).toBe(true); expect(entry.domainEvents.length).toBe(0); // No events on reconstitute }); }); });