feat(admin+api): 代理停用默认、结算加固与冒烟配置探针

- 代理层级默认授信比例与停用冻结/禁登全局默认

- 结算预览去重、比分校验、串关当场判负与市场类型校验

- 站内信 Banner/公告自动通知开关;开发环境动态 API 端口

- 扩充 RBAC/结算/认证/返现单元测试与 agent skills
This commit is contained in:
2026-06-23 11:08:41 +08:00
parent fa06fee64c
commit ce84226219
47 changed files with 9284 additions and 118 deletions

View File

@@ -0,0 +1,126 @@
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');
});
});