- 代理层级默认授信比例与停用冻结/禁登全局默认 - 结算预览去重、比分校验、串关当场判负与市场类型校验 - 站内信 Banner/公告自动通知开关;开发环境动态 API 端口 - 扩充 RBAC/结算/认证/返现单元测试与 agent skills
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { Decimal } from '@prisma/client/runtime/library';
|
|
import { CashbackService } from './cashback.service';
|
|
|
|
describe('CashbackService previewBatch', () => {
|
|
const tx = {
|
|
cashbackBatch: {
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
},
|
|
cashbackItem: {
|
|
create: jest.fn(),
|
|
},
|
|
cashbackBet: {
|
|
create: jest.fn(),
|
|
},
|
|
};
|
|
const prisma = {
|
|
cashbackBatch: {
|
|
findFirst: jest.fn(),
|
|
},
|
|
cashbackBet: {
|
|
findMany: jest.fn(),
|
|
},
|
|
bet: {
|
|
findMany: jest.fn(),
|
|
},
|
|
cashbackRule: {
|
|
findMany: jest.fn(),
|
|
},
|
|
agentProfile: {
|
|
findMany: jest.fn(),
|
|
},
|
|
user: {
|
|
findMany: jest.fn(),
|
|
},
|
|
wallet: {
|
|
findMany: jest.fn(),
|
|
},
|
|
$transaction: jest.fn(async (fn: (client: typeof tx) => Promise<unknown>) => fn(tx)),
|
|
};
|
|
const funds = {};
|
|
const systemConfig = {
|
|
getPlatformDirectCashbackSettings: jest.fn(),
|
|
};
|
|
|
|
let service: CashbackService;
|
|
|
|
const platformPlayerId = 100n;
|
|
const adminInvitePlayerId = 101n;
|
|
const adminSponsorId = 200n;
|
|
const periodStart = new Date('2026-06-01T00:00:00.000Z');
|
|
const periodEnd = new Date('2026-06-01T23:59:59.999Z');
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
service = new CashbackService(prisma as never, funds as never, systemConfig as never);
|
|
systemConfig.getPlatformDirectCashbackSettings.mockResolvedValue({
|
|
platformDirectRate: 0.02,
|
|
adminInviteRate: 0.05,
|
|
});
|
|
prisma.cashbackBatch.findFirst.mockResolvedValue(null);
|
|
prisma.cashbackBet.findMany.mockResolvedValue([]);
|
|
prisma.cashbackRule.findMany.mockResolvedValue([]);
|
|
prisma.agentProfile.findMany.mockResolvedValue([]);
|
|
tx.cashbackBatch.findMany.mockResolvedValue([]);
|
|
tx.cashbackItem.create.mockResolvedValue({});
|
|
tx.cashbackBet.create.mockResolvedValue({});
|
|
tx.cashbackBatch.create.mockImplementation(({ data }: { data: Record<string, unknown> }) =>
|
|
Promise.resolve({ id: 1n, batchNo: 'CB-TEST', ...data }),
|
|
);
|
|
});
|
|
|
|
it('uses platformDirectRate for platform-direct players', async () => {
|
|
prisma.bet.findMany.mockResolvedValue([
|
|
{
|
|
id: 1n,
|
|
userId: platformPlayerId,
|
|
stake: new Decimal(1000),
|
|
status: 'WON',
|
|
settledAt: new Date('2026-06-01T12:00:00.000Z'),
|
|
user: { id: platformPlayerId, parentId: null, inviteSponsorId: null },
|
|
selections: [{ marketType: 'FT_1X2' }],
|
|
},
|
|
]);
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: platformPlayerId, username: 'pd1', parent: null },
|
|
]);
|
|
prisma.wallet.findMany.mockResolvedValue([
|
|
{ userId: platformPlayerId, availableBalance: new Decimal(0) },
|
|
]);
|
|
|
|
const result = await service.previewBatch(periodStart, periodEnd);
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].amount.toString()).toBe('20');
|
|
expect(result.totalAmount.toString()).toBe('20');
|
|
});
|
|
|
|
it('uses adminInviteRate for admin-invited platform-direct players', async () => {
|
|
prisma.bet.findMany.mockResolvedValue([
|
|
{
|
|
id: 2n,
|
|
userId: adminInvitePlayerId,
|
|
stake: new Decimal(1000),
|
|
status: 'LOST',
|
|
settledAt: new Date('2026-06-01T12:00:00.000Z'),
|
|
user: {
|
|
id: adminInvitePlayerId,
|
|
parentId: null,
|
|
inviteSponsorId: adminSponsorId,
|
|
},
|
|
selections: [{ marketType: 'FT_1X2' }],
|
|
},
|
|
]);
|
|
prisma.user.findMany
|
|
.mockResolvedValueOnce([{ id: adminSponsorId, userType: 'ADMIN' }])
|
|
.mockResolvedValueOnce([{ id: adminInvitePlayerId, username: 'inv1', parent: null }]);
|
|
prisma.wallet.findMany.mockResolvedValue([
|
|
{ userId: adminInvitePlayerId, availableBalance: new Decimal(0) },
|
|
]);
|
|
|
|
const result = await service.previewBatch(periodStart, periodEnd);
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].amount.toString()).toBe('50');
|
|
expect(result.totalAmount.toString()).toBe('50');
|
|
});
|
|
});
|