rwadurian/backend/services/reward-service/test/app.e2e-spec.ts

319 lines
11 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import { App } from 'supertest/types';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HealthController } from '../src/api/controllers/health.controller';
import { RewardController } from '../src/api/controllers/reward.controller';
import { SettlementController } from '../src/api/controllers/settlement.controller';
import { RewardApplicationService } from '../src/application/services/reward-application.service';
import { JwtService } from '@nestjs/jwt';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from '../src/shared/strategies/jwt.strategy';
import { RewardStatus } from '../src/domain/value-objects/reward-status.enum';
import { RightType } from '../src/domain/value-objects/right-type.enum';
describe('Reward Service (e2e)', () => {
let app: INestApplication<App>;
let jwtService: JwtService;
let mockRewardService: any;
const TEST_JWT_SECRET = 'test-secret-key-for-testing';
const createTestToken = (userId: string = '100') => {
return jwtService.sign({
sub: userId,
username: 'testuser',
roles: ['user'],
});
};
beforeEach(async () => {
mockRewardService = {
getRewardSummary: jest.fn().mockResolvedValue({
pendingUsdt: 1000,
pendingHashpower: 0.5,
pendingExpireAt: new Date(Date.now() + 12 * 60 * 60 * 1000),
settleableUsdt: 500,
settleableHashpower: 0.2,
settledTotalUsdt: 2000,
settledTotalHashpower: 1.0,
expiredTotalUsdt: 100,
expiredTotalHashpower: 0.1,
}),
getRewardDetails: jest.fn().mockResolvedValue({
data: [
{
id: '1',
rightType: RightType.SHARE_RIGHT,
usdtAmount: 500,
hashpowerAmount: 0,
rewardStatus: RewardStatus.PENDING,
createdAt: new Date(),
expireAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
remainingTimeMs: 86400000,
claimedAt: null,
settledAt: null,
expiredAt: null,
memo: 'Test reward',
},
],
pagination: {
page: 1,
pageSize: 20,
total: 1,
},
}),
getPendingRewards: jest.fn().mockResolvedValue([
{
id: '1',
rightType: RightType.SHARE_RIGHT,
usdtAmount: 500,
hashpowerAmount: 0,
createdAt: new Date(),
expireAt: new Date(Date.now() + 12 * 60 * 60 * 1000),
remainingTimeMs: 43200000,
memo: 'Pending reward',
},
]),
settleRewards: jest.fn().mockResolvedValue({
success: true,
settledUsdtAmount: 500,
receivedAmount: 0.25,
settleCurrency: 'BNB',
txHash: '0x123abc',
}),
};
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [() => ({ JWT_SECRET: TEST_JWT_SECRET })],
}),
PassportModule,
JwtModule.register({
secret: TEST_JWT_SECRET,
signOptions: { expiresIn: '1h' },
}),
],
controllers: [HealthController, RewardController, SettlementController],
providers: [
{
provide: RewardApplicationService,
useValue: mockRewardService,
},
{
provide: JwtStrategy,
useFactory: (configService: ConfigService) => {
return new JwtStrategy({
get: (key: string) => key === 'JWT_SECRET' ? TEST_JWT_SECRET : undefined,
} as ConfigService);
},
inject: [ConfigService],
},
],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ transform: true }));
await app.init();
jwtService = moduleFixture.get<JwtService>(JwtService);
});
afterEach(async () => {
await app.close();
});
describe('Health Check', () => {
it('/health (GET) should return healthy status', () => {
return request(app.getHttpServer())
.get('/health')
.expect(200)
.expect((res) => {
expect(res.body.status).toBe('ok');
expect(res.body.service).toBe('reward-service');
expect(res.body.timestamp).toBeDefined();
});
});
});
describe('Rewards API', () => {
describe('GET /rewards/summary', () => {
it('should return 401 without auth token', () => {
return request(app.getHttpServer())
.get('/rewards/summary')
.expect(401);
});
it('should return reward summary with valid token', () => {
const token = createTestToken();
return request(app.getHttpServer())
.get('/rewards/summary')
.set('Authorization', `Bearer ${token}`)
.expect(200)
.expect((res) => {
expect(res.body.pendingUsdt).toBe(1000);
expect(res.body.settleableUsdt).toBe(500);
expect(res.body.settledTotalUsdt).toBe(2000);
expect(res.body.expiredTotalUsdt).toBe(100);
});
});
});
describe('GET /rewards/details', () => {
it('should return 401 without auth token', () => {
return request(app.getHttpServer())
.get('/rewards/details')
.expect(401);
});
it('should return paginated reward details with valid token', () => {
const token = createTestToken();
return request(app.getHttpServer())
.get('/rewards/details')
.set('Authorization', `Bearer ${token}`)
.expect(200)
.expect((res) => {
expect(res.body.data).toHaveLength(1);
expect(res.body.data[0].usdtAmount).toBe(500);
expect(res.body.pagination.total).toBe(1);
});
});
it('should accept filter parameters', () => {
const token = createTestToken();
return request(app.getHttpServer())
.get('/rewards/details')
.query({ status: RewardStatus.PENDING, page: 1, pageSize: 10 })
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
});
describe('GET /rewards/pending', () => {
it('should return 401 without auth token', () => {
return request(app.getHttpServer())
.get('/rewards/pending')
.expect(401);
});
it('should return pending rewards with countdown', () => {
const token = createTestToken();
return request(app.getHttpServer())
.get('/rewards/pending')
.set('Authorization', `Bearer ${token}`)
.expect(200)
.expect((res) => {
expect(res.body).toHaveLength(1);
expect(res.body[0].usdtAmount).toBe(500);
expect(res.body[0].remainingTimeMs).toBeGreaterThan(0);
});
});
});
});
describe('Settlement API', () => {
describe('POST /rewards/settle', () => {
it('should return 401 without auth token', () => {
return request(app.getHttpServer())
.post('/rewards/settle')
.send({ settleCurrency: 'BNB' })
.expect(401);
});
it('should settle rewards successfully with valid token', () => {
const token = createTestToken();
return request(app.getHttpServer())
.post('/rewards/settle')
.set('Authorization', `Bearer ${token}`)
.send({ settleCurrency: 'BNB' })
.expect(201)
.expect((res) => {
expect(res.body.success).toBe(true);
expect(res.body.settledUsdtAmount).toBe(500);
expect(res.body.receivedAmount).toBe(0.25);
expect(res.body.settleCurrency).toBe('BNB');
expect(res.body.txHash).toBe('0x123abc');
});
});
it('should validate settleCurrency parameter', () => {
const token = createTestToken();
return request(app.getHttpServer())
.post('/rewards/settle')
.set('Authorization', `Bearer ${token}`)
.send({ settleCurrency: '' })
.expect(400);
});
it('should accept different settlement currencies', async () => {
const token = createTestToken();
const currencies = ['BNB', 'OG', 'USDT', 'DST'];
for (const currency of currencies) {
mockRewardService.settleRewards.mockResolvedValueOnce({
success: true,
settledUsdtAmount: 500,
receivedAmount: currency === 'USDT' ? 500 : 0.25,
settleCurrency: currency,
txHash: '0x123',
});
await request(app.getHttpServer())
.post('/rewards/settle')
.set('Authorization', `Bearer ${token}`)
.send({ settleCurrency: currency })
.expect(201);
}
});
});
describe('Settlement failure scenarios', () => {
it('should handle no settleable rewards', () => {
const token = createTestToken();
mockRewardService.settleRewards.mockResolvedValueOnce({
success: false,
settledUsdtAmount: 0,
receivedAmount: 0,
settleCurrency: 'BNB',
error: '没有可结算的收益',
});
return request(app.getHttpServer())
.post('/rewards/settle')
.set('Authorization', `Bearer ${token}`)
.send({ settleCurrency: 'BNB' })
.expect(201)
.expect((res) => {
expect(res.body.success).toBe(false);
expect(res.body.error).toBe('没有可结算的收益');
});
});
it('should handle wallet service failure', () => {
const token = createTestToken();
mockRewardService.settleRewards.mockResolvedValueOnce({
success: false,
settledUsdtAmount: 500,
receivedAmount: 0,
settleCurrency: 'BNB',
error: 'Insufficient liquidity',
});
return request(app.getHttpServer())
.post('/rewards/settle')
.set('Authorization', `Bearer ${token}`)
.send({ settleCurrency: 'BNB' })
.expect(201)
.expect((res) => {
expect(res.body.success).toBe(false);
expect(res.body.error).toBe('Insufficient liquidity');
});
});
});
});
});