Skip to content

Commit 7893aed

Browse files
author
evgeniy.chernomortsev
committed
test(common): add nested dto and swagger examples
1 parent 4bc17c3 commit 7893aed

File tree

4 files changed

+225
-11
lines changed

4 files changed

+225
-11
lines changed

integration/standard-schema/e2e/standard-schema.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,64 @@ describe('StandardSchemaValidationPipe with Zod (e2e)', () => {
118118
})
119119
.expect(400);
120120
});
121+
122+
it('should create user with valid nested address', () => {
123+
return request(server)
124+
.post('/users')
125+
.send({
126+
name: 'John Doe',
127+
128+
address: {
129+
street: '123 Main St',
130+
city: 'New York',
131+
zipCode: '10001',
132+
country: 'US',
133+
},
134+
})
135+
.expect(201)
136+
.expect(res => {
137+
expect(res.body.success).to.equal(true);
138+
expect(res.body.data.address.street).to.equal('123 Main St');
139+
expect(res.body.data.address.city).to.equal('New York');
140+
expect(res.body.data.address.zipCode).to.equal('10001');
141+
expect(res.body.data.address.country).to.equal('US');
142+
});
143+
});
144+
145+
it('should reject invalid nested address zipCode', () => {
146+
return request(server)
147+
.post('/users')
148+
.send({
149+
name: 'John Doe',
150+
151+
address: {
152+
street: '123 Main St',
153+
city: 'New York',
154+
zipCode: 'invalid',
155+
country: 'US',
156+
},
157+
})
158+
.expect(400)
159+
.expect(res => {
160+
expect(res.body.message).to.be.an('array');
161+
expect(res.body.message.some((m: string) => m.includes('zip'))).to.be
162+
.true;
163+
});
164+
});
165+
166+
it('should reject incomplete nested address', () => {
167+
return request(server)
168+
.post('/users')
169+
.send({
170+
name: 'John Doe',
171+
172+
address: {
173+
street: '123 Main St',
174+
// missing city, zipCode, country
175+
},
176+
})
177+
.expect(400);
178+
});
121179
});
122180

123181
describe('POST /users/query', () => {
Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,114 @@
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
12
import { z } from 'zod';
23

34
/**
4-
* Zod schema for CreateUserDto
5+
* Zod schema for AddressDto (nested)
56
*/
6-
const createUserSchema = z.object({
7+
export const addressSchema = z.object({
8+
street: z.string().min(1, 'Street is required'),
9+
city: z.string().min(1, 'City is required'),
10+
zipCode: z.string().regex(/^\d{5}(-\d{4})?$/, 'Invalid zip code format'),
11+
country: z.string().min(2).max(2, 'Country must be ISO 3166-1 alpha-2 code'),
12+
});
13+
14+
/**
15+
* Nested DTO for address with Swagger documentation.
16+
* Uses `implements z.infer<typeof schema>` to ensure type safety.
17+
*/
18+
export class AddressDto implements z.infer<typeof addressSchema> {
19+
static schema = addressSchema;
20+
21+
@ApiProperty({ description: 'Street address', example: '123 Main St' })
22+
declare street: string;
23+
24+
@ApiProperty({ description: 'City name', example: 'New York' })
25+
declare city: string;
26+
27+
@ApiProperty({ description: 'ZIP code (US format)', example: '10001' })
28+
declare zipCode: string;
29+
30+
@ApiProperty({
31+
description: 'Country code (ISO 3166-1 alpha-2)',
32+
example: 'US',
33+
minLength: 2,
34+
maxLength: 2,
35+
})
36+
declare country: string;
37+
}
38+
39+
/**
40+
* Zod schema for CreateUserDto with nested address
41+
*/
42+
export const createUserSchema = z.object({
743
name: z.string().min(2, 'Name must be at least 2 characters'),
844
email: z.string().email('Invalid email format'),
945
age: z.number().int().min(0).max(150).optional(),
46+
address: addressSchema.optional(),
1047
});
1148

1249
/**
13-
* DTO with Zod schema for user creation
50+
* DTO with Zod schema and Swagger decorators.
51+
* Uses `implements z.infer<typeof schema>` to ensure type safety
52+
* between the Zod schema and class properties.
1453
*/
15-
export class CreateUserDto {
54+
export class CreateUserDto implements z.infer<typeof createUserSchema> {
1655
static schema = createUserSchema;
1756

18-
name: string;
19-
email: string;
20-
age?: number;
57+
@ApiProperty({
58+
description: 'User name',
59+
example: 'John Doe',
60+
minLength: 2,
61+
})
62+
declare name: string;
63+
64+
@ApiProperty({
65+
description: 'User email address',
66+
example: '[email protected]',
67+
format: 'email',
68+
})
69+
declare email: string;
70+
71+
@ApiPropertyOptional({
72+
description: 'User age',
73+
example: 30,
74+
minimum: 0,
75+
maximum: 150,
76+
})
77+
declare age?: number;
78+
79+
@ApiPropertyOptional({
80+
description: 'User address',
81+
type: () => AddressDto,
82+
})
83+
declare address?: AddressDto;
2184
}
2285

2386
/**
2487
* Zod schema for QueryDto
2588
*/
26-
const querySchema = z.object({
89+
export const querySchema = z.object({
2790
limit: z.coerce.number().int().min(1).max(100).optional(),
2891
offset: z.coerce.number().int().min(0).optional(),
2992
});
3093

3194
/**
3295
* DTO with Zod schema for query parameters
3396
*/
34-
export class QueryDto {
97+
export class QueryDto implements z.infer<typeof querySchema> {
3598
static schema = querySchema;
3699

37-
limit?: number;
38-
offset?: number;
100+
@ApiPropertyOptional({
101+
description: 'Number of items to return',
102+
example: 10,
103+
minimum: 1,
104+
maximum: 100,
105+
})
106+
declare limit?: number;
107+
108+
@ApiPropertyOptional({
109+
description: 'Number of items to skip',
110+
example: 0,
111+
minimum: 0,
112+
})
113+
declare offset?: number;
39114
}

package-lock.json

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"@nestjs/apollo": "13.2.3",
9999
"@nestjs/graphql": "13.2.3",
100100
"@nestjs/mongoose": "11.0.4",
101+
"@nestjs/swagger": "11.2.3",
101102
"@nestjs/typeorm": "11.0.0",
102103
"@types/amqplib": "0.10.8",
103104
"@types/bytes": "3.1.5",

0 commit comments

Comments
 (0)