212 lines
7.0 KiB
TypeScript
212 lines
7.0 KiB
TypeScript
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
|
|
});
|
|
});
|
|
});
|