nodejs-express-starter 1.7.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/.dockerignore +3 -0
- package/.editorconfig +9 -0
- package/.env.example +22 -0
- package/.eslintignore +2 -0
- package/.eslintrc.json +32 -0
- package/.gitignore +14 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +4 -0
- package/Dockerfile +15 -0
- package/LICENSE +21 -0
- package/README.md +440 -0
- package/bin/createNodejsApp.js +105 -0
- package/docker-compose.dev.yml +4 -0
- package/docker-compose.prod.yml +4 -0
- package/docker-compose.test.yml +4 -0
- package/docker-compose.yml +30 -0
- package/jest.config.js +9 -0
- package/package.json +117 -0
- package/src/app.js +82 -0
- package/src/config/config.js +64 -0
- package/src/config/logger.js +26 -0
- package/src/config/morgan.js +25 -0
- package/src/config/passport.js +30 -0
- package/src/config/roles.js +12 -0
- package/src/config/tokens.js +10 -0
- package/src/controllers/auth.controller.js +59 -0
- package/src/controllers/index.js +2 -0
- package/src/controllers/user.controller.js +43 -0
- package/src/docs/components.yml +92 -0
- package/src/docs/swaggerDef.js +21 -0
- package/src/index.js +57 -0
- package/src/middlewares/auth.js +33 -0
- package/src/middlewares/error.js +47 -0
- package/src/middlewares/rateLimiter.js +11 -0
- package/src/middlewares/requestId.js +14 -0
- package/src/middlewares/validate.js +21 -0
- package/src/models/index.js +2 -0
- package/src/models/plugins/index.js +2 -0
- package/src/models/plugins/paginate.plugin.js +70 -0
- package/src/models/plugins/toJSON.plugin.js +43 -0
- package/src/models/token.model.js +44 -0
- package/src/models/user.model.js +91 -0
- package/src/routes/v1/auth.route.js +291 -0
- package/src/routes/v1/docs.route.js +21 -0
- package/src/routes/v1/health.route.js +43 -0
- package/src/routes/v1/index.js +39 -0
- package/src/routes/v1/user.route.js +252 -0
- package/src/services/auth.service.js +99 -0
- package/src/services/email.service.js +63 -0
- package/src/services/index.js +4 -0
- package/src/services/token.service.js +123 -0
- package/src/services/user.service.js +89 -0
- package/src/utils/ApiError.js +14 -0
- package/src/utils/catchAsync.js +5 -0
- package/src/utils/pick.js +17 -0
- package/src/validations/auth.validation.js +60 -0
- package/src/validations/custom.validation.js +21 -0
- package/src/validations/index.js +2 -0
- package/src/validations/user.validation.js +54 -0
- package/tests/fixtures/token.fixture.js +14 -0
- package/tests/fixtures/user.fixture.js +46 -0
- package/tests/integration/auth.test.js +587 -0
- package/tests/integration/docs.test.js +14 -0
- package/tests/integration/health.test.js +32 -0
- package/tests/integration/user.test.js +625 -0
- package/tests/unit/middlewares/error.test.js +168 -0
- package/tests/unit/models/plugins/paginate.plugin.test.js +61 -0
- package/tests/unit/models/plugins/toJSON.plugin.test.js +89 -0
- package/tests/unit/models/user.model.test.js +57 -0
- package/tests/utils/setupTestDB.js +18 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
const request = require('supertest');
|
|
2
|
+
const { faker } = require('@faker-js/faker');
|
|
3
|
+
const httpStatus = require('http-status');
|
|
4
|
+
const app = require('../../src/app');
|
|
5
|
+
const setupTestDB = require('../utils/setupTestDB');
|
|
6
|
+
const { User } = require('../../src/models');
|
|
7
|
+
const { userOne, userTwo, admin, insertUsers } = require('../fixtures/user.fixture');
|
|
8
|
+
const { userOneAccessToken, adminAccessToken } = require('../fixtures/token.fixture');
|
|
9
|
+
|
|
10
|
+
setupTestDB();
|
|
11
|
+
|
|
12
|
+
describe('User routes', () => {
|
|
13
|
+
describe('POST /v1/users', () => {
|
|
14
|
+
let newUser;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
newUser = {
|
|
18
|
+
name: faker.person.fullName(),
|
|
19
|
+
email: faker.internet.email().toLowerCase(),
|
|
20
|
+
password: 'password1',
|
|
21
|
+
role: 'user',
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should return 201 and successfully create new user if data is ok', async () => {
|
|
26
|
+
await insertUsers([admin]);
|
|
27
|
+
|
|
28
|
+
const res = await request(app)
|
|
29
|
+
.post('/v1/users')
|
|
30
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
31
|
+
.send(newUser)
|
|
32
|
+
.expect(httpStatus.CREATED);
|
|
33
|
+
|
|
34
|
+
expect(res.body).not.toHaveProperty('password');
|
|
35
|
+
expect(res.body).toEqual({
|
|
36
|
+
id: expect.anything(),
|
|
37
|
+
name: newUser.name,
|
|
38
|
+
email: newUser.email,
|
|
39
|
+
role: newUser.role,
|
|
40
|
+
isEmailVerified: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const dbUser = await User.findById(res.body.id);
|
|
44
|
+
expect(dbUser).toBeDefined();
|
|
45
|
+
expect(dbUser.password).not.toBe(newUser.password);
|
|
46
|
+
expect(dbUser).toMatchObject({ name: newUser.name, email: newUser.email, role: newUser.role, isEmailVerified: false });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('should be able to create an admin as well', async () => {
|
|
50
|
+
await insertUsers([admin]);
|
|
51
|
+
newUser.role = 'admin';
|
|
52
|
+
|
|
53
|
+
const res = await request(app)
|
|
54
|
+
.post('/v1/users')
|
|
55
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
56
|
+
.send(newUser)
|
|
57
|
+
.expect(httpStatus.CREATED);
|
|
58
|
+
|
|
59
|
+
expect(res.body.role).toBe('admin');
|
|
60
|
+
|
|
61
|
+
const dbUser = await User.findById(res.body.id);
|
|
62
|
+
expect(dbUser.role).toBe('admin');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should return 401 error if access token is missing', async () => {
|
|
66
|
+
await request(app).post('/v1/users').send(newUser).expect(httpStatus.UNAUTHORIZED);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should return 403 error if logged in user is not admin', async () => {
|
|
70
|
+
await insertUsers([userOne]);
|
|
71
|
+
|
|
72
|
+
await request(app)
|
|
73
|
+
.post('/v1/users')
|
|
74
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
75
|
+
.send(newUser)
|
|
76
|
+
.expect(httpStatus.FORBIDDEN);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should return 400 error if email is invalid', async () => {
|
|
80
|
+
await insertUsers([admin]);
|
|
81
|
+
newUser.email = 'invalidEmail';
|
|
82
|
+
|
|
83
|
+
await request(app)
|
|
84
|
+
.post('/v1/users')
|
|
85
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
86
|
+
.send(newUser)
|
|
87
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should return 400 error if email is already used', async () => {
|
|
91
|
+
await insertUsers([admin, userOne]);
|
|
92
|
+
newUser.email = userOne.email;
|
|
93
|
+
|
|
94
|
+
await request(app)
|
|
95
|
+
.post('/v1/users')
|
|
96
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
97
|
+
.send(newUser)
|
|
98
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('should return 400 error if password length is less than 8 characters', async () => {
|
|
102
|
+
await insertUsers([admin]);
|
|
103
|
+
newUser.password = 'passwo1';
|
|
104
|
+
|
|
105
|
+
await request(app)
|
|
106
|
+
.post('/v1/users')
|
|
107
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
108
|
+
.send(newUser)
|
|
109
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should return 400 error if password does not contain both letters and numbers', async () => {
|
|
113
|
+
await insertUsers([admin]);
|
|
114
|
+
newUser.password = 'password';
|
|
115
|
+
|
|
116
|
+
await request(app)
|
|
117
|
+
.post('/v1/users')
|
|
118
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
119
|
+
.send(newUser)
|
|
120
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
121
|
+
|
|
122
|
+
newUser.password = '1111111';
|
|
123
|
+
|
|
124
|
+
await request(app)
|
|
125
|
+
.post('/v1/users')
|
|
126
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
127
|
+
.send(newUser)
|
|
128
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('should return 400 error if role is neither user nor admin', async () => {
|
|
132
|
+
await insertUsers([admin]);
|
|
133
|
+
newUser.role = 'invalid';
|
|
134
|
+
|
|
135
|
+
await request(app)
|
|
136
|
+
.post('/v1/users')
|
|
137
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
138
|
+
.send(newUser)
|
|
139
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('GET /v1/users', () => {
|
|
144
|
+
test('should return 200 and apply the default query options', async () => {
|
|
145
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
146
|
+
|
|
147
|
+
const res = await request(app)
|
|
148
|
+
.get('/v1/users')
|
|
149
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
150
|
+
.send()
|
|
151
|
+
.expect(httpStatus.OK);
|
|
152
|
+
|
|
153
|
+
expect(res.body).toEqual({
|
|
154
|
+
results: expect.any(Array),
|
|
155
|
+
page: 1,
|
|
156
|
+
limit: 10,
|
|
157
|
+
totalPages: 1,
|
|
158
|
+
totalResults: 3,
|
|
159
|
+
});
|
|
160
|
+
expect(res.body.results).toHaveLength(3);
|
|
161
|
+
expect(res.body.results[0]).toEqual({
|
|
162
|
+
id: userOne._id.toHexString(),
|
|
163
|
+
name: userOne.name,
|
|
164
|
+
email: userOne.email,
|
|
165
|
+
role: userOne.role,
|
|
166
|
+
isEmailVerified: userOne.isEmailVerified,
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('should return 401 if access token is missing', async () => {
|
|
171
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
172
|
+
|
|
173
|
+
await request(app).get('/v1/users').send().expect(httpStatus.UNAUTHORIZED);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should return 403 if a non-admin is trying to access all users', async () => {
|
|
177
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
178
|
+
|
|
179
|
+
await request(app)
|
|
180
|
+
.get('/v1/users')
|
|
181
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
182
|
+
.send()
|
|
183
|
+
.expect(httpStatus.FORBIDDEN);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('should correctly apply filter on name field', async () => {
|
|
187
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
188
|
+
|
|
189
|
+
const res = await request(app)
|
|
190
|
+
.get('/v1/users')
|
|
191
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
192
|
+
.query({ name: userOne.name })
|
|
193
|
+
.send()
|
|
194
|
+
.expect(httpStatus.OK);
|
|
195
|
+
|
|
196
|
+
expect(res.body).toEqual({
|
|
197
|
+
results: expect.any(Array),
|
|
198
|
+
page: 1,
|
|
199
|
+
limit: 10,
|
|
200
|
+
totalPages: 1,
|
|
201
|
+
totalResults: 1,
|
|
202
|
+
});
|
|
203
|
+
expect(res.body.results).toHaveLength(1);
|
|
204
|
+
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('should correctly apply filter on role field', async () => {
|
|
208
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
209
|
+
|
|
210
|
+
const res = await request(app)
|
|
211
|
+
.get('/v1/users')
|
|
212
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
213
|
+
.query({ role: 'user' })
|
|
214
|
+
.send()
|
|
215
|
+
.expect(httpStatus.OK);
|
|
216
|
+
|
|
217
|
+
expect(res.body).toEqual({
|
|
218
|
+
results: expect.any(Array),
|
|
219
|
+
page: 1,
|
|
220
|
+
limit: 10,
|
|
221
|
+
totalPages: 1,
|
|
222
|
+
totalResults: 2,
|
|
223
|
+
});
|
|
224
|
+
expect(res.body.results).toHaveLength(2);
|
|
225
|
+
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
|
226
|
+
expect(res.body.results[1].id).toBe(userTwo._id.toHexString());
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('should correctly sort the returned array if descending sort param is specified', async () => {
|
|
230
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
231
|
+
|
|
232
|
+
const res = await request(app)
|
|
233
|
+
.get('/v1/users')
|
|
234
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
235
|
+
.query({ sortBy: 'role:desc' })
|
|
236
|
+
.send()
|
|
237
|
+
.expect(httpStatus.OK);
|
|
238
|
+
|
|
239
|
+
expect(res.body).toEqual({
|
|
240
|
+
results: expect.any(Array),
|
|
241
|
+
page: 1,
|
|
242
|
+
limit: 10,
|
|
243
|
+
totalPages: 1,
|
|
244
|
+
totalResults: 3,
|
|
245
|
+
});
|
|
246
|
+
expect(res.body.results).toHaveLength(3);
|
|
247
|
+
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
|
248
|
+
expect(res.body.results[1].id).toBe(userTwo._id.toHexString());
|
|
249
|
+
expect(res.body.results[2].id).toBe(admin._id.toHexString());
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('should correctly sort the returned array if ascending sort param is specified', async () => {
|
|
253
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
254
|
+
|
|
255
|
+
const res = await request(app)
|
|
256
|
+
.get('/v1/users')
|
|
257
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
258
|
+
.query({ sortBy: 'role:asc' })
|
|
259
|
+
.send()
|
|
260
|
+
.expect(httpStatus.OK);
|
|
261
|
+
|
|
262
|
+
expect(res.body).toEqual({
|
|
263
|
+
results: expect.any(Array),
|
|
264
|
+
page: 1,
|
|
265
|
+
limit: 10,
|
|
266
|
+
totalPages: 1,
|
|
267
|
+
totalResults: 3,
|
|
268
|
+
});
|
|
269
|
+
expect(res.body.results).toHaveLength(3);
|
|
270
|
+
expect(res.body.results[0].id).toBe(admin._id.toHexString());
|
|
271
|
+
expect(res.body.results[1].id).toBe(userOne._id.toHexString());
|
|
272
|
+
expect(res.body.results[2].id).toBe(userTwo._id.toHexString());
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should correctly sort the returned array if multiple sorting criteria are specified', async () => {
|
|
276
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
277
|
+
|
|
278
|
+
const res = await request(app)
|
|
279
|
+
.get('/v1/users')
|
|
280
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
281
|
+
.query({ sortBy: 'role:desc,name:asc' })
|
|
282
|
+
.send()
|
|
283
|
+
.expect(httpStatus.OK);
|
|
284
|
+
|
|
285
|
+
expect(res.body).toEqual({
|
|
286
|
+
results: expect.any(Array),
|
|
287
|
+
page: 1,
|
|
288
|
+
limit: 10,
|
|
289
|
+
totalPages: 1,
|
|
290
|
+
totalResults: 3,
|
|
291
|
+
});
|
|
292
|
+
expect(res.body.results).toHaveLength(3);
|
|
293
|
+
|
|
294
|
+
const expectedOrder = [userOne, userTwo, admin].sort((a, b) => {
|
|
295
|
+
if (a.role < b.role) {
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
if (a.role > b.role) {
|
|
299
|
+
return -1;
|
|
300
|
+
}
|
|
301
|
+
return a.name < b.name ? -1 : 1;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expectedOrder.forEach((user, index) => {
|
|
305
|
+
expect(res.body.results[index].id).toBe(user._id.toHexString());
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('should limit returned array if limit param is specified', async () => {
|
|
310
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
311
|
+
|
|
312
|
+
const res = await request(app)
|
|
313
|
+
.get('/v1/users')
|
|
314
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
315
|
+
.query({ limit: 2 })
|
|
316
|
+
.send()
|
|
317
|
+
.expect(httpStatus.OK);
|
|
318
|
+
|
|
319
|
+
expect(res.body).toEqual({
|
|
320
|
+
results: expect.any(Array),
|
|
321
|
+
page: 1,
|
|
322
|
+
limit: 2,
|
|
323
|
+
totalPages: 2,
|
|
324
|
+
totalResults: 3,
|
|
325
|
+
});
|
|
326
|
+
expect(res.body.results).toHaveLength(2);
|
|
327
|
+
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
|
328
|
+
expect(res.body.results[1].id).toBe(userTwo._id.toHexString());
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('should return the correct page if page and limit params are specified', async () => {
|
|
332
|
+
await insertUsers([userOne, userTwo, admin]);
|
|
333
|
+
|
|
334
|
+
const res = await request(app)
|
|
335
|
+
.get('/v1/users')
|
|
336
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
337
|
+
.query({ page: 2, limit: 2 })
|
|
338
|
+
.send()
|
|
339
|
+
.expect(httpStatus.OK);
|
|
340
|
+
|
|
341
|
+
expect(res.body).toEqual({
|
|
342
|
+
results: expect.any(Array),
|
|
343
|
+
page: 2,
|
|
344
|
+
limit: 2,
|
|
345
|
+
totalPages: 2,
|
|
346
|
+
totalResults: 3,
|
|
347
|
+
});
|
|
348
|
+
expect(res.body.results).toHaveLength(1);
|
|
349
|
+
expect(res.body.results[0].id).toBe(admin._id.toHexString());
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('GET /v1/users/:userId', () => {
|
|
354
|
+
test('should return 200 and the user object if data is ok', async () => {
|
|
355
|
+
await insertUsers([userOne]);
|
|
356
|
+
|
|
357
|
+
const res = await request(app)
|
|
358
|
+
.get(`/v1/users/${userOne._id}`)
|
|
359
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
360
|
+
.send()
|
|
361
|
+
.expect(httpStatus.OK);
|
|
362
|
+
|
|
363
|
+
expect(res.body).not.toHaveProperty('password');
|
|
364
|
+
expect(res.body).toEqual({
|
|
365
|
+
id: userOne._id.toHexString(),
|
|
366
|
+
email: userOne.email,
|
|
367
|
+
name: userOne.name,
|
|
368
|
+
role: userOne.role,
|
|
369
|
+
isEmailVerified: userOne.isEmailVerified,
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('should return 401 error if access token is missing', async () => {
|
|
374
|
+
await insertUsers([userOne]);
|
|
375
|
+
|
|
376
|
+
await request(app).get(`/v1/users/${userOne._id}`).send().expect(httpStatus.UNAUTHORIZED);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('should return 403 error if user is trying to get another user', async () => {
|
|
380
|
+
await insertUsers([userOne, userTwo]);
|
|
381
|
+
|
|
382
|
+
await request(app)
|
|
383
|
+
.get(`/v1/users/${userTwo._id}`)
|
|
384
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
385
|
+
.send()
|
|
386
|
+
.expect(httpStatus.FORBIDDEN);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('should return 200 and the user object if admin is trying to get another user', async () => {
|
|
390
|
+
await insertUsers([userOne, admin]);
|
|
391
|
+
|
|
392
|
+
await request(app)
|
|
393
|
+
.get(`/v1/users/${userOne._id}`)
|
|
394
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
395
|
+
.send()
|
|
396
|
+
.expect(httpStatus.OK);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('should return 400 error if userId is not a valid mongo id', async () => {
|
|
400
|
+
await insertUsers([admin]);
|
|
401
|
+
|
|
402
|
+
await request(app)
|
|
403
|
+
.get('/v1/users/invalidId')
|
|
404
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
405
|
+
.send()
|
|
406
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('should return 404 error if user is not found', async () => {
|
|
410
|
+
await insertUsers([admin]);
|
|
411
|
+
|
|
412
|
+
await request(app)
|
|
413
|
+
.get(`/v1/users/${userOne._id}`)
|
|
414
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
415
|
+
.send()
|
|
416
|
+
.expect(httpStatus.NOT_FOUND);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe('DELETE /v1/users/:userId', () => {
|
|
421
|
+
test('should return 204 if data is ok', async () => {
|
|
422
|
+
await insertUsers([userOne]);
|
|
423
|
+
|
|
424
|
+
await request(app)
|
|
425
|
+
.delete(`/v1/users/${userOne._id}`)
|
|
426
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
427
|
+
.send()
|
|
428
|
+
.expect(httpStatus.NO_CONTENT);
|
|
429
|
+
|
|
430
|
+
const dbUser = await User.findById(userOne._id);
|
|
431
|
+
expect(dbUser).toBeNull();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('should return 401 error if access token is missing', async () => {
|
|
435
|
+
await insertUsers([userOne]);
|
|
436
|
+
|
|
437
|
+
await request(app).delete(`/v1/users/${userOne._id}`).send().expect(httpStatus.UNAUTHORIZED);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('should return 403 error if user is trying to delete another user', async () => {
|
|
441
|
+
await insertUsers([userOne, userTwo]);
|
|
442
|
+
|
|
443
|
+
await request(app)
|
|
444
|
+
.delete(`/v1/users/${userTwo._id}`)
|
|
445
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
446
|
+
.send()
|
|
447
|
+
.expect(httpStatus.FORBIDDEN);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('should return 204 if admin is trying to delete another user', async () => {
|
|
451
|
+
await insertUsers([userOne, admin]);
|
|
452
|
+
|
|
453
|
+
await request(app)
|
|
454
|
+
.delete(`/v1/users/${userOne._id}`)
|
|
455
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
456
|
+
.send()
|
|
457
|
+
.expect(httpStatus.NO_CONTENT);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('should return 400 error if userId is not a valid mongo id', async () => {
|
|
461
|
+
await insertUsers([admin]);
|
|
462
|
+
|
|
463
|
+
await request(app)
|
|
464
|
+
.delete('/v1/users/invalidId')
|
|
465
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
466
|
+
.send()
|
|
467
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('should return 404 error if user already is not found', async () => {
|
|
471
|
+
await insertUsers([admin]);
|
|
472
|
+
|
|
473
|
+
await request(app)
|
|
474
|
+
.delete(`/v1/users/${userOne._id}`)
|
|
475
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
476
|
+
.send()
|
|
477
|
+
.expect(httpStatus.NOT_FOUND);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe('PATCH /v1/users/:userId', () => {
|
|
482
|
+
test('should return 200 and successfully update user if data is ok', async () => {
|
|
483
|
+
await insertUsers([userOne]);
|
|
484
|
+
const updateBody = {
|
|
485
|
+
name: faker.person.fullName(),
|
|
486
|
+
email: faker.internet.email().toLowerCase(),
|
|
487
|
+
password: 'newPassword1',
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const res = await request(app)
|
|
491
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
492
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
493
|
+
.send(updateBody)
|
|
494
|
+
.expect(httpStatus.OK);
|
|
495
|
+
|
|
496
|
+
expect(res.body).not.toHaveProperty('password');
|
|
497
|
+
expect(res.body).toEqual({
|
|
498
|
+
id: userOne._id.toHexString(),
|
|
499
|
+
name: updateBody.name,
|
|
500
|
+
email: updateBody.email,
|
|
501
|
+
role: 'user',
|
|
502
|
+
isEmailVerified: false,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const dbUser = await User.findById(userOne._id);
|
|
506
|
+
expect(dbUser).toBeDefined();
|
|
507
|
+
expect(dbUser.password).not.toBe(updateBody.password);
|
|
508
|
+
expect(dbUser).toMatchObject({ name: updateBody.name, email: updateBody.email, role: 'user' });
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test('should return 401 error if access token is missing', async () => {
|
|
512
|
+
await insertUsers([userOne]);
|
|
513
|
+
const updateBody = { name: faker.person.fullName() };
|
|
514
|
+
|
|
515
|
+
await request(app).patch(`/v1/users/${userOne._id}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('should return 403 if user is updating another user', async () => {
|
|
519
|
+
await insertUsers([userOne, userTwo]);
|
|
520
|
+
const updateBody = { name: faker.person.fullName() };
|
|
521
|
+
|
|
522
|
+
await request(app)
|
|
523
|
+
.patch(`/v1/users/${userTwo._id}`)
|
|
524
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
525
|
+
.send(updateBody)
|
|
526
|
+
.expect(httpStatus.FORBIDDEN);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test('should return 200 and successfully update user if admin is updating another user', async () => {
|
|
530
|
+
await insertUsers([userOne, admin]);
|
|
531
|
+
const updateBody = { name: faker.person.fullName() };
|
|
532
|
+
|
|
533
|
+
await request(app)
|
|
534
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
535
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
536
|
+
.send(updateBody)
|
|
537
|
+
.expect(httpStatus.OK);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('should return 404 if admin is updating another user that is not found', async () => {
|
|
541
|
+
await insertUsers([admin]);
|
|
542
|
+
const updateBody = { name: faker.person.fullName() };
|
|
543
|
+
|
|
544
|
+
await request(app)
|
|
545
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
546
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
547
|
+
.send(updateBody)
|
|
548
|
+
.expect(httpStatus.NOT_FOUND);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test('should return 400 error if userId is not a valid mongo id', async () => {
|
|
552
|
+
await insertUsers([admin]);
|
|
553
|
+
const updateBody = { name: faker.person.fullName() };
|
|
554
|
+
|
|
555
|
+
await request(app)
|
|
556
|
+
.patch(`/v1/users/invalidId`)
|
|
557
|
+
.set('Authorization', `Bearer ${adminAccessToken}`)
|
|
558
|
+
.send(updateBody)
|
|
559
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test('should return 400 if email is invalid', async () => {
|
|
563
|
+
await insertUsers([userOne]);
|
|
564
|
+
const updateBody = { email: 'invalidEmail' };
|
|
565
|
+
|
|
566
|
+
await request(app)
|
|
567
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
568
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
569
|
+
.send(updateBody)
|
|
570
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('should return 400 if email is already taken', async () => {
|
|
574
|
+
await insertUsers([userOne, userTwo]);
|
|
575
|
+
const updateBody = { email: userTwo.email };
|
|
576
|
+
|
|
577
|
+
await request(app)
|
|
578
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
579
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
580
|
+
.send(updateBody)
|
|
581
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test('should not return 400 if email is my email', async () => {
|
|
585
|
+
await insertUsers([userOne]);
|
|
586
|
+
const updateBody = { email: userOne.email };
|
|
587
|
+
|
|
588
|
+
await request(app)
|
|
589
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
590
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
591
|
+
.send(updateBody)
|
|
592
|
+
.expect(httpStatus.OK);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test('should return 400 if password length is less than 8 characters', async () => {
|
|
596
|
+
await insertUsers([userOne]);
|
|
597
|
+
const updateBody = { password: 'passwo1' };
|
|
598
|
+
|
|
599
|
+
await request(app)
|
|
600
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
601
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
602
|
+
.send(updateBody)
|
|
603
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test('should return 400 if password does not contain both letters and numbers', async () => {
|
|
607
|
+
await insertUsers([userOne]);
|
|
608
|
+
const updateBody = { password: 'password' };
|
|
609
|
+
|
|
610
|
+
await request(app)
|
|
611
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
612
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
613
|
+
.send(updateBody)
|
|
614
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
615
|
+
|
|
616
|
+
updateBody.password = '11111111';
|
|
617
|
+
|
|
618
|
+
await request(app)
|
|
619
|
+
.patch(`/v1/users/${userOne._id}`)
|
|
620
|
+
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
|
621
|
+
.send(updateBody)
|
|
622
|
+
.expect(httpStatus.BAD_REQUEST);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
});
|