refactor: enforce strict backend typing
Enable strict TypeScript and reject non-null and definite-assignment assertions. Declare externally populated DTO fields, preserve runtime validation, and narrow values at their owners. Verified: backend verify (257 tests), unchanged OpenAPI, and 21 SQLite/HTTP tests on retry. The initial SQLite run had two timeouts; baseline HEAD passed 21 tests as well.
This commit is contained in:
@@ -8,10 +8,10 @@ import {
|
||||
|
||||
export class ProblemDetailsDto {
|
||||
@ApiProperty({ example: 401 })
|
||||
status: number;
|
||||
declare status: number;
|
||||
|
||||
@ApiProperty({ example: 'UNAUTHORIZED' })
|
||||
code: string;
|
||||
declare code: string;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { BadRequestException, ValidationPipe } from '@nestjs/common';
|
||||
import { CredentialsDto } from '../auth/auth.dto';
|
||||
import {
|
||||
CreateBankCardDto,
|
||||
UpdateBankCardDto,
|
||||
} from '../bank-cards/bank-card.dto';
|
||||
|
||||
describe('DTO validation', () => {
|
||||
const pipe = new ValidationPipe({ transform: true, whitelist: true });
|
||||
|
||||
it('requires credentials and normalizes a valid email', async () => {
|
||||
const metadata = { type: 'body' as const, metatype: CredentialsDto };
|
||||
await expect(pipe.transform({}, metadata)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
await expect(
|
||||
pipe.transform({ email: 'user@example.com' }, metadata),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(
|
||||
pipe.transform(
|
||||
{ email: ' User@Example.com ', password: 'long-enough-password' },
|
||||
metadata,
|
||||
),
|
||||
).resolves.toEqual({
|
||||
email: 'user@example.com',
|
||||
password: 'long-enough-password',
|
||||
});
|
||||
});
|
||||
|
||||
it('requires and validates nested money in a new card', async () => {
|
||||
const metadata = { type: 'body' as const, metatype: CreateBankCardDto };
|
||||
const card = {
|
||||
id: '00000000-0000-4000-8000-000000000001',
|
||||
name: 'Everyday',
|
||||
openingDate: '2026-09-13',
|
||||
};
|
||||
for (const openingBalance of [undefined, null, {}, { currency: 'RUB' }]) {
|
||||
await expect(
|
||||
pipe.transform({ ...card, openingBalance }, metadata),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
}
|
||||
const valid = {
|
||||
...card,
|
||||
openingBalance: { currency: 'RUB', value: '100.00' },
|
||||
};
|
||||
await expect(pipe.transform(valid, metadata)).resolves.toEqual(valid);
|
||||
});
|
||||
|
||||
it('keeps update fields optional while requiring version and rejecting null', async () => {
|
||||
const metadata = { type: 'body' as const, metatype: UpdateBankCardDto };
|
||||
await expect(pipe.transform({}, metadata)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
await expect(
|
||||
pipe.transform({ version: 1, name: null }, metadata),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(
|
||||
pipe.transform({ version: 1 }, metadata),
|
||||
).resolves.toMatchObject({ version: 1 });
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,8 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class MoneyDto {
|
||||
@ApiProperty()
|
||||
currency: string;
|
||||
declare currency: string;
|
||||
|
||||
@ApiProperty()
|
||||
value: string;
|
||||
declare value: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user