@dismissible/nestjs-api 0.0.2-canary.8976e84.0 → 0.0.2-canary.d2f56d7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @dismissible/nestjs-api might be problematic. Click here for more details.

Files changed (43) hide show
  1. package/config/.env.yaml +34 -1
  2. package/jest.config.ts +23 -1
  3. package/jest.e2e-config.ts +3 -1
  4. package/package.json +20 -11
  5. package/project.json +6 -3
  6. package/scripts/performance-test.config.json +29 -0
  7. package/scripts/performance-test.ts +855 -0
  8. package/src/app-test.factory.ts +8 -1
  9. package/src/app.module.ts +13 -1
  10. package/src/app.setup.ts +42 -4
  11. package/src/bootstrap.ts +8 -3
  12. package/src/config/app.config.spec.ts +118 -0
  13. package/src/config/app.config.ts +5 -0
  14. package/src/config/default-app.config.spec.ts +74 -0
  15. package/src/config/default-app.config.ts +15 -0
  16. package/src/cors/cors.config.spec.ts +162 -0
  17. package/src/cors/cors.config.ts +37 -0
  18. package/src/cors/index.ts +1 -0
  19. package/src/health/health.controller.spec.ts +7 -31
  20. package/src/health/health.controller.ts +4 -5
  21. package/src/health/health.module.ts +2 -3
  22. package/src/health/index.ts +0 -1
  23. package/src/helmet/helmet.config.spec.ts +197 -0
  24. package/src/helmet/helmet.config.ts +60 -0
  25. package/src/helmet/index.ts +1 -0
  26. package/src/server/server.config.spec.ts +65 -0
  27. package/src/swagger/swagger.config.spec.ts +113 -0
  28. package/src/swagger/swagger.config.ts +2 -10
  29. package/src/swagger/swagger.factory.spec.ts +126 -0
  30. package/src/validation/index.ts +1 -0
  31. package/src/validation/validation.config.ts +47 -0
  32. package/test/config/.env.yaml +23 -0
  33. package/test/config-jwt-auth/.env.yaml +26 -0
  34. package/test/dismiss.e2e-spec.ts +64 -0
  35. package/test/full-cycle.e2e-spec.ts +57 -0
  36. package/test/get-or-create.e2e-spec.ts +51 -0
  37. package/test/jwt-auth.e2e-spec.ts +353 -0
  38. package/test/restore.e2e-spec.ts +63 -0
  39. package/tsconfig.e2e.json +12 -0
  40. package/tsconfig.spec.json +12 -0
  41. package/src/app.e2e-spec.ts +0 -221
  42. package/src/health/health.service.spec.ts +0 -46
  43. package/src/health/health.service.ts +0 -16
@@ -0,0 +1,197 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { HelmetConfig } from './helmet.config';
5
+
6
+ describe('HelmetConfig', () => {
7
+ describe('enabled', () => {
8
+ it('should transform string "true" to boolean true', () => {
9
+ const config = plainToInstance(HelmetConfig, { enabled: 'true' });
10
+ expect(config.enabled).toBe(true);
11
+ });
12
+
13
+ it('should transform string "false" to boolean false', () => {
14
+ const config = plainToInstance(HelmetConfig, { enabled: 'false' });
15
+ expect(config.enabled).toBe(false);
16
+ });
17
+
18
+ it('should keep boolean true as true', () => {
19
+ const config = plainToInstance(HelmetConfig, { enabled: true });
20
+ expect(config.enabled).toBe(true);
21
+ });
22
+
23
+ it('should keep boolean false as false', () => {
24
+ const config = plainToInstance(HelmetConfig, { enabled: false });
25
+ expect(config.enabled).toBe(false);
26
+ });
27
+ });
28
+
29
+ describe('contentSecurityPolicy', () => {
30
+ it('should transform string "true" to boolean true', () => {
31
+ const config = plainToInstance(HelmetConfig, {
32
+ enabled: true,
33
+ contentSecurityPolicy: 'true',
34
+ });
35
+ expect(config.contentSecurityPolicy).toBe(true);
36
+ });
37
+
38
+ it('should transform string "false" to boolean false', () => {
39
+ const config = plainToInstance(HelmetConfig, {
40
+ enabled: true,
41
+ contentSecurityPolicy: 'false',
42
+ });
43
+ expect(config.contentSecurityPolicy).toBe(false);
44
+ });
45
+
46
+ it('should keep boolean values unchanged', () => {
47
+ const configTrue = plainToInstance(HelmetConfig, {
48
+ enabled: true,
49
+ contentSecurityPolicy: true,
50
+ });
51
+ const configFalse = plainToInstance(HelmetConfig, {
52
+ enabled: true,
53
+ contentSecurityPolicy: false,
54
+ });
55
+ expect(configTrue.contentSecurityPolicy).toBe(true);
56
+ expect(configFalse.contentSecurityPolicy).toBe(false);
57
+ });
58
+ });
59
+
60
+ describe('crossOriginEmbedderPolicy', () => {
61
+ it('should transform string "true" to boolean true', () => {
62
+ const config = plainToInstance(HelmetConfig, {
63
+ enabled: true,
64
+ crossOriginEmbedderPolicy: 'true',
65
+ });
66
+ expect(config.crossOriginEmbedderPolicy).toBe(true);
67
+ });
68
+
69
+ it('should transform string "false" to boolean false', () => {
70
+ const config = plainToInstance(HelmetConfig, {
71
+ enabled: true,
72
+ crossOriginEmbedderPolicy: 'false',
73
+ });
74
+ expect(config.crossOriginEmbedderPolicy).toBe(false);
75
+ });
76
+
77
+ it('should keep boolean values unchanged', () => {
78
+ const configTrue = plainToInstance(HelmetConfig, {
79
+ enabled: true,
80
+ crossOriginEmbedderPolicy: true,
81
+ });
82
+ const configFalse = plainToInstance(HelmetConfig, {
83
+ enabled: true,
84
+ crossOriginEmbedderPolicy: false,
85
+ });
86
+ expect(configTrue.crossOriginEmbedderPolicy).toBe(true);
87
+ expect(configFalse.crossOriginEmbedderPolicy).toBe(false);
88
+ });
89
+ });
90
+
91
+ describe('hstsMaxAge', () => {
92
+ it('should transform string to number', () => {
93
+ const config = plainToInstance(HelmetConfig, { enabled: true, hstsMaxAge: '31536000' });
94
+ expect(config.hstsMaxAge).toBe(31536000);
95
+ });
96
+
97
+ it('should keep number unchanged', () => {
98
+ const config = plainToInstance(HelmetConfig, { enabled: true, hstsMaxAge: 86400 });
99
+ expect(config.hstsMaxAge).toBe(86400);
100
+ });
101
+ });
102
+
103
+ describe('hstsIncludeSubDomains', () => {
104
+ it('should transform string "true" to boolean true', () => {
105
+ const config = plainToInstance(HelmetConfig, {
106
+ enabled: true,
107
+ hstsIncludeSubDomains: 'true',
108
+ });
109
+ expect(config.hstsIncludeSubDomains).toBe(true);
110
+ });
111
+
112
+ it('should transform string "false" to boolean false', () => {
113
+ const config = plainToInstance(HelmetConfig, {
114
+ enabled: true,
115
+ hstsIncludeSubDomains: 'false',
116
+ });
117
+ expect(config.hstsIncludeSubDomains).toBe(false);
118
+ });
119
+
120
+ it('should keep boolean values unchanged', () => {
121
+ const configTrue = plainToInstance(HelmetConfig, {
122
+ enabled: true,
123
+ hstsIncludeSubDomains: true,
124
+ });
125
+ const configFalse = plainToInstance(HelmetConfig, {
126
+ enabled: true,
127
+ hstsIncludeSubDomains: false,
128
+ });
129
+ expect(configTrue.hstsIncludeSubDomains).toBe(true);
130
+ expect(configFalse.hstsIncludeSubDomains).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe('hstsPreload', () => {
135
+ it('should transform string "true" to boolean true', () => {
136
+ const config = plainToInstance(HelmetConfig, {
137
+ enabled: true,
138
+ hstsPreload: 'true',
139
+ });
140
+ expect(config.hstsPreload).toBe(true);
141
+ });
142
+
143
+ it('should transform string "false" to boolean false', () => {
144
+ const config = plainToInstance(HelmetConfig, {
145
+ enabled: true,
146
+ hstsPreload: 'false',
147
+ });
148
+ expect(config.hstsPreload).toBe(false);
149
+ });
150
+
151
+ it('should keep boolean values unchanged', () => {
152
+ const configTrue = plainToInstance(HelmetConfig, {
153
+ enabled: true,
154
+ hstsPreload: true,
155
+ });
156
+ const configFalse = plainToInstance(HelmetConfig, {
157
+ enabled: true,
158
+ hstsPreload: false,
159
+ });
160
+ expect(configTrue.hstsPreload).toBe(true);
161
+ expect(configFalse.hstsPreload).toBe(false);
162
+ });
163
+ });
164
+
165
+ describe('validation', () => {
166
+ it('should pass validation with valid config', async () => {
167
+ const config = plainToInstance(HelmetConfig, {
168
+ enabled: true,
169
+ contentSecurityPolicy: true,
170
+ crossOriginEmbedderPolicy: true,
171
+ hstsMaxAge: 31536000,
172
+ hstsIncludeSubDomains: true,
173
+ hstsPreload: false,
174
+ });
175
+
176
+ const errors = await validate(config);
177
+ expect(errors).toHaveLength(0);
178
+ });
179
+
180
+ it('should pass validation with only required fields', async () => {
181
+ const config = plainToInstance(HelmetConfig, {
182
+ enabled: true,
183
+ });
184
+
185
+ const errors = await validate(config);
186
+ expect(errors).toHaveLength(0);
187
+ });
188
+
189
+ it('should fail validation when enabled is missing', async () => {
190
+ const config = plainToInstance(HelmetConfig, {});
191
+
192
+ const errors = await validate(config);
193
+ expect(errors.length).toBeGreaterThan(0);
194
+ expect(errors[0].property).toBe('enabled');
195
+ });
196
+ });
197
+ });
@@ -0,0 +1,60 @@
1
+ import { IsBoolean, IsNumber, IsOptional } from 'class-validator';
2
+ import { Type } from 'class-transformer';
3
+ import { TransformBoolean } from '@dismissible/nestjs-validation';
4
+
5
+ /**
6
+ * @see https://helmetjs.github.io/
7
+ */
8
+ export class HelmetConfig {
9
+ /**
10
+ * Whether to enable Helmet middleware
11
+ */
12
+ @IsBoolean()
13
+ @TransformBoolean()
14
+ public readonly enabled!: boolean;
15
+
16
+ /**
17
+ * Whether to enable Content-Security-Policy header.
18
+ * @default true
19
+ */
20
+ @IsBoolean()
21
+ @IsOptional()
22
+ @TransformBoolean()
23
+ public readonly contentSecurityPolicy?: boolean;
24
+
25
+ /**
26
+ * Whether to enable Cross-Origin-Embedder-Policy header.
27
+ * @default true
28
+ */
29
+ @IsBoolean()
30
+ @IsOptional()
31
+ @TransformBoolean()
32
+ public readonly crossOriginEmbedderPolicy?: boolean;
33
+
34
+ /**
35
+ * HSTS max-age in seconds.
36
+ * @default 31536000 (1 year)
37
+ */
38
+ @IsNumber()
39
+ @IsOptional()
40
+ @Type(() => Number)
41
+ public readonly hstsMaxAge?: number;
42
+
43
+ /**
44
+ * Whether to include subdomains in HSTS header.
45
+ * @default true
46
+ */
47
+ @IsBoolean()
48
+ @IsOptional()
49
+ @TransformBoolean()
50
+ public readonly hstsIncludeSubDomains?: boolean;
51
+
52
+ /**
53
+ * Whether to add HSTS preload directive.
54
+ * @default false
55
+ */
56
+ @IsBoolean()
57
+ @IsOptional()
58
+ @TransformBoolean()
59
+ public readonly hstsPreload?: boolean;
60
+ }
@@ -0,0 +1 @@
1
+ export * from './helmet.config';
@@ -0,0 +1,65 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { ServerConfig } from './server.config';
5
+
6
+ describe('ServerConfig', () => {
7
+ describe('port', () => {
8
+ it('should transform string to number', () => {
9
+ const config = plainToInstance(ServerConfig, { port: '3001' });
10
+ expect(config.port).toBe(3001);
11
+ });
12
+
13
+ it('should keep number unchanged', () => {
14
+ const config = plainToInstance(ServerConfig, { port: 3001 });
15
+ expect(config.port).toBe(3001);
16
+ });
17
+
18
+ it('should handle different port numbers', () => {
19
+ const config = plainToInstance(ServerConfig, { port: '8080' });
20
+ expect(config.port).toBe(8080);
21
+ });
22
+ });
23
+
24
+ describe('validation', () => {
25
+ it('should pass validation with valid port number', async () => {
26
+ const config = plainToInstance(ServerConfig, { port: 3001 });
27
+ const errors = await validate(config);
28
+ expect(errors).toHaveLength(0);
29
+ });
30
+
31
+ it('should pass validation with string port that transforms to number', async () => {
32
+ const config = plainToInstance(ServerConfig, { port: '3001' });
33
+ const errors = await validate(config);
34
+ expect(errors).toHaveLength(0);
35
+ });
36
+
37
+ it('should fail validation when port is missing', async () => {
38
+ const config = plainToInstance(ServerConfig, {});
39
+ const errors = await validate(config);
40
+ expect(errors.length).toBeGreaterThan(0);
41
+ expect(errors[0].property).toBe('port');
42
+ });
43
+
44
+ it('should fail validation when port is not a number', async () => {
45
+ const config = plainToInstance(ServerConfig, { port: 'not-a-number' });
46
+ const errors = await validate(config);
47
+ expect(errors.length).toBeGreaterThan(0);
48
+ expect(errors[0].property).toBe('port');
49
+ });
50
+
51
+ it('should fail validation when port is null', async () => {
52
+ const config = plainToInstance(ServerConfig, { port: null });
53
+ const errors = await validate(config);
54
+ expect(errors.length).toBeGreaterThan(0);
55
+ expect(errors[0].property).toBe('port');
56
+ });
57
+
58
+ it('should fail validation when port is undefined', async () => {
59
+ const config = plainToInstance(ServerConfig, { port: undefined });
60
+ const errors = await validate(config);
61
+ expect(errors.length).toBeGreaterThan(0);
62
+ expect(errors[0].property).toBe('port');
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,113 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { SwaggerConfig } from './swagger.config';
5
+
6
+ describe('SwaggerConfig', () => {
7
+ describe('enabled', () => {
8
+ it('should transform string "true" to boolean true', () => {
9
+ const config = plainToInstance(SwaggerConfig, { enabled: 'true' });
10
+ expect(config.enabled).toBe(true);
11
+ });
12
+
13
+ it('should transform string "false" to boolean false', () => {
14
+ const config = plainToInstance(SwaggerConfig, { enabled: 'false' });
15
+ expect(config.enabled).toBe(false);
16
+ });
17
+
18
+ it('should keep boolean true as true', () => {
19
+ const config = plainToInstance(SwaggerConfig, { enabled: true });
20
+ expect(config.enabled).toBe(true);
21
+ });
22
+
23
+ it('should keep boolean false as false', () => {
24
+ const config = plainToInstance(SwaggerConfig, { enabled: false });
25
+ expect(config.enabled).toBe(false);
26
+ });
27
+
28
+ it('should transform other string values to false', () => {
29
+ const config = plainToInstance(SwaggerConfig, { enabled: 'yes' });
30
+ expect(config.enabled).toBe(false);
31
+ });
32
+
33
+ it('should handle case-insensitive "true"', () => {
34
+ const config = plainToInstance(SwaggerConfig, { enabled: 'TRUE' });
35
+ expect(config.enabled).toBe(true);
36
+ });
37
+
38
+ it('should handle case-insensitive "True"', () => {
39
+ const config = plainToInstance(SwaggerConfig, { enabled: 'True' });
40
+ expect(config.enabled).toBe(true);
41
+ });
42
+ });
43
+
44
+ describe('path', () => {
45
+ it('should accept string path', () => {
46
+ const config = plainToInstance(SwaggerConfig, {
47
+ enabled: true,
48
+ path: 'api-docs',
49
+ });
50
+ expect(config.path).toBe('api-docs');
51
+ });
52
+
53
+ it('should allow path to be optional', () => {
54
+ const config = plainToInstance(SwaggerConfig, { enabled: true });
55
+ expect(config.path).toBeUndefined();
56
+ });
57
+ });
58
+
59
+ describe('validation', () => {
60
+ it('should pass validation with valid config', async () => {
61
+ const config = plainToInstance(SwaggerConfig, {
62
+ enabled: true,
63
+ path: 'docs',
64
+ });
65
+ const errors = await validate(config);
66
+ expect(errors).toHaveLength(0);
67
+ });
68
+
69
+ it('should pass validation with only required fields', async () => {
70
+ const config = plainToInstance(SwaggerConfig, { enabled: true });
71
+ const errors = await validate(config);
72
+ expect(errors).toHaveLength(0);
73
+ });
74
+
75
+ it('should pass validation when enabled is transformed from string', async () => {
76
+ const config = plainToInstance(SwaggerConfig, { enabled: 'true' });
77
+ const errors = await validate(config);
78
+ expect(errors).toHaveLength(0);
79
+ });
80
+
81
+ it('should fail validation when enabled is missing', async () => {
82
+ const config = plainToInstance(SwaggerConfig, {});
83
+ const errors = await validate(config);
84
+ expect(errors.length).toBeGreaterThan(0);
85
+ expect(errors[0].property).toBe('enabled');
86
+ });
87
+
88
+ it('should fail validation when enabled is null', async () => {
89
+ const config = plainToInstance(SwaggerConfig, { enabled: null });
90
+ const errors = await validate(config);
91
+ expect(errors.length).toBeGreaterThan(0);
92
+ expect(errors[0].property).toBe('enabled');
93
+ });
94
+
95
+ it('should fail validation when enabled is undefined', async () => {
96
+ const config = plainToInstance(SwaggerConfig, { enabled: undefined });
97
+ const errors = await validate(config);
98
+ expect(errors.length).toBeGreaterThan(0);
99
+ expect(errors[0].property).toBe('enabled');
100
+ });
101
+
102
+ it('should fail validation when path is not a string', async () => {
103
+ const config = plainToInstance(SwaggerConfig, {
104
+ enabled: true,
105
+ path: 123,
106
+ });
107
+ const errors = await validate(config);
108
+ expect(errors.length).toBeGreaterThan(0);
109
+ const pathError = errors.find((e) => e.property === 'path');
110
+ expect(pathError).toBeDefined();
111
+ });
112
+ });
113
+ });
@@ -1,17 +1,9 @@
1
1
  import { IsBoolean, IsOptional, IsString } from 'class-validator';
2
- import { Transform } from 'class-transformer';
2
+ import { TransformBoolean } from '@dismissible/nestjs-validation';
3
3
 
4
4
  export class SwaggerConfig {
5
5
  @IsBoolean()
6
- @Transform(({ value }) => {
7
- if (typeof value === 'boolean') {
8
- return value;
9
- }
10
- if (typeof value === 'string') {
11
- return value.toLowerCase() === 'true';
12
- }
13
- return value;
14
- })
6
+ @TransformBoolean()
15
7
  public readonly enabled!: boolean;
16
8
 
17
9
  @IsString()
@@ -0,0 +1,126 @@
1
+ import { INestApplication } from '@nestjs/common';
2
+ import { DocumentBuilder } from '@nestjs/swagger';
3
+ import { configureAppWithSwagger } from './swagger.factory';
4
+ import { SwaggerConfig } from './swagger.config';
5
+
6
+ // Mock SwaggerModule and DocumentBuilder
7
+ const mockCreateDocument = jest.fn();
8
+ const mockSetup = jest.fn();
9
+ const mockSetTitle = jest.fn().mockReturnThis();
10
+ const mockSetDescription = jest.fn().mockReturnThis();
11
+ const mockSetVersion = jest.fn().mockReturnThis();
12
+ const mockBuild = jest.fn().mockReturnValue({});
13
+
14
+ jest.mock('@nestjs/swagger', () => ({
15
+ SwaggerModule: {
16
+ createDocument: (...args: any[]) => mockCreateDocument(...args),
17
+ setup: (...args: any[]) => mockSetup(...args),
18
+ },
19
+ DocumentBuilder: jest.fn().mockImplementation(() => ({
20
+ setTitle: mockSetTitle,
21
+ setDescription: mockSetDescription,
22
+ setVersion: mockSetVersion,
23
+ build: mockBuild,
24
+ })),
25
+ }));
26
+
27
+ describe('configureAppWithSwagger', () => {
28
+ let mockApp: jest.Mocked<INestApplication>;
29
+
30
+ beforeEach(() => {
31
+ jest.clearAllMocks();
32
+ mockApp = {} as any;
33
+ });
34
+
35
+ it('should configure Swagger when enabled is true', () => {
36
+ const swaggerConfig: SwaggerConfig = {
37
+ enabled: true,
38
+ path: 'docs',
39
+ };
40
+
41
+ configureAppWithSwagger(mockApp, swaggerConfig);
42
+
43
+ expect(DocumentBuilder).toHaveBeenCalled();
44
+ expect(mockSetTitle).toHaveBeenCalledWith('Dismissible');
45
+ expect(mockSetDescription).toHaveBeenCalledWith('An API to handle dismissible items for users');
46
+ expect(mockSetVersion).toHaveBeenCalledWith('1.0');
47
+ expect(mockBuild).toHaveBeenCalled();
48
+ expect(mockSetup).toHaveBeenCalledWith('docs', mockApp, expect.any(Function), {
49
+ useGlobalPrefix: true,
50
+ });
51
+ });
52
+
53
+ it('should use default path "docs" when path is not provided', () => {
54
+ const swaggerConfig: SwaggerConfig = {
55
+ enabled: true,
56
+ };
57
+
58
+ configureAppWithSwagger(mockApp, swaggerConfig);
59
+
60
+ expect(mockSetup).toHaveBeenCalledWith('docs', mockApp, expect.any(Function), {
61
+ useGlobalPrefix: true,
62
+ });
63
+ });
64
+
65
+ it('should use custom path when provided', () => {
66
+ const swaggerConfig: SwaggerConfig = {
67
+ enabled: true,
68
+ path: 'api-docs',
69
+ };
70
+
71
+ configureAppWithSwagger(mockApp, swaggerConfig);
72
+
73
+ expect(mockSetup).toHaveBeenCalledWith('api-docs', mockApp, expect.any(Function), {
74
+ useGlobalPrefix: true,
75
+ });
76
+ });
77
+
78
+ it('should not configure Swagger when enabled is false', () => {
79
+ const swaggerConfig: SwaggerConfig = {
80
+ enabled: false,
81
+ };
82
+
83
+ configureAppWithSwagger(mockApp, swaggerConfig);
84
+
85
+ expect(DocumentBuilder).not.toHaveBeenCalled();
86
+ expect(mockSetup).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it('should create document with correct operationIdFactory', () => {
90
+ const swaggerConfig: SwaggerConfig = {
91
+ enabled: true,
92
+ };
93
+
94
+ configureAppWithSwagger(mockApp, swaggerConfig);
95
+
96
+ const setupCall = mockSetup.mock.calls[0];
97
+ const documentFactory = setupCall[2];
98
+ documentFactory();
99
+
100
+ expect(mockCreateDocument).toHaveBeenCalledWith(
101
+ mockApp,
102
+ {},
103
+ expect.objectContaining({
104
+ operationIdFactory: expect.any(Function),
105
+ }),
106
+ );
107
+ });
108
+
109
+ it('should use methodKey as operationId', () => {
110
+ const swaggerConfig: SwaggerConfig = {
111
+ enabled: true,
112
+ };
113
+
114
+ configureAppWithSwagger(mockApp, swaggerConfig);
115
+
116
+ const setupCall = mockSetup.mock.calls[0];
117
+ const documentFactory = setupCall[2];
118
+ documentFactory();
119
+
120
+ const createDocumentCall = mockCreateDocument.mock.calls[0];
121
+ const operationIdFactory = createDocumentCall[2].operationIdFactory;
122
+
123
+ expect(operationIdFactory('UserController', 'createUser')).toBe('createUser');
124
+ expect(operationIdFactory('ItemController', 'getItem')).toBe('getItem');
125
+ });
126
+ });
@@ -0,0 +1 @@
1
+ export * from './validation.config';
@@ -0,0 +1,47 @@
1
+ import { IsBoolean, IsOptional } from 'class-validator';
2
+ import { TransformBoolean } from '@dismissible/nestjs-validation';
3
+
4
+ /**
5
+ * Configuration for NestJS ValidationPipe
6
+ * @see https://docs.nestjs.com/techniques/validation
7
+ */
8
+ export class ValidationConfig {
9
+ /**
10
+ * Whether to disable error messages in validation responses.
11
+ * Should be true in production to prevent information disclosure.
12
+ * @default true in production, false in development
13
+ */
14
+ @IsBoolean()
15
+ @IsOptional()
16
+ @TransformBoolean()
17
+ public readonly disableErrorMessages?: boolean;
18
+
19
+ /**
20
+ * If set to true, validator will strip validated (returned) object of any properties
21
+ * that do not use any validation decorators.
22
+ * @default true
23
+ */
24
+ @IsBoolean()
25
+ @IsOptional()
26
+ @TransformBoolean()
27
+ public readonly whitelist?: boolean;
28
+
29
+ /**
30
+ * If set to true, instead of stripping non-whitelisted properties,
31
+ * validator will throw an error.
32
+ * @default true
33
+ */
34
+ @IsBoolean()
35
+ @IsOptional()
36
+ @TransformBoolean()
37
+ public readonly forbidNonWhitelisted?: boolean;
38
+
39
+ /**
40
+ * If set to true, class-transformer will attempt transformation based on TS reflected type.
41
+ * @default true
42
+ */
43
+ @IsBoolean()
44
+ @IsOptional()
45
+ @TransformBoolean()
46
+ public readonly transform?: boolean;
47
+ }
@@ -0,0 +1,23 @@
1
+ server:
2
+ port: 3001
3
+
4
+ cors:
5
+ enabled: false
6
+
7
+ helmet:
8
+ enabled: false
9
+
10
+ swagger:
11
+ enabled: false
12
+
13
+ db:
14
+ connectionString: ${DATABASE_URL:-postgresql://postgres:postgres@localhost:5432/dismissible}
15
+
16
+ jwtAuth:
17
+ enabled: false
18
+
19
+ validation:
20
+ disableErrorMessages: false
21
+ whitelist: true
22
+ forbidNonWhitelisted: true
23
+ transform: true
@@ -0,0 +1,26 @@
1
+ server:
2
+ port: 3001
3
+
4
+ cors:
5
+ enabled: false
6
+
7
+ helmet:
8
+ enabled: false
9
+
10
+ swagger:
11
+ enabled: false
12
+
13
+ db:
14
+ connectionString: ${DATABASE_URL:-postgresql://postgres:postgres@localhost:5432/dismissible}
15
+
16
+ jwtAuth:
17
+ enabled: true
18
+ wellKnownUrl: https://auth.example.com/.well-known/openid-configuration
19
+ issuer: https://auth.example.com
20
+ audience: dismissible-api
21
+
22
+ validation:
23
+ disableErrorMessages: false
24
+ whitelist: true
25
+ forbidNonWhitelisted: true
26
+ transform: true