This commit is contained in:
wchino
2026-06-13 17:38:25 +08:00
parent e7e938f261
commit 7b33d9f9fa
190 changed files with 23222 additions and 4336 deletions

View File

@@ -0,0 +1,119 @@
import { Decimal } from '@prisma/client/runtime/library';
import { DepositService } from './deposit.service';
import { expectAppError } from '../../testing/prisma-mock';
describe('DepositService', () => {
const tx = {
$queryRaw: jest.fn(),
depositOrder: {
findUnique: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
bet: {
findMany: jest.fn(),
},
user: {
findFirst: jest.fn(),
},
agentProfile: {
findUnique: jest.fn(),
},
};
const prisma = {
...tx,
$transaction: jest.fn(async (fn: (client: typeof tx) => Promise<unknown>) => fn(tx)),
};
const funds = {
deposit: jest.fn(),
withdraw: jest.fn(),
};
const credit = {
recalculateUsedCredit: jest.fn(),
};
let service: DepositService;
beforeEach(() => {
jest.clearAllMocks();
service = new DepositService(prisma as never, funds as never, credit as never);
tx.$queryRaw.mockResolvedValue([{ id: 1n }]);
tx.depositOrder.findUnique.mockResolvedValue({
id: 1n,
orderNo: 'DEP-1',
playerId: 7n,
amount: new Decimal(100),
approvedAmount: new Decimal(100),
status: 'APPROVED',
reviewedAt: new Date(),
screenshotUrl: '/uploads/a.png',
});
tx.bet.findMany.mockResolvedValue([]);
tx.user.findFirst.mockResolvedValue(null);
tx.agentProfile.findUnique.mockResolvedValue(null);
credit.recalculateUsedCredit.mockResolvedValue(undefined);
});
it('posts approved deposits as player wallet transactions and refreshes parent credit', async () => {
tx.depositOrder.findUnique.mockResolvedValue({
id: 1n,
orderNo: 'DEP-1',
playerId: 7n,
amount: new Decimal(100),
approvedAmount: null,
status: 'PENDING',
reviewedAt: null,
screenshotUrl: '/uploads/a.png',
});
tx.user.findFirst.mockResolvedValue({ parentId: 3n });
tx.agentProfile.findUnique.mockResolvedValue({
creditLimit: new Decimal(1000),
usedCredit: new Decimal(100),
});
await service.approveDepositOrder(1n, 2n, 100, 'Bank receipt checked');
expect(tx.depositOrder.update).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
status: 'APPROVED',
approvedAmount: new Decimal(100),
}),
}),
);
expect(funds.deposit).toHaveBeenCalledWith(
expect.objectContaining({
userId: 7n,
amount: new Decimal(100),
operatorId: 2n,
remark: 'Bank receipt checked',
referenceId: 'DEP-1',
transactionType: 'PLAYER_DEPOSIT',
businessKey: 'deposit:DEP-1:approve',
tx,
}),
);
expect(credit.recalculateUsedCredit).toHaveBeenCalledTimes(2);
expect(credit.recalculateUsedCredit).toHaveBeenNthCalledWith(1, 3n, tx);
expect(credit.recalculateUsedCredit).toHaveBeenNthCalledWith(2, 3n, tx);
});
it('blocks approved deposit revoke when bets exist after approval', async () => {
tx.bet.findMany.mockResolvedValue([{ id: 99n }]);
await expect(
service.reopenDepositOrderForReview(1n, 2n),
).rejects.toMatchObject(expectAppError('DEPOSIT_REVOKE_SETTLED_BETS'));
expect(funds.withdraw).not.toHaveBeenCalled();
expect(tx.depositOrder.update).not.toHaveBeenCalled();
});
it('does not delete a funded deposit order', async () => {
await expect(
service.deleteDepositOrder(1n, 2n),
).rejects.toMatchObject(expectAppError('DEPOSIT_ORDER_FUNDED_DELETE_FORBIDDEN'));
expect(tx.depositOrder.delete).not.toHaveBeenCalled();
});
});