test: harden business boundary conditions
This commit is contained in:
61
apps/server/src/notifications/notifications.service.spec.ts
Normal file
61
apps/server/src/notifications/notifications.service.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user