@dismissible/nestjs-api 0.0.2-canary.585db17.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.
- package/config/.env.yaml +41 -0
- package/jest.config.ts +35 -0
- package/jest.e2e-config.ts +13 -0
- package/nest-cli.json +16 -0
- package/package.json +58 -0
- package/project.json +94 -0
- package/scripts/performance-test.config.json +29 -0
- package/scripts/performance-test.ts +845 -0
- package/src/app-test.factory.ts +39 -0
- package/src/app.module.ts +60 -0
- package/src/app.setup.ts +52 -0
- package/src/bootstrap.ts +29 -0
- package/src/config/app.config.spec.ts +117 -0
- package/src/config/app.config.ts +20 -0
- package/src/config/config.module.spec.ts +94 -0
- package/src/config/config.module.ts +50 -0
- package/src/config/default-app.config.spec.ts +74 -0
- package/src/config/default-app.config.ts +24 -0
- package/src/config/index.ts +2 -0
- package/src/cors/cors.config.spec.ts +162 -0
- package/src/cors/cors.config.ts +37 -0
- package/src/cors/index.ts +1 -0
- package/src/health/health.controller.spec.ts +24 -0
- package/src/health/health.controller.ts +16 -0
- package/src/health/health.module.ts +9 -0
- package/src/health/index.ts +2 -0
- package/src/helmet/helmet.config.spec.ts +197 -0
- package/src/helmet/helmet.config.ts +60 -0
- package/src/helmet/index.ts +1 -0
- package/src/index.ts +5 -0
- package/src/main.ts +3 -0
- package/src/server/index.ts +1 -0
- package/src/server/server.config.spec.ts +65 -0
- package/src/server/server.config.ts +8 -0
- package/src/swagger/index.ts +2 -0
- package/src/swagger/swagger.config.spec.ts +113 -0
- package/src/swagger/swagger.config.ts +12 -0
- package/src/swagger/swagger.factory.spec.ts +125 -0
- package/src/swagger/swagger.factory.ts +24 -0
- package/src/validation/index.ts +1 -0
- package/src/validation/validation.config.ts +47 -0
- package/test/config/.env.yaml +23 -0
- package/test/config-jwt-auth/.env.yaml +26 -0
- package/test/dismiss.e2e-spec.ts +61 -0
- package/test/full-cycle.e2e-spec.ts +55 -0
- package/test/get-or-create.e2e-spec.ts +51 -0
- package/test/jwt-auth.e2e-spec.ts +335 -0
- package/test/restore.e2e-spec.ts +61 -0
- package/tsconfig.app.json +13 -0
- package/tsconfig.e2e.json +12 -0
- package/tsconfig.json +13 -0
- package/tsconfig.spec.json +12 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { INestApplication } from '@nestjs/common';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { createTestApp, cleanupTestData } from '../src/app-test.factory';
|
|
5
|
+
import { JwtAuthService, IJwtValidationResult } from '@dismissible/nestjs-jwt-auth-hook';
|
|
6
|
+
import { NullLogger } from '@dismissible/nestjs-logger';
|
|
7
|
+
|
|
8
|
+
describe('JWT Authentication E2E', () => {
|
|
9
|
+
let app: INestApplication;
|
|
10
|
+
let mockValidateToken: jest.Mock;
|
|
11
|
+
|
|
12
|
+
const createValidResult = (sub = 'test-user'): IJwtValidationResult => ({
|
|
13
|
+
valid: true,
|
|
14
|
+
payload: {
|
|
15
|
+
sub,
|
|
16
|
+
iss: 'https://auth.example.com',
|
|
17
|
+
aud: 'dismissible-api',
|
|
18
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
19
|
+
iat: Math.floor(Date.now() / 1000),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const createInvalidResult = (error: string): IJwtValidationResult => ({
|
|
24
|
+
valid: false,
|
|
25
|
+
error,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
mockValidateToken = jest.fn();
|
|
30
|
+
|
|
31
|
+
app = await createTestApp({
|
|
32
|
+
moduleOptions: {
|
|
33
|
+
configPath: join(__dirname, 'config-jwt-auth'),
|
|
34
|
+
logger: NullLogger,
|
|
35
|
+
},
|
|
36
|
+
customize: (builder) => {
|
|
37
|
+
return builder.overrideProvider(JwtAuthService).useValue({
|
|
38
|
+
initializeJwksClient: jest.fn().mockResolvedValue(undefined),
|
|
39
|
+
extractBearerToken: (authHeader: string | undefined) => {
|
|
40
|
+
if (!authHeader) return null;
|
|
41
|
+
const parts = authHeader.split(' ');
|
|
42
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') return null;
|
|
43
|
+
return parts[1];
|
|
44
|
+
},
|
|
45
|
+
validateToken: mockValidateToken,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await cleanupTestData(app);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterAll(async () => {
|
|
54
|
+
if (app) {
|
|
55
|
+
await cleanupTestData(app);
|
|
56
|
+
await app.close();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
mockValidateToken.mockReset();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('Successful authentication scenarios', () => {
|
|
65
|
+
it('should allow GET request with valid bearer token', async () => {
|
|
66
|
+
mockValidateToken.mockResolvedValue(createValidResult('auth-user-1'));
|
|
67
|
+
|
|
68
|
+
const response = await request(app.getHttpServer())
|
|
69
|
+
.get('/v1/users/auth-user-1/items/jwt-test-get')
|
|
70
|
+
.set('Authorization', 'Bearer valid-token-123')
|
|
71
|
+
.expect(200);
|
|
72
|
+
|
|
73
|
+
expect(response.body.data).toBeDefined();
|
|
74
|
+
expect(response.body.data.itemId).toBe('jwt-test-get');
|
|
75
|
+
expect(response.body.data.userId).toBe('auth-user-1');
|
|
76
|
+
expect(mockValidateToken).toHaveBeenCalledWith('valid-token-123');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should allow DELETE (dismiss) request with valid bearer token', async () => {
|
|
80
|
+
mockValidateToken.mockResolvedValue(createValidResult('auth-user-2'));
|
|
81
|
+
|
|
82
|
+
await request(app.getHttpServer())
|
|
83
|
+
.get('/v1/users/auth-user-2/items/jwt-test-dismiss')
|
|
84
|
+
.set('Authorization', 'Bearer valid-token-123')
|
|
85
|
+
.expect(200);
|
|
86
|
+
|
|
87
|
+
const response = await request(app.getHttpServer())
|
|
88
|
+
.delete('/v1/users/auth-user-2/items/jwt-test-dismiss')
|
|
89
|
+
.set('Authorization', 'Bearer valid-token-123')
|
|
90
|
+
.expect(200);
|
|
91
|
+
|
|
92
|
+
expect(response.body.data).toBeDefined();
|
|
93
|
+
expect(response.body.data.dismissedAt).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should allow POST (restore) request with valid bearer token', async () => {
|
|
97
|
+
mockValidateToken.mockResolvedValue(createValidResult('auth-user-3'));
|
|
98
|
+
|
|
99
|
+
await request(app.getHttpServer())
|
|
100
|
+
.get('/v1/users/auth-user-3/items/jwt-test-restore')
|
|
101
|
+
.set('Authorization', 'Bearer valid-token-123')
|
|
102
|
+
.expect(200);
|
|
103
|
+
|
|
104
|
+
await request(app.getHttpServer())
|
|
105
|
+
.delete('/v1/users/auth-user-3/items/jwt-test-restore')
|
|
106
|
+
.set('Authorization', 'Bearer valid-token-123')
|
|
107
|
+
.expect(200);
|
|
108
|
+
|
|
109
|
+
const response = await request(app.getHttpServer())
|
|
110
|
+
.post('/v1/users/auth-user-3/items/jwt-test-restore')
|
|
111
|
+
.set('Authorization', 'Bearer valid-token-123')
|
|
112
|
+
.expect(201);
|
|
113
|
+
|
|
114
|
+
expect(response.body.data).toBeDefined();
|
|
115
|
+
expect(response.body.data.dismissedAt).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle lowercase bearer prefix', async () => {
|
|
119
|
+
mockValidateToken.mockResolvedValue(createValidResult('auth-user-4'));
|
|
120
|
+
|
|
121
|
+
const response = await request(app.getHttpServer())
|
|
122
|
+
.get('/v1/users/auth-user-4/items/jwt-test-lowercase')
|
|
123
|
+
.set('Authorization', 'bearer lowercase-token')
|
|
124
|
+
.expect(200);
|
|
125
|
+
|
|
126
|
+
expect(response.body.data).toBeDefined();
|
|
127
|
+
expect(mockValidateToken).toHaveBeenCalledWith('lowercase-token');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('Failed authentication scenarios', () => {
|
|
132
|
+
it('should reject request without Authorization header', async () => {
|
|
133
|
+
const response = await request(app.getHttpServer())
|
|
134
|
+
.get('/v1/users/noauth-user/items/jwt-test-noheader')
|
|
135
|
+
.expect(401);
|
|
136
|
+
|
|
137
|
+
expect(response.body.error).toBeDefined();
|
|
138
|
+
expect(response.body.error.message).toContain('Missing or invalid bearer token');
|
|
139
|
+
expect(mockValidateToken).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should reject request with empty Authorization header', async () => {
|
|
143
|
+
const response = await request(app.getHttpServer())
|
|
144
|
+
.get('/v1/users/noauth-user/items/jwt-test-empty')
|
|
145
|
+
.set('Authorization', '')
|
|
146
|
+
.expect(401);
|
|
147
|
+
|
|
148
|
+
expect(response.body.error).toBeDefined();
|
|
149
|
+
expect(response.body.error.message).toContain('Missing or invalid bearer token');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should reject request with non-Bearer auth scheme', async () => {
|
|
153
|
+
const response = await request(app.getHttpServer())
|
|
154
|
+
.get('/v1/users/noauth-user/items/jwt-test-basic')
|
|
155
|
+
.set('Authorization', 'Basic dXNlcjpwYXNz')
|
|
156
|
+
.expect(401);
|
|
157
|
+
|
|
158
|
+
expect(response.body.error).toBeDefined();
|
|
159
|
+
expect(response.body.error.message).toContain('Missing or invalid bearer token');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should reject request with Bearer but no token', async () => {
|
|
163
|
+
const response = await request(app.getHttpServer())
|
|
164
|
+
.get('/v1/users/noauth-user/items/jwt-test-notoken')
|
|
165
|
+
.set('Authorization', 'Bearer')
|
|
166
|
+
.expect(401);
|
|
167
|
+
|
|
168
|
+
expect(response.body.error).toBeDefined();
|
|
169
|
+
expect(response.body.error.message).toContain('Missing or invalid bearer token');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should reject request with expired token', async () => {
|
|
173
|
+
mockValidateToken.mockResolvedValue(createInvalidResult('jwt expired'));
|
|
174
|
+
|
|
175
|
+
const response = await request(app.getHttpServer())
|
|
176
|
+
.get('/v1/users/expired-user/items/jwt-test-expired')
|
|
177
|
+
.set('Authorization', 'Bearer expired-token')
|
|
178
|
+
.expect(401);
|
|
179
|
+
|
|
180
|
+
expect(response.body.error).toBeDefined();
|
|
181
|
+
expect(response.body.error.message).toContain('jwt expired');
|
|
182
|
+
expect(mockValidateToken).toHaveBeenCalledWith('expired-token');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should reject request with invalid token signature', async () => {
|
|
186
|
+
mockValidateToken.mockResolvedValue(createInvalidResult('invalid signature'));
|
|
187
|
+
|
|
188
|
+
const response = await request(app.getHttpServer())
|
|
189
|
+
.get('/v1/users/invalid-user/items/jwt-test-invalid-sig')
|
|
190
|
+
.set('Authorization', 'Bearer tampered-token')
|
|
191
|
+
.expect(401);
|
|
192
|
+
|
|
193
|
+
expect(response.body.error).toBeDefined();
|
|
194
|
+
expect(response.body.error.message).toContain('invalid signature');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should reject request with malformed token', async () => {
|
|
198
|
+
mockValidateToken.mockResolvedValue(createInvalidResult('Invalid token format'));
|
|
199
|
+
|
|
200
|
+
const response = await request(app.getHttpServer())
|
|
201
|
+
.get('/v1/users/malformed-user/items/jwt-test-malformed')
|
|
202
|
+
.set('Authorization', 'Bearer not.a.valid.jwt.token')
|
|
203
|
+
.expect(401);
|
|
204
|
+
|
|
205
|
+
expect(response.body.error).toBeDefined();
|
|
206
|
+
expect(response.body.error.message).toContain('Invalid token format');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should reject request when token is missing key ID (kid)', async () => {
|
|
210
|
+
mockValidateToken.mockResolvedValue(createInvalidResult('Token missing key ID (kid)'));
|
|
211
|
+
|
|
212
|
+
const response = await request(app.getHttpServer())
|
|
213
|
+
.get('/v1/users/nokid-user/items/jwt-test-nokid')
|
|
214
|
+
.set('Authorization', 'Bearer token-without-kid')
|
|
215
|
+
.expect(401);
|
|
216
|
+
|
|
217
|
+
expect(response.body.error).toBeDefined();
|
|
218
|
+
expect(response.body.error.message).toContain('Token missing key ID');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should reject request when signing key cannot be found', async () => {
|
|
222
|
+
mockValidateToken.mockResolvedValue(createInvalidResult('Unable to find signing key'));
|
|
223
|
+
|
|
224
|
+
const response = await request(app.getHttpServer())
|
|
225
|
+
.get('/v1/users/nokey-user/items/jwt-test-nokey')
|
|
226
|
+
.set('Authorization', 'Bearer token-with-unknown-kid')
|
|
227
|
+
.expect(401);
|
|
228
|
+
|
|
229
|
+
expect(response.body.error).toBeDefined();
|
|
230
|
+
expect(response.body.error.message).toContain('Unable to find signing key');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should reject dismiss request without valid token', async () => {
|
|
234
|
+
mockValidateToken.mockResolvedValue(createValidResult('dismiss-noauth-user'));
|
|
235
|
+
|
|
236
|
+
await request(app.getHttpServer())
|
|
237
|
+
.get('/v1/users/dismiss-noauth-user/items/jwt-test-dismiss-fail')
|
|
238
|
+
.set('Authorization', 'Bearer valid-token')
|
|
239
|
+
.expect(200);
|
|
240
|
+
|
|
241
|
+
mockValidateToken.mockReset();
|
|
242
|
+
|
|
243
|
+
const response = await request(app.getHttpServer())
|
|
244
|
+
.delete('/v1/users/dismiss-noauth-user/items/jwt-test-dismiss-fail')
|
|
245
|
+
.expect(401);
|
|
246
|
+
|
|
247
|
+
expect(response.body.error).toBeDefined();
|
|
248
|
+
expect(response.body.error.message).toContain('Missing or invalid bearer token');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should reject restore request without valid token', async () => {
|
|
252
|
+
mockValidateToken.mockResolvedValue(createValidResult('restore-noauth-user'));
|
|
253
|
+
|
|
254
|
+
await request(app.getHttpServer())
|
|
255
|
+
.get('/v1/users/restore-noauth-user/items/jwt-test-restore-fail')
|
|
256
|
+
.set('Authorization', 'Bearer valid-token')
|
|
257
|
+
.expect(200);
|
|
258
|
+
|
|
259
|
+
await request(app.getHttpServer())
|
|
260
|
+
.delete('/v1/users/restore-noauth-user/items/jwt-test-restore-fail')
|
|
261
|
+
.set('Authorization', 'Bearer valid-token')
|
|
262
|
+
.expect(200);
|
|
263
|
+
|
|
264
|
+
mockValidateToken.mockReset();
|
|
265
|
+
|
|
266
|
+
const response = await request(app.getHttpServer())
|
|
267
|
+
.post('/v1/users/restore-noauth-user/items/jwt-test-restore-fail')
|
|
268
|
+
.expect(401);
|
|
269
|
+
|
|
270
|
+
expect(response.body.error).toBeDefined();
|
|
271
|
+
expect(response.body.error.message).toContain('Missing or invalid bearer token');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('Token validation edge cases', () => {
|
|
276
|
+
it('should validate token for each request independently', async () => {
|
|
277
|
+
mockValidateToken.mockResolvedValueOnce(createValidResult('multi-user'));
|
|
278
|
+
|
|
279
|
+
await request(app.getHttpServer())
|
|
280
|
+
.get('/v1/users/multi-user/items/jwt-test-multi-1')
|
|
281
|
+
.set('Authorization', 'Bearer token-1')
|
|
282
|
+
.expect(200);
|
|
283
|
+
|
|
284
|
+
mockValidateToken.mockResolvedValueOnce(createValidResult('multi-user'));
|
|
285
|
+
|
|
286
|
+
await request(app.getHttpServer())
|
|
287
|
+
.get('/v1/users/multi-user/items/jwt-test-multi-2')
|
|
288
|
+
.set('Authorization', 'Bearer token-2')
|
|
289
|
+
.expect(200);
|
|
290
|
+
|
|
291
|
+
expect(mockValidateToken).toHaveBeenCalledTimes(2);
|
|
292
|
+
expect(mockValidateToken).toHaveBeenNthCalledWith(1, 'token-1');
|
|
293
|
+
expect(mockValidateToken).toHaveBeenNthCalledWith(2, 'token-2');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should handle validation service errors gracefully', async () => {
|
|
297
|
+
mockValidateToken.mockRejectedValue(new Error('JWKS client error'));
|
|
298
|
+
|
|
299
|
+
const response = await request(app.getHttpServer())
|
|
300
|
+
.get('/v1/users/error-user/items/jwt-test-error')
|
|
301
|
+
.set('Authorization', 'Bearer some-token')
|
|
302
|
+
.expect(500);
|
|
303
|
+
|
|
304
|
+
expect(response.body.statusCode).toBe(500);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should reject token with wrong issuer', async () => {
|
|
308
|
+
mockValidateToken.mockResolvedValue(
|
|
309
|
+
createInvalidResult('jwt issuer invalid. expected: https://auth.example.com'),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const response = await request(app.getHttpServer())
|
|
313
|
+
.get('/v1/users/issuer-user/items/jwt-test-issuer')
|
|
314
|
+
.set('Authorization', 'Bearer wrong-issuer-token')
|
|
315
|
+
.expect(401);
|
|
316
|
+
|
|
317
|
+
expect(response.body.error).toBeDefined();
|
|
318
|
+
expect(response.body.error.message).toContain('issuer invalid');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should reject token with wrong audience', async () => {
|
|
322
|
+
mockValidateToken.mockResolvedValue(
|
|
323
|
+
createInvalidResult('jwt audience invalid. expected: dismissible-api'),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const response = await request(app.getHttpServer())
|
|
327
|
+
.get('/v1/users/audience-user/items/jwt-test-audience')
|
|
328
|
+
.set('Authorization', 'Bearer wrong-audience-token')
|
|
329
|
+
.expect(401);
|
|
330
|
+
|
|
331
|
+
expect(response.body.error).toBeDefined();
|
|
332
|
+
expect(response.body.error.message).toContain('audience invalid');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { INestApplication } from '@nestjs/common';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { createTestApp, cleanupTestData } from '../src/app-test.factory';
|
|
5
|
+
import { NullLogger } from '@dismissible/nestjs-logger';
|
|
6
|
+
|
|
7
|
+
describe('POST /v1/users/:userId/items/:id (restore)', () => {
|
|
8
|
+
let app: INestApplication;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
app = await createTestApp({
|
|
12
|
+
moduleOptions: {
|
|
13
|
+
configPath: join(__dirname, 'config'),
|
|
14
|
+
logger: NullLogger,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
await cleanupTestData(app);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
await cleanupTestData(app);
|
|
22
|
+
await app.close();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should restore a dismissed item', async () => {
|
|
26
|
+
const userId = 'user-restore-1';
|
|
27
|
+
await request(app.getHttpServer()).get(`/v1/users/${userId}/items/restore-test-1`).expect(200);
|
|
28
|
+
|
|
29
|
+
await request(app.getHttpServer())
|
|
30
|
+
.delete(`/v1/users/${userId}/items/restore-test-1`)
|
|
31
|
+
.expect(200);
|
|
32
|
+
|
|
33
|
+
const response = await request(app.getHttpServer())
|
|
34
|
+
.post(`/v1/users/${userId}/items/restore-test-1`)
|
|
35
|
+
.expect(201);
|
|
36
|
+
|
|
37
|
+
expect(response.body.data).toBeDefined();
|
|
38
|
+
expect(response.body.data.dismissedAt).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return 400 when restoring non-existent item', async () => {
|
|
42
|
+
const response = await request(app.getHttpServer())
|
|
43
|
+
.post('/v1/users/user-123/items/non-existent-restore')
|
|
44
|
+
.expect(400);
|
|
45
|
+
|
|
46
|
+
expect(response.body.error).toBeDefined();
|
|
47
|
+
expect(response.body.error.message).toContain('not found');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return 400 when restoring non-dismissed item', async () => {
|
|
51
|
+
const userId = 'user-restore-2';
|
|
52
|
+
await request(app.getHttpServer()).get(`/v1/users/${userId}/items/restore-test-2`).expect(200);
|
|
53
|
+
|
|
54
|
+
const response = await request(app.getHttpServer())
|
|
55
|
+
.post(`/v1/users/${userId}/items/restore-test-2`)
|
|
56
|
+
.expect(400);
|
|
57
|
+
|
|
58
|
+
expect(response.body.error).toBeDefined();
|
|
59
|
+
expect(response.body.error.message).toContain('not dismissed');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"target": "ES2021"
|
|
10
|
+
},
|
|
11
|
+
"exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts", "**/*.e2e-spec.ts"],
|
|
12
|
+
"include": ["src/**/*.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"types": ["node", "jest"],
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"target": "ES2021"
|
|
10
|
+
},
|
|
11
|
+
"include": ["test/**/*.e2e-spec.ts", "src/**/*.ts"]
|
|
12
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"types": ["node", "jest"],
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"target": "ES2021"
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.spec.ts", "src/**/*.test.ts"]
|
|
12
|
+
}
|