test: harden business boundary conditions

This commit is contained in:
2026-07-15 00:03:55 +08:00
parent 17a5046ea0
commit b1f35f9d1a
65 changed files with 2311 additions and 293 deletions

View File

@@ -0,0 +1,31 @@
import 'reflect-metadata';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { BatchRoomExpenseDto } from './expense.dto';
describe('BatchRoomExpenseDto boundaries', () => {
it.each([
{ expenses: [] },
{ expenses: [{ roomId: 1, expenseType: 'water', amount: 0 }] },
{ expenses: [{ roomId: 1, expenseType: 'water', amount: -1 }] },
{ expenses: [{ roomId: 1, expenseType: 'water', amount: 1.001 }] },
{ periodStart: '2026-02-31' },
])('rejects invalid batch payload %#', async (override) => {
const dto = plainToInstance(BatchRoomExpenseDto, {
periodStart: '2026-07-01',
periodEnd: '2026-07-31',
expenses: [{ roomId: 1, expenseType: 'water', amount: 10 }],
...override,
});
await expect(validate(dto)).resolves.not.toHaveLength(0);
});
it('accepts a valid batch payload', async () => {
const dto = plainToInstance(BatchRoomExpenseDto, {
periodStart: '2026-07-01',
periodEnd: '2026-07-31',
expenses: [{ roomId: 1, expenseType: 'water', amount: 10.25 }],
});
await expect(validate(dto)).resolves.toHaveLength(0);
});
});

View File

@@ -1,4 +1,4 @@
import { IsDateString, IsIn, IsInt, IsString, IsNumber, IsOptional, Matches, Min } from 'class-validator';
import { ArrayNotEmpty, IsArray, IsDateString, IsIn, IsInt, IsISO8601, IsString, IsNumber, IsOptional, Matches, Min, ValidateNested } from 'class-validator';
import { PartialType } from '@nestjs/mapped-types';
import { Type } from 'class-transformer';
@@ -13,9 +13,13 @@ export class CreateRoomExpenseDto {
@Min(0.01)
amount: number;
@Matches(/^\d{4}-\d{2}-\d{2}$/)
@IsISO8601({ strict: true })
@IsDateString()
periodStart: string;
@Matches(/^\d{4}-\d{2}-\d{2}$/)
@IsISO8601({ strict: true })
@IsDateString()
periodEnd: string;
@@ -39,6 +43,8 @@ export class CreatePersonalExpenseDto {
@Min(0.01)
amount: number;
@Matches(/^\d{4}-\d{2}-\d{2}$/)
@IsISO8601({ strict: true })
@IsDateString()
expenseDate: string;
@@ -74,14 +80,38 @@ export class QueryPersonalExpenseDto {
studentId?: number;
}
export class BatchRoomExpenseDto {
export class BatchRoomExpenseItemDto {
@IsInt()
roomId: number;
@IsString()
expenseType: string;
@IsNumber({ maxDecimalPlaces: 2 })
@Min(0.01)
amount: number;
@IsOptional()
@IsString()
description?: string;
}
export class BatchRoomExpenseDto {
@Matches(/^\d{4}-\d{2}-\d{2}$/)
@IsISO8601({ strict: true })
@IsDateString()
periodStart: string;
@IsString()
@Matches(/^\d{4}-\d{2}-\d{2}$/)
@IsISO8601({ strict: true })
@IsDateString()
periodEnd: string;
expenses: { roomId: number; expenseType: string; amount: number; description?: string }[];
@IsArray()
@ArrayNotEmpty()
@ValidateNested({ each: true })
@Type(() => BatchRoomExpenseItemDto)
expenses: BatchRoomExpenseItemDto[];
}