Skip to content

Commit 762d9b9

Browse files
Marfuenclaude
andcommitted
test(vendors): add UpdateVendorDto validation tests
Tests cover the PATCH validation fix: - Empty description accepted (the bug: onboarding vendors have "") - Empty name still rejected - assigneeId: null accepted (unassigned vendors) - Empty website transformed to undefined - Invalid URLs, enums, and unknown properties rejected Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cdc71c7 commit 762d9b9

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { plainToInstance } from 'class-transformer';
2+
import { validate } from 'class-validator';
3+
import { UpdateVendorDto } from './update-vendor.dto';
4+
5+
/**
6+
* Mirrors the global ValidationPipe config from main.ts:
7+
* whitelist: true, transform: true, enableImplicitConversion: true
8+
*/
9+
function toDto(plain: Record<string, unknown>): UpdateVendorDto {
10+
return plainToInstance(UpdateVendorDto, plain, {
11+
enableImplicitConversion: true,
12+
});
13+
}
14+
15+
describe('UpdateVendorDto', () => {
16+
it('should accept a valid full update payload', async () => {
17+
const dto = toDto({
18+
name: 'Acronis',
19+
description: 'Backup solutions provider',
20+
category: 'software_as_a_service',
21+
status: 'assessed',
22+
website: 'https://www.acronis.com',
23+
isSubProcessor: false,
24+
assigneeId: 'mem_abc123',
25+
});
26+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
27+
expect(errors).toHaveLength(0);
28+
});
29+
30+
it('should accept a minimal update (single field)', async () => {
31+
const dto = toDto({ website: 'https://www.acronis.com' });
32+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
33+
expect(errors).toHaveLength(0);
34+
});
35+
36+
it('should accept an empty body (no fields to update)', async () => {
37+
const dto = toDto({});
38+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
39+
expect(errors).toHaveLength(0);
40+
});
41+
42+
// ── The bug this DTO fix addresses ────────────────────────────────
43+
it('should accept empty description (vendors from onboarding)', async () => {
44+
const dto = toDto({
45+
name: 'Acronis',
46+
description: '',
47+
category: 'software_as_a_service',
48+
status: 'assessed',
49+
website: 'https://www.acronis.com',
50+
isSubProcessor: false,
51+
});
52+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
53+
expect(errors).toHaveLength(0);
54+
});
55+
56+
it('should still reject empty name', async () => {
57+
const dto = toDto({ name: '' });
58+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
59+
expect(errors.length).toBeGreaterThan(0);
60+
expect(errors[0].property).toBe('name');
61+
});
62+
63+
// ── assigneeId: null (unassigned vendor) ──────────────────────────
64+
it('should accept assigneeId: null', async () => {
65+
const dto = toDto({ assigneeId: null });
66+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
67+
expect(errors).toHaveLength(0);
68+
});
69+
70+
// ── website handling ──────────────────────────────────────────────
71+
it('should transform empty website to undefined (skip validation)', async () => {
72+
const dto = toDto({ website: '' });
73+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
74+
expect(errors).toHaveLength(0);
75+
expect(dto.website).toBeUndefined();
76+
});
77+
78+
it('should accept a valid website URL', async () => {
79+
const dto = toDto({ website: 'https://www.cloudflare.com' });
80+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
81+
expect(errors).toHaveLength(0);
82+
});
83+
84+
it('should reject an invalid website URL', async () => {
85+
const dto = toDto({ website: 'not-a-url' });
86+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
87+
expect(errors.length).toBeGreaterThan(0);
88+
expect(errors[0].property).toBe('website');
89+
});
90+
91+
// ── enum validation ───────────────────────────────────────────────
92+
it('should reject invalid category enum', async () => {
93+
const dto = toDto({ category: 'invalid_category' });
94+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
95+
expect(errors.length).toBeGreaterThan(0);
96+
expect(errors[0].property).toBe('category');
97+
});
98+
99+
it('should reject invalid status enum', async () => {
100+
const dto = toDto({ status: 'invalid_status' });
101+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
102+
expect(errors.length).toBeGreaterThan(0);
103+
expect(errors[0].property).toBe('status');
104+
});
105+
106+
// ── forbidNonWhitelisted ──────────────────────────────────────────
107+
it('should reject unknown properties', async () => {
108+
const dto = toDto({ name: 'Acronis', unknownField: 'value' });
109+
const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true });
110+
expect(errors.length).toBeGreaterThan(0);
111+
expect(errors.some((e) => e.property === 'unknownField')).toBe(true);
112+
});
113+
});

0 commit comments

Comments
 (0)