Backend verify / commits (push) Skipped
Backend verify / commits (pull_request) Failing after 32s
Backend verify / security (pull_request) Skipped
Backend verify / verify (pull_request) Skipped
Backend verify / security (push) Successful in 10s
Backend verify / verify (push) Failing after 6m21s
117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
import { BadRequestException, ValidationPipe } from '@nestjs/common';
|
|
|
|
import { LoginDto, RegisterDto } from '../modules/auth/presentation/auth.dto';
|
|
import {
|
|
CreateBankCardDto,
|
|
UpdateBankCardDto,
|
|
} from '../modules/banking/bank-cards/presentation/bank-card.dto';
|
|
|
|
describe('DTO validation', () => {
|
|
const pipe = new ValidationPipe({
|
|
forbidUnknownValues: true, transform: true, whitelist: true });
|
|
|
|
it('requires credentials and normalizes a valid email', async () => {
|
|
const metadata = { type: 'body' as const, metatype: RegisterDto };
|
|
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',
|
|
});
|
|
});
|
|
|
|
// Test intent: register accepts 8..72 printable ASCII characters only (bcrypt
|
|
// reads 72 bytes, so non-ASCII could be silently truncated); login only caps
|
|
// bytes so accounts created under the older policy can still sign in.
|
|
describe('password policy', () => {
|
|
const register = { type: 'body' as const, metatype: RegisterDto };
|
|
const login = { type: 'body' as const, metatype: LoginDto };
|
|
const body = (password: string) => ({ email: 'user@example.com', password });
|
|
const accepts = (value: unknown, metadata: typeof login | typeof register) =>
|
|
pipe.transform(value, metadata).then(
|
|
() => true,
|
|
(error: unknown) => {
|
|
if (!(error instanceof BadRequestException)) throw error;
|
|
return false;
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
['7 chars', 'a'.repeat(7), false],
|
|
['8 chars', 'a'.repeat(8), true],
|
|
['72 chars', 'a'.repeat(72), true],
|
|
['73 chars', 'a'.repeat(73), false],
|
|
['all printable symbols and space', 'Aa1 !~"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}', true],
|
|
['Cyrillic, 8+ chars', 'пароль-пароль', false],
|
|
['emoji', 'passw😀rd', false],
|
|
['tab', 'pass\tword1', false],
|
|
['newline', 'password1\n', false],
|
|
['DEL', 'password\x7f1', false],
|
|
['non-string', 12345678, false],
|
|
])('register: %s', async (_, password, accepted) => {
|
|
await expect(accepts(body(password as string), register)).resolves.toBe(accepted);
|
|
});
|
|
|
|
it('register keeps raw whitespace untouched', async () => {
|
|
const password = ' spaced pass ';
|
|
await expect(pipe.transform(body(password), register)).resolves.toEqual(
|
|
body(password),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
['legacy Cyrillic password', 'пароль-пароль', true],
|
|
['short legacy password', 'short', true],
|
|
['72 bytes', 'a'.repeat(72), true],
|
|
['73 bytes', 'a'.repeat(73), false],
|
|
['36 Cyrillic letters = 72 bytes', 'я'.repeat(36), true],
|
|
['37 Cyrillic letters = 74 bytes', 'я'.repeat(37), false],
|
|
['empty', '', false],
|
|
['non-string', 12345678, false],
|
|
])('login: %s', async (_, password, accepted) => {
|
|
await expect(accepts(body(password as string), login)).resolves.toBe(accepted);
|
|
});
|
|
});
|
|
|
|
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 });
|
|
});
|
|
});
|