从 main 同步站内信手动发送、媒体库选择、列表子路由重构等 API/Admin 改动;玩家端仅合并 ADMIN_CUSTOM 富文本、消息预览与充值截图压缩逻辑,保留 theme-4 样式。
230 lines
7.0 KiB
TypeScript
230 lines
7.0 KiB
TypeScript
import { PlayerMessagesService } from './player-messages.service';
|
|
|
|
describe('PlayerMessagesService', () => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const prisma: any = {
|
|
playerMessage: {
|
|
create: jest.fn(),
|
|
findMany: jest.fn(),
|
|
count: jest.fn(),
|
|
findFirst: jest.fn(),
|
|
update: jest.fn(),
|
|
updateMany: jest.fn(),
|
|
delete: jest.fn(),
|
|
deleteMany: jest.fn(),
|
|
createMany: jest.fn(),
|
|
},
|
|
playerMessageBroadcast: {
|
|
findMany: jest.fn(),
|
|
count: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
create: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
user: {
|
|
findUnique: jest.fn(),
|
|
findMany: jest.fn(),
|
|
findFirst: jest.fn(),
|
|
},
|
|
$transaction: jest.fn(async (fn: (tx: typeof prisma) => Promise<unknown>) => fn(prisma)),
|
|
};
|
|
|
|
let service: PlayerMessagesService;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
service = new PlayerMessagesService(prisma as never);
|
|
});
|
|
|
|
it('creates localized deposit approved messages', async () => {
|
|
prisma.playerMessage.create.mockResolvedValue({
|
|
id: 1n,
|
|
type: 'DEPOSIT_APPROVED',
|
|
title: '充值已到账',
|
|
body: 'body',
|
|
payload: { orderNo: 'DEP-1' },
|
|
readAt: null,
|
|
createdAt: new Date('2026-06-17T10:00:00.000Z'),
|
|
});
|
|
|
|
const result = await service.createDepositApprovedMessage(7n, {
|
|
depositOrderId: 10n,
|
|
orderNo: 'DEP-1',
|
|
amount: '100',
|
|
approvedAmount: '100',
|
|
locale: 'zh-CN',
|
|
});
|
|
|
|
expect(prisma.playerMessage.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
userId: 7n,
|
|
type: 'DEPOSIT_APPROVED',
|
|
title: '充值已到账',
|
|
}),
|
|
}),
|
|
);
|
|
expect(result.type).toBe('DEPOSIT_APPROVED');
|
|
expect(result.isRead).toBe(false);
|
|
});
|
|
|
|
it('returns paginated inbox with unread count', async () => {
|
|
prisma.playerMessage.findMany.mockResolvedValue([
|
|
{
|
|
id: 2n,
|
|
type: 'DEPOSIT_REJECTED',
|
|
title: 'Deposit rejected',
|
|
body: 'Rejected',
|
|
payload: null,
|
|
readAt: null,
|
|
createdAt: new Date('2026-06-17T11:00:00.000Z'),
|
|
},
|
|
]);
|
|
prisma.playerMessage.count.mockResolvedValueOnce(1).mockResolvedValueOnce(1);
|
|
|
|
const result = await service.listForPlayer(7n, 1, 20);
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.unreadCount).toBe(1);
|
|
expect(result.total).toBe(1);
|
|
});
|
|
|
|
it('deletes a single message for the player', async () => {
|
|
prisma.playerMessage.findFirst.mockResolvedValue({
|
|
id: 3n,
|
|
type: 'DEPOSIT_APPROVED',
|
|
title: 'Deposit approved',
|
|
body: 'body',
|
|
payload: null,
|
|
readAt: null,
|
|
createdAt: new Date('2026-06-17T12:00:00.000Z'),
|
|
});
|
|
prisma.playerMessage.delete.mockResolvedValue({ id: 3n });
|
|
|
|
const result = await service.deleteForPlayer(7n, 3n);
|
|
|
|
expect(prisma.playerMessage.delete).toHaveBeenCalledWith({ where: { id: 3n } });
|
|
expect(result).toEqual({ deleted: true, wasUnread: true });
|
|
});
|
|
|
|
it('deletes all messages for the player', async () => {
|
|
prisma.playerMessage.deleteMany.mockResolvedValue({ count: 4 });
|
|
|
|
const result = await service.deleteAllForPlayer(7n);
|
|
|
|
expect(prisma.playerMessage.deleteMany).toHaveBeenCalledWith({ where: { userId: 7n } });
|
|
expect(result).toEqual({ deleted: 4 });
|
|
});
|
|
|
|
it('broadcasts banner promotion inbox messages to active players', async () => {
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: 10n, locale: 'zh-CN', preferences: { locale: 'zh-CN' } },
|
|
{ id: 11n, locale: 'en-US', preferences: null },
|
|
]);
|
|
prisma.playerMessage.createMany.mockResolvedValue({ count: 2 });
|
|
|
|
const count = await service.broadcastBannerPromotion({
|
|
contentId: 99n,
|
|
translations: [
|
|
{ locale: 'zh-CN', title: '夏季活动', body: '<p>限时优惠</p>' },
|
|
{ locale: 'en-US', title: 'Summer promo', body: '' },
|
|
],
|
|
});
|
|
|
|
expect(count).toBe(2);
|
|
expect(prisma.playerMessage.createMany).toHaveBeenCalledWith({
|
|
data: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
userId: 10n,
|
|
type: 'BANNER_PROMO',
|
|
title: '夏季活动',
|
|
body: '限时优惠',
|
|
}),
|
|
expect.objectContaining({
|
|
userId: 11n,
|
|
type: 'BANNER_PROMO',
|
|
title: 'Summer promo',
|
|
}),
|
|
]),
|
|
});
|
|
});
|
|
|
|
it('broadcasts announcement promotion inbox messages to active players', async () => {
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: 10n, locale: 'zh-CN', preferences: { locale: 'zh-CN' } },
|
|
]);
|
|
prisma.playerMessage.createMany.mockResolvedValue({ count: 1 });
|
|
|
|
const count = await service.broadcastAnnouncementPromotion({
|
|
contentId: 100n,
|
|
translations: [{ locale: 'zh-CN', title: '维护通知', body: '系统维护中' }],
|
|
});
|
|
|
|
expect(count).toBe(1);
|
|
expect(prisma.playerMessage.createMany).toHaveBeenCalledWith({
|
|
data: [
|
|
expect.objectContaining({
|
|
userId: 10n,
|
|
type: 'ANNOUNCEMENT_PROMO',
|
|
title: '维护通知',
|
|
body: '系统维护中',
|
|
}),
|
|
],
|
|
});
|
|
});
|
|
|
|
it('creates custom broadcast to all active players', async () => {
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: 1n, username: 'p1', locale: 'zh-CN', preferences: { locale: 'zh-CN' } },
|
|
{ id: 2n, username: 'p2', locale: 'en-US', preferences: null },
|
|
]);
|
|
prisma.playerMessageBroadcast.create.mockResolvedValue({
|
|
id: 9n,
|
|
title: 'Hello',
|
|
body: '<p>World</p>',
|
|
translations: {
|
|
'en-US': { title: 'Hello', body: '<p>World</p>' },
|
|
'zh-CN': { title: '你好', body: '<p>内容</p>' },
|
|
},
|
|
targetType: 'ALL',
|
|
targetUserId: null,
|
|
targetUsername: null,
|
|
recipientCount: 2,
|
|
createdById: 99n,
|
|
createdByUsername: 'admin',
|
|
createdAt: new Date('2026-06-22T10:00:00.000Z'),
|
|
});
|
|
|
|
const result = await service.createCustomBroadcast({
|
|
translations: [
|
|
{ locale: 'en-US', title: 'Hello', body: '<p>World</p>' },
|
|
{ locale: 'zh-CN', title: '你好', body: '<p>内容</p>' },
|
|
],
|
|
targetType: 'ALL',
|
|
createdById: 99n,
|
|
createdByUsername: 'admin',
|
|
});
|
|
|
|
expect(result.recipientCount).toBe(2);
|
|
expect(prisma.playerMessage.createMany).toHaveBeenCalledWith({
|
|
data: [
|
|
expect.objectContaining({ userId: 1n, type: 'ADMIN_CUSTOM', title: '你好', broadcastId: 9n }),
|
|
expect.objectContaining({ userId: 2n, type: 'ADMIN_CUSTOM', title: 'Hello', broadcastId: 9n }),
|
|
],
|
|
});
|
|
});
|
|
|
|
it('deletes broadcast and cascades player messages', async () => {
|
|
prisma.playerMessageBroadcast.findUnique.mockResolvedValue({
|
|
id: 5n,
|
|
recipientCount: 3,
|
|
});
|
|
prisma.playerMessageBroadcast.delete.mockResolvedValue({ id: 5n });
|
|
|
|
const result = await service.deleteBroadcast(5n);
|
|
|
|
expect(result).toEqual({ deleted: true, recipientCount: 3 });
|
|
expect(prisma.playerMessageBroadcast.delete).toHaveBeenCalledWith({ where: { id: 5n } });
|
|
});
|
|
});
|