import { BadRequestException, NotFoundException } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { NotificationsService } from './notifications.service'; function createQueryBuilder() { return { where: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), getMany: jest.fn().mockResolvedValue([]), }; } describe('NotificationsService boundaries', () => { it('rejects empty recipients and de-duplicates repeated recipients', async () => { const repo = { save: jest.fn().mockImplementation(async (rows) => rows), }; const service = new NotificationsService(repo as never, { emit: jest.fn() } as never); await expect( service.create({ recipientIds: [], type: 'test', title: '标题' }), ).rejects.toBeInstanceOf(BadRequestException); const saved = await service.create({ recipientIds: [1, 1, 2], type: 'test', title: '标题' }); expect(saved).toHaveLength(2); expect(repo.save).toHaveBeenCalledWith([ expect.objectContaining({ recipientId: 1 }), expect.objectContaining({ recipientId: 2 }), ]); }); it('clamps service-level page size to protect callers outside the controller', async () => { const qb = createQueryBuilder(); const service = new NotificationsService( { createQueryBuilder: jest.fn().mockReturnValue(qb) } as never, new EventEmitter2(), ); await service.findByUser(7, undefined, 1000); expect(qb.take).toHaveBeenCalledWith(100); }); it('does not allow marking another user notification as read', async () => { const repo = { findOne: jest.fn().mockResolvedValue(null), update: jest.fn() }; const service = new NotificationsService(repo as never, new EventEmitter2()); await expect(service.markRead(3, 7)).rejects.toBeInstanceOf(NotFoundException); expect(repo.update).not.toHaveBeenCalled(); }); it('treats marking an already-read notification as idempotent', async () => { const repo = { findOne: jest.fn().mockResolvedValue({ id: 3, recipientId: 7, isRead: true }), update: jest.fn(), }; const service = new NotificationsService(repo as never, new EventEmitter2()); await expect(service.markRead(3, 7)).resolves.toBeUndefined(); expect(repo.update).not.toHaveBeenCalled(); }); });