forked from wangziqi/gongxue-base
test: harden business boundary conditions
This commit is contained in:
77
apps/server/src/bills/bills.boundaries.spec.ts
Normal file
77
apps/server/src/bills/bills.boundaries.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { BillsService } from './bills.service';
|
||||
import { Bill } from '../entities/bill.entity';
|
||||
|
||||
function queryBuilder() {
|
||||
return {
|
||||
update: jest.fn().mockReturnThis(),
|
||||
set: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
execute: jest.fn().mockResolvedValue({ affected: 1 }),
|
||||
};
|
||||
}
|
||||
|
||||
function createService(bills: Partial<Bill>[] = []) {
|
||||
const billRepo = {
|
||||
find: jest.fn().mockResolvedValue(bills),
|
||||
findOne: jest.fn().mockResolvedValue(bills[0] ?? null),
|
||||
save: jest.fn(async (value) => value),
|
||||
createQueryBuilder: jest.fn(() => queryBuilder()),
|
||||
};
|
||||
const manager = {
|
||||
delete: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
const dataSource = { transaction: jest.fn(async (callback) => callback(manager)) };
|
||||
const service = new BillsService(
|
||||
billRepo as any,
|
||||
{ delete: jest.fn() } as any,
|
||||
{} as any,
|
||||
{ update: jest.fn() } as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
dataSource as any,
|
||||
{} as any,
|
||||
);
|
||||
return { service, billRepo, dataSource, manager };
|
||||
}
|
||||
|
||||
describe('BillsService state and batch boundaries', () => {
|
||||
it('rejects an empty batch status update', async () => {
|
||||
const { service, billRepo } = createService();
|
||||
await expect(service.batchUpdateStatus([], 'paid')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(billRepo.find).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects a batch status update when some ids do not exist', async () => {
|
||||
const { service, billRepo } = createService([{ id: 1, paidAmount: 0, outstandingAmount: 10 }]);
|
||||
await expect(service.batchUpdateStatus([1, 2], 'unpaid')).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(billRepo.createQueryBuilder).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects marking a partially paid bill unpaid', async () => {
|
||||
const { service, billRepo } = createService([{ id: 1, paidAmount: 10, outstandingAmount: 90, status: 'partially_paid' }]);
|
||||
await expect(service.updateStatus(1, { status: 'unpaid' })).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(billRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects an empty batch delete', async () => {
|
||||
const { service, dataSource } = createService();
|
||||
await expect(service.batchRemove([])).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(dataSource.transaction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects a batch delete when some ids do not exist', async () => {
|
||||
const { service, dataSource } = createService([{ id: 1, paidAmount: 0, status: 'unpaid' }]);
|
||||
await expect(service.batchRemove([1, 2])).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(dataSource.transaction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('deletes a bill and its links in one transaction', async () => {
|
||||
const { service, dataSource, manager } = createService([{ id: 1, paidAmount: 0, status: 'unpaid' }]);
|
||||
await expect(service.remove(1)).resolves.toEqual({ message: '账单已删除' });
|
||||
expect(dataSource.transaction).toHaveBeenCalledTimes(1);
|
||||
expect(manager.delete).toHaveBeenCalledTimes(2);
|
||||
expect(manager.update).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user