@adaptivestone/framework 4.3.1 → 4.5.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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 4.5.0
2
+
3
+ [NEW] Now getSuper() available as a method on monsgoose models
4
+ [UPDATE] Update rate-limiter-flexible to v3
5
+ [UPDATE] Update test runner to suport ESM. In case problem with test please copy babel.config.js from framework to your project directory
6
+
7
+ ### 4.4.0
8
+
9
+ [NEW] New method to grab url of server it testing enviroument global.server.testingGetUrl("/some/endpoint")
10
+
1
11
  ### 4.3.1
2
12
 
3
13
  [UPDATE] Yup file validator update. As formidable now return all fields as an array
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
3
+ };
@@ -1,5 +1,3 @@
1
- const request = require('supertest');
2
-
3
1
  const userEmail = 'testing@test.com';
4
2
  const userPassword = 'SuperNiceSecret123$';
5
3
 
@@ -9,85 +7,136 @@ describe('auth', () => {
9
7
  describe('registration', () => {
10
8
  it('code NOT able to create user with wrong email', async () => {
11
9
  expect.assertions(1);
12
- const { status } = await request(global.server.app.httpServer.express)
13
- .post('/auth/register')
14
- .send({
15
- email: 'bad email',
16
- password: userPassword,
17
- nickName: 'test',
18
- });
10
+ const { status } = await fetch(
11
+ global.server.testingGetUrl('/auth/register'),
12
+ {
13
+ method: 'POST',
14
+ headers: {
15
+ 'Content-type': 'application/json',
16
+ },
17
+ body: JSON.stringify({
18
+ email: 'bad email',
19
+ password: userPassword,
20
+ nickName: 'test',
21
+ }),
22
+ },
23
+ ).catch(() => {});
24
+
19
25
  expect(status).toBe(400);
20
26
  });
21
27
 
22
28
  it('can create user', async () => {
23
29
  expect.assertions(1);
24
- const { status } = await request(global.server.app.httpServer.express)
25
- .post('/auth/register')
26
- .send({
27
- email: userEmail,
28
- password: userPassword,
29
- nickName: 'test',
30
- });
30
+ const { status } = await fetch(
31
+ global.server.testingGetUrl('/auth/register'),
32
+ {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-type': 'application/json',
36
+ },
37
+ body: JSON.stringify({
38
+ email: userEmail,
39
+ password: userPassword,
40
+ nickName: 'test',
41
+ }),
42
+ },
43
+ );
31
44
  expect(status).toBe(201);
32
45
  });
33
46
 
34
47
  it('can not create user with the same nickname', async () => {
35
48
  expect.assertions(1);
36
- await request(global.server.app.httpServer.express)
37
- .post('/auth/register')
38
- .send({
49
+ await fetch(global.server.testingGetUrl('/auth/register'), {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Content-type': 'application/json',
53
+ },
54
+ body: JSON.stringify({
39
55
  email: userEmail,
40
56
  password: userPassword,
41
57
  nickName: 'test',
42
- });
58
+ }),
59
+ });
60
+
61
+ const { status } = await fetch(
62
+ global.server.testingGetUrl('/auth/register'),
63
+ {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-type': 'application/json',
67
+ },
68
+ body: JSON.stringify({
69
+ email: userEmail2,
70
+ password: '123',
71
+ nickName: 'test',
72
+ }),
73
+ },
74
+ ).catch(() => {});
43
75
 
44
- const { status } = await request(global.server.app.httpServer.express)
45
- .post('/auth/register')
46
- .send({
47
- email: userEmail2,
48
- password: '123',
49
- nickName: 'test',
50
- });
51
76
  expect(status).toBe(400);
52
77
  });
53
78
 
54
79
  it('can NOT create SAME user', async () => {
55
80
  expect.assertions(1);
56
- const { status } = await request(global.server.app.httpServer.express)
57
- .post('/auth/register')
58
- .send({
59
- email: userEmail,
60
- password: userPassword,
61
- nickName: 'test',
62
- });
81
+
82
+ const { status } = await fetch(
83
+ global.server.testingGetUrl('/auth/register'),
84
+ {
85
+ method: 'POST',
86
+ headers: {
87
+ 'Content-type': 'application/json',
88
+ },
89
+ body: JSON.stringify({
90
+ email: userEmail,
91
+ password: userPassword,
92
+ nickName: 'test',
93
+ }),
94
+ },
95
+ );
96
+
63
97
  expect(status).toBe(400);
64
98
  });
65
99
  });
66
100
 
67
101
  describe('login', () => {
68
- it('can NOT login with normal creds and not Verifyed email', async () => {
102
+ it('can NOT login with normal creds and not verified email', async () => {
69
103
  expect.assertions(1);
70
- const { status } = await request(global.server.app.httpServer.express)
71
- .post('/auth/login')
72
- .send({
73
- email: userEmail,
74
- password: userPassword,
75
- });
104
+ const { status } = await fetch(
105
+ global.server.testingGetUrl('/auth/login'),
106
+ {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-type': 'application/json',
110
+ },
111
+ body: JSON.stringify({
112
+ email: userEmail,
113
+ password: userPassword,
114
+ }),
115
+ },
116
+ ).catch(() => {});
117
+
76
118
  expect(status).toBe(400);
77
119
  });
78
120
 
79
121
  it('can NOT login with WRONG creds', async () => {
80
122
  expect.assertions(1);
81
- const { status } = await request(global.server.app.httpServer.express)
82
- .post('/auth/login')
83
- .send({
84
- email: 'test@test.by',
85
- password: 'noPassword$',
86
- });
123
+ const { status } = await fetch(
124
+ global.server.testingGetUrl('/auth/login'),
125
+ {
126
+ method: 'POST',
127
+ headers: {
128
+ 'Content-type': 'application/json',
129
+ },
130
+ body: JSON.stringify({
131
+ email: 'test@test.by',
132
+ password: 'noPassword$',
133
+ }),
134
+ },
135
+ ).catch(() => {});
87
136
  expect(status).toBe(400);
88
137
  });
89
138
 
90
- it('can login with normal creds and verifyed email', async () => {
139
+ it('can login with normal creds and verified email', async () => {
91
140
  expect.assertions(2);
92
141
 
93
142
  const user = await global.server.app
@@ -96,16 +145,21 @@ describe('auth', () => {
96
145
  user.isVerified = true;
97
146
  await user.save();
98
147
 
99
- const { status, body } = await request(
100
- global.server.app.httpServer.express,
101
- )
102
- .post('/auth/login')
103
- .send({
148
+ const response = await fetch(global.server.testingGetUrl('/auth/login'), {
149
+ method: 'POST',
150
+ headers: {
151
+ 'Content-type': 'application/json',
152
+ },
153
+ body: JSON.stringify({
104
154
  email: userEmail,
105
155
  password: userPassword,
106
- });
107
- expect(status).toBe(200);
108
- expect(body.token).toBeDefined();
156
+ }),
157
+ });
158
+
159
+ const responseBody = await response.json();
160
+
161
+ expect(response.status).toBe(200);
162
+ expect(responseBody.token).toBeDefined();
109
163
  });
110
164
  });
111
165
 
@@ -126,9 +180,14 @@ describe('auth', () => {
126
180
 
127
181
  await user.save();
128
182
 
129
- const { status } = await request(
130
- global.server.app.httpServer.express,
131
- ).post(`/auth/verify?verification_token=testToken`);
183
+ const { status } = await fetch(
184
+ `${global.server.testingGetUrl(
185
+ '/auth/verify',
186
+ )}?verification_token=testToken`,
187
+ {
188
+ method: 'POST',
189
+ },
190
+ );
132
191
 
133
192
  const { isVerified } = await global.server.app.getModel('User').findOne({
134
193
  email: 'Test@gmail.com',
@@ -154,9 +213,14 @@ describe('auth', () => {
154
213
 
155
214
  await user.save();
156
215
 
157
- const { status } = await request(
158
- global.server.app.httpServer.express,
159
- ).post(`/auth/verify?verification_token=testToken123wrong`);
216
+ const { status } = await fetch(
217
+ `${global.server.testingGetUrl(
218
+ '/auth/verify',
219
+ )}?verification_token=testToken123wrong`,
220
+ {
221
+ method: 'POST',
222
+ },
223
+ );
160
224
 
161
225
  const { isVerified } = await global.server.app.getModel('User').findOne({
162
226
  email: 'Test423@gmail.com',
@@ -168,21 +232,37 @@ describe('auth', () => {
168
232
 
169
233
  it('can NOT send recovery to not exist email', async () => {
170
234
  expect.assertions(1);
171
- const { status } = await request(global.server.app.httpServer.express)
172
- .post('/auth/send-recovery-email')
173
- .send({
174
- email: 'notExists@gmail.com',
175
- });
235
+ const { status } = await fetch(
236
+ global.server.testingGetUrl('/auth/send-recovery-email'),
237
+ {
238
+ method: 'POST',
239
+ headers: {
240
+ 'Content-type': 'application/json',
241
+ },
242
+ body: JSON.stringify({
243
+ email: 'notExists@gmail.com',
244
+ }),
245
+ },
246
+ );
247
+
176
248
  expect(status).toBe(400);
177
249
  });
178
250
 
179
251
  it('can send recovery to exist email', async () => {
180
252
  expect.assertions(1);
181
- const { status } = await request(global.server.app.httpServer.express)
182
- .post('/auth/send-recovery-email')
183
- .send({
184
- email: userEmail,
185
- });
253
+ const { status } = await fetch(
254
+ global.server.testingGetUrl('/auth/send-recovery-email'),
255
+ {
256
+ method: 'POST',
257
+ headers: {
258
+ 'Content-type': 'application/json',
259
+ },
260
+ body: JSON.stringify({
261
+ email: userEmail,
262
+ }),
263
+ },
264
+ );
265
+
186
266
  expect(status).toBe(200);
187
267
  });
188
268
 
@@ -203,12 +283,19 @@ describe('auth', () => {
203
283
 
204
284
  await user.save();
205
285
 
206
- const { status } = await request(global.server.app.httpServer.express)
207
- .post('/auth/recover-password')
208
- .send({
209
- password: 'newPass',
210
- passwordRecoveryToken: 'superPassword',
211
- });
286
+ const { status } = await fetch(
287
+ global.server.testingGetUrl('/auth/recover-password'),
288
+ {
289
+ method: 'POST',
290
+ headers: {
291
+ 'Content-type': 'application/json',
292
+ },
293
+ body: JSON.stringify({
294
+ password: 'newPass',
295
+ passwordRecoveryToken: 'superPassword',
296
+ }),
297
+ },
298
+ );
212
299
 
213
300
  expect(status).toBe(200);
214
301
  });
@@ -230,94 +317,133 @@ describe('auth', () => {
230
317
 
231
318
  await user.save();
232
319
 
233
- const { status } = await request(global.server.app.httpServer.express)
234
- .post('/auth/recover-password')
235
- .send({
236
- password: 'newPass',
237
- passwordRecoveryToken: '13123',
238
- });
320
+ const { status } = await fetch(
321
+ global.server.testingGetUrl('/auth/recover-password'),
322
+ {
323
+ method: 'POST',
324
+ headers: {
325
+ 'Content-type': 'application/json',
326
+ },
327
+ body: JSON.stringify({
328
+ password: 'newPass',
329
+ passwordRecoveryToken: '13123',
330
+ }),
331
+ },
332
+ );
239
333
 
240
334
  expect(status).toBe(400);
241
335
  });
242
336
 
243
- it('can login with normal creds and NOT verifyed email is option isAuthWithVefificationFlow is set', async () => {
337
+ it('can login with normal creds and NOT verifyed email if option isAuthWithVefificationFlow is set', async () => {
244
338
  expect.assertions(4);
245
339
 
246
- const { status } = await request(global.server.app.httpServer.express)
247
- .post('/auth/register')
248
- .send({
249
- email: userEmail2,
250
- password: userPassword,
251
- });
252
-
253
- const { status: status2 } = await request(
254
- global.server.app.httpServer.express,
255
- )
256
- .post('/auth/login')
257
- .send({
258
- email: userEmail2,
259
- password: userPassword,
260
- });
340
+ const { status } = await fetch(
341
+ global.server.testingGetUrl('/auth/register'),
342
+ {
343
+ method: 'POST',
344
+ headers: {
345
+ 'Content-type': 'application/json',
346
+ },
347
+ body: JSON.stringify({
348
+ email: userEmail2,
349
+ password: userPassword,
350
+ }),
351
+ },
352
+ );
353
+
354
+ const { status: status2 } = await fetch(
355
+ global.server.testingGetUrl('/auth/login'),
356
+ {
357
+ method: 'POST',
358
+ headers: {
359
+ 'Content-type': 'application/json',
360
+ },
361
+ body: JSON.stringify({
362
+ email: userEmail2,
363
+ password: userPassword,
364
+ }),
365
+ },
366
+ );
261
367
 
262
368
  global.server.app.updateConfig('auth', {
263
369
  isAuthWithVefificationFlow: false,
264
370
  });
265
371
 
266
- const { status: status3, body } = await request(
267
- global.server.app.httpServer.express,
268
- )
269
- .post('/auth/login')
270
- .send({
271
- email: userEmail2,
272
- password: userPassword,
273
- });
372
+ const response3 = await fetch(
373
+ global.server.testingGetUrl('/auth/login'),
374
+ {
375
+ method: 'POST',
376
+ headers: {
377
+ 'Content-type': 'application/json',
378
+ },
379
+ body: JSON.stringify({
380
+ email: userEmail2,
381
+ password: userPassword,
382
+ }),
383
+ },
384
+ );
385
+
386
+ const responseBody3 = await response3.json();
274
387
 
275
388
  expect(status).toBe(201);
276
389
  expect(status2).toBe(400);
277
- expect(status3).toBe(200);
278
- expect(body.token).toBeDefined();
390
+ expect(response3.status).toBe(200);
391
+ expect(responseBody3.token).toBeDefined();
279
392
  });
280
393
  });
281
394
 
282
395
  it('can user send verification', async () => {
283
396
  expect.assertions(1);
284
397
 
285
- const { status } = await request(global.server.app.httpServer.express)
286
- .post('/auth/send-verification')
287
- .send({
288
- email: userEmail2,
289
- });
398
+ const { status } = await fetch(
399
+ global.server.testingGetUrl('/auth/send-verification'),
400
+ {
401
+ method: 'POST',
402
+ headers: {
403
+ 'Content-type': 'application/json',
404
+ },
405
+ body: JSON.stringify({
406
+ email: userEmail2,
407
+ }),
408
+ },
409
+ );
410
+
290
411
  expect(status).toBe(200);
291
412
  });
292
413
 
293
414
  it('can not user send verification to wrong email', async () => {
294
415
  expect.assertions(1);
295
416
 
296
- const { status } = await request(global.server.app.httpServer.express)
297
- .post('/auth/send-verification')
298
- .send({
299
- email: 'wrong@gmail.com',
300
- });
417
+ const { status } = await fetch(
418
+ global.server.testingGetUrl('/auth/send-verification'),
419
+ {
420
+ method: 'POST',
421
+ headers: {
422
+ 'Content-type': 'application/json',
423
+ },
424
+ body: JSON.stringify({
425
+ email: 'wrong@gmail.com',
426
+ }),
427
+ },
428
+ );
429
+
301
430
  expect(status).toBe(400);
302
431
  });
303
432
 
304
433
  describe('rate limiter', () => {
305
- it('we should receive 429 on rate limit exceeded', async () => {
434
+ it('should receive 429 on rate limit exceeded', async () => {
306
435
  expect.assertions(1);
307
- const resultsPromise = [];
308
436
 
309
- for (let i = 0; i < 11; i += 1) {
310
- resultsPromise.push(
311
- request(global.server.app.httpServer.express)
312
- .post('/auth/logout')
313
- .send({}),
314
- );
315
- }
437
+ const requests = Array.from({ length: 11 }, () =>
438
+ fetch(global.server.testingGetUrl('/auth/logout'), {
439
+ method: 'POST',
440
+ }),
441
+ );
316
442
 
317
- const results = await Promise.all(resultsPromise);
318
- const statuses = results.map((res) => res.status);
443
+ const responses = await Promise.all(requests);
444
+ const statusCodes = responses.map((response) => response.status);
319
445
 
320
- expect(statuses.indexOf(429)).not.toBe(-1);
446
+ expect(statusCodes).toContain(429);
321
447
  });
322
448
  });
323
449
  });
@@ -1,11 +1,10 @@
1
- const request = require('supertest');
2
-
3
1
  describe('home', () => {
4
2
  it('can open home have', async () => {
5
3
  expect.assertions(1);
6
- const { status } = await request(global.server.app.httpServer.express).get(
7
- '/',
4
+ const { status } = await fetch(global.server.testingGetUrl('/')).catch(
5
+ () => {},
8
6
  );
7
+
9
8
  expect(status).toBe(200);
10
9
  });
11
10
  });
@@ -28,26 +28,29 @@ class ControllerManager extends Base {
28
28
  }
29
29
  return 0;
30
30
  });
31
- // const controllers = [];
31
+ const controllers = [];
32
32
  for (const controller of controllersToLoad) {
33
33
  // TODO wait until https://github.com/nodejs/node/issues/35889
34
- // controllers.push(
35
- // import(controller.path).then(({ default: ControllerModule }) => {
36
- // eslint-disable-next-line import/no-dynamic-require, global-require
37
- const ControllerModule = require(controller.path);
38
- const contollerName = ControllerModule.name.toLowerCase();
39
- let prefix = path.dirname(controller.file);
40
- if (prefix === '.') {
41
- prefix = '';
42
- }
43
- const controllePath = prefix
44
- ? `${prefix}/${contollerName}`
45
- : contollerName;
46
- this.controllers[controllePath] = new ControllerModule(this.app, prefix);
47
- // }),
48
- // );
34
+ controllers.push(
35
+ import(controller.path).then(({ default: ControllerModule }) => {
36
+ // eslint-disable-next-line import/no-dynamic-require, global-require
37
+ // const ControllerModule = require(controller.path);
38
+ const contollerName = ControllerModule.name.toLowerCase();
39
+ let prefix = path.dirname(controller.file);
40
+ if (prefix === '.') {
41
+ prefix = '';
42
+ }
43
+ const controllePath = prefix
44
+ ? `${prefix}/${contollerName}`
45
+ : contollerName;
46
+ this.controllers[controllePath] = new ControllerModule(
47
+ this.app,
48
+ prefix,
49
+ );
50
+ }),
51
+ );
49
52
  }
50
- // await Promise.all(controllers);
53
+ await Promise.all(controllers);
51
54
  }
52
55
 
53
56
  static get loggerGroup() {
package/models/User.js CHANGED
@@ -88,8 +88,8 @@ class User extends AbstractModel {
88
88
  const scryptAsync = promisify(scrypt);
89
89
  const data = await scryptAsync(
90
90
  this.email + Date.now(),
91
- this.constructor.getSuper().saltSecret,
92
- this.constructor.getSuper().hashRounds,
91
+ this.getSuper().saltSecret,
92
+ this.getSuper().hashRounds,
93
93
  );
94
94
  const token = data.toString('base64url');
95
95
  this.sessionTokens.push({ token, valid: timestamp });
@@ -138,8 +138,8 @@ class User extends AbstractModel {
138
138
  const scryptAsync = promisify(scrypt);
139
139
  const data = await scryptAsync(
140
140
  userMongoose.email + Date.now(),
141
- userMongoose.constructor.getSuper().saltSecret,
142
- userMongoose.constructor.getSuper().hashRounds,
141
+ userMongoose.getSuper().saltSecret,
142
+ userMongoose.getSuper().hashRounds,
143
143
  );
144
144
  const token = data.toString('base64url');
145
145
  // if (err) {
@@ -177,7 +177,7 @@ class User extends AbstractModel {
177
177
  const passwordRecoveryToken =
178
178
  await User.generateUserPasswordRecoveryToken(this);
179
179
  const mail = new Mailer(
180
- this.constructor.getSuper().app,
180
+ this.getSuper().app,
181
181
  'recovery',
182
182
  {
183
183
  link: `${i18n.language}/auth/recovery?password_recovery_token=${passwordRecoveryToken.token}`,
@@ -194,8 +194,8 @@ class User extends AbstractModel {
194
194
  const scryptAsync = promisify(scrypt);
195
195
  const data = await scryptAsync(
196
196
  userMongoose.email + Date.now(),
197
- userMongoose.constructor.getSuper().saltSecret,
198
- userMongoose.constructor.getSuper().hashRounds,
197
+ userMongoose.getSuper().saltSecret,
198
+ userMongoose.getSuper().hashRounds,
199
199
  );
200
200
  const token = data.toString('base64url');
201
201
  // if (err) {
@@ -243,7 +243,7 @@ class User extends AbstractModel {
243
243
  async sendVerificationEmail(i18n) {
244
244
  const verificationToken = await User.generateUserVerificationToken(this);
245
245
  const mail = new Mailer(
246
- this.constructor.getSuper().app,
246
+ this.getSuper().app,
247
247
  'verification',
248
248
  {
249
249
  link: `${i18n.language}/auth/login?verification_token=${verificationToken.token}`,
@@ -14,6 +14,7 @@ class AbstractModel extends Base {
14
14
  this.mongooseSchema.set('minimize', false);
15
15
  this.mongooseSchema.loadClass(this.constructor);
16
16
  this.mongooseSchema.statics.getSuper = () => this;
17
+ this.mongooseSchema.methods.getSuper = () => this;
17
18
  this.initHooks();
18
19
  this.mongooseModel = mongoose.model(
19
20
  this.constructor.name,
@@ -48,10 +48,10 @@ class Cli extends Base {
48
48
  return false;
49
49
  }
50
50
  // TODO wait until https://github.com/nodejs/node/issues/35889
51
- // const { default: Command } = await import(this.commands[command]);
51
+ const { default: Command } = await import(this.commands[command]);
52
52
 
53
53
  // eslint-disable-next-line import/no-dynamic-require, global-require
54
- const Command = require(this.commands[command]);
54
+ // const Command = require(this.commands[command]);
55
55
 
56
56
  const c = new Command(this.app, this.commands, args);
57
57
  let result = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptivestone/framework",
3
- "version": "4.3.1",
3
+ "version": "4.5.0",
4
4
  "description": "Adaptive stone node js framework",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -22,7 +22,8 @@
22
22
  "prepare": "husky install",
23
23
  "cli": "node cliCommand",
24
24
  "benchmark": "h2load -n 10000 -c 50 -p 'http/1.1' http://localhost:3300/",
25
- "benchmark2": "h2load -n 10000 -c 50 https://localhost:3300/"
25
+ "benchmark2": "h2load -n 10000 -c 50 https://localhost:3300/",
26
+ "redis:docker": "docker run --rm -p 6379:6379 redis"
26
27
  },
27
28
  "jest": {
28
29
  "setupFilesAfterEnv": [
@@ -55,13 +56,14 @@
55
56
  "nodemailer-sendmail-transport": "^1.0.2",
56
57
  "nodemailer-stub-transport": "^1.1.0",
57
58
  "pug": "^3.0.2",
58
- "rate-limiter-flexible": "^2.2.4",
59
+ "rate-limiter-flexible": "^3.0.0",
59
60
  "redis": "^4.3.1",
60
61
  "winston": "^3.3.3",
61
62
  "winston-transport-sentry-node": "^2.0.0",
62
63
  "yup": "^1.0.0"
63
64
  },
64
65
  "devDependencies": {
66
+ "@babel/preset-env": "^7.22.15",
65
67
  "eslint": "^8.0.0",
66
68
  "eslint-config-airbnb-base": "^15.0.0",
67
69
  "eslint-config-prettier": "^9.0.0",
@@ -71,8 +73,7 @@
71
73
  "lint-staged": "^14.0.0",
72
74
  "mongodb-memory-server": "^8.0.2",
73
75
  "nodemon": "^3.0.1",
74
- "prettier": "^3.0.0",
75
- "supertest": "^6.1.4"
76
+ "prettier": "^3.0.0"
76
77
  },
77
78
  "lint-staged": {
78
79
  "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
package/server.js CHANGED
@@ -52,17 +52,17 @@ class Server {
52
52
  */
53
53
  async startServer(callbackBefore404 = async () => Promise.resolve()) {
54
54
  // eslint-disable-next-line global-require
55
- const HttpServer = require('./services/http/HttpServer');
55
+ // const HttpServer = require('./services/http/HttpServer');
56
56
  // eslint-disable-next-line global-require
57
- const ControllerManager = require('./controllers/index');
57
+ // const ControllerManager = require('./controllers/index');
58
58
  // TODO wait until https://github.com/nodejs/node/issues/35889
59
- // const [{ default: HttpServer }, { default: ControllerManager }] =
60
- // await Promise.all([
61
- // // eslint-disable-next-line import/extensions
62
- // import('./services/http/HttpServer.js'), // Speed optimisation
63
- // // eslint-disable-next-line import/extensions
64
- // import('./controllers/index.js'), // Speed optimisation
65
- // ]);
59
+ const [{ default: HttpServer }, { default: ControllerManager }] =
60
+ await Promise.all([
61
+ // eslint-disable-next-line import/extensions
62
+ import('./services/http/HttpServer.js'), // Speed optimisation
63
+ // eslint-disable-next-line import/extensions
64
+ import('./controllers/index.js'), // Speed optimisation
65
+ ]);
66
66
 
67
67
  this.addErrorHandling();
68
68
 
@@ -16,10 +16,9 @@ class RateLimiter extends AbstractMiddleware {
16
16
 
17
17
  constructor(app, params) {
18
18
  super(app, params);
19
- const routeParams = params;
20
19
  const limiterOptions = this.app.getConfig('rateLimiter');
21
20
 
22
- this.finalOptions = merge(limiterOptions, routeParams);
21
+ this.finalOptions = merge(limiterOptions, params);
23
22
  this.limiter = null;
24
23
 
25
24
  switch (this.finalOptions.driver) {
@@ -32,7 +31,7 @@ class RateLimiter extends AbstractMiddleware {
32
31
  break;
33
32
 
34
33
  case 'mongo':
35
- this.limiter = RateLimiterMongo({
34
+ this.limiter = new RateLimiterMongo({
36
35
  storeClient: mongoose.connection,
37
36
  ...this.finalOptions.limiterOptions,
38
37
  });
@@ -50,7 +49,6 @@ class RateLimiter extends AbstractMiddleware {
50
49
  const redisConfig = this.app.getConfig('redis');
51
50
  const redisClient = redis.createClient({
52
51
  url: redisConfig.url,
53
- legacyMode: true,
54
52
  });
55
53
 
56
54
  // TODO: change it
@@ -71,6 +69,7 @@ class RateLimiter extends AbstractMiddleware {
71
69
 
72
70
  return new RateLimiterRedis({
73
71
  storeClient: redisClient,
72
+ useRedisPackage: true,
74
73
  ...this.finalOptions.limiterOptions,
75
74
  });
76
75
  }
@@ -91,7 +90,7 @@ class RateLimiter extends AbstractMiddleware {
91
90
 
92
91
  if (request && request.length) {
93
92
  request.forEach((val) => {
94
- if (req.body[val]) {
93
+ if (req.body && req.body[val]) {
95
94
  key.push(req.body[val]);
96
95
  }
97
96
  // if (req.appInfo.request && req.appInfo.request[val]) {
@@ -108,6 +107,7 @@ class RateLimiter extends AbstractMiddleware {
108
107
  this.logger.info(
109
108
  `RateLimmiter not inited correclty! Please check init logs `,
110
109
  );
110
+ return res.status(500).send('');
111
111
  }
112
112
 
113
113
  const { namespace } = this.app.getConfig('redis');
@@ -119,7 +119,6 @@ class RateLimiter extends AbstractMiddleware {
119
119
  .catch(() => {
120
120
  this.logger.warn(`Too many requests. Consume key: ${consumeKey}`);
121
121
  });
122
-
123
122
  if (consumeResult) {
124
123
  return next();
125
124
  }
@@ -1,6 +1,22 @@
1
+ const { setTimeout } = require('node:timers/promises');
1
2
  const RateLimiter = require('./RateLimiter');
2
3
 
4
+ let mongoRateLimiter;
5
+
3
6
  describe('rate limiter methods', () => {
7
+ beforeAll(() => {
8
+ mongoRateLimiter = new RateLimiter(global.server.app, {
9
+ driver: 'mongo',
10
+ limiterOptions: {
11
+ keyPrefix: `mongo_${Date.now()}`,
12
+ },
13
+ });
14
+ });
15
+
16
+ afterAll(async () => {
17
+ // we need to wait because redis mongo ask mongo to create indexes
18
+ await setTimeout(200);
19
+ });
4
20
  it('have description fields', async () => {
5
21
  expect.assertions(1);
6
22
  const middleware = new RateLimiter(global.server.app, {
@@ -47,4 +63,167 @@ describe('rate limiter methods', () => {
47
63
 
48
64
  expect(res).toBe('192.168.0.0__someId');
49
65
  });
66
+
67
+ it('generateConsumeKey with request works correctly', async () => {
68
+ expect.assertions(1);
69
+
70
+ const redisRateLimiter = new RateLimiter(global.server.app, {
71
+ driver: 'redis',
72
+ consumeKeyComponents: {
73
+ request: ['email'],
74
+ },
75
+ });
76
+
77
+ const res = await redisRateLimiter.gerenateConsumeKey({
78
+ ip: '192.168.0.0',
79
+ body: {
80
+ email: 'foo@example.com',
81
+ },
82
+ });
83
+
84
+ expect(res).toBe('192.168.0.0__foo@example.com');
85
+ });
86
+
87
+ it('middleware without driver should fail', async () => {
88
+ expect.assertions(2);
89
+ const rateLimiter = new RateLimiter(global.server.app, {
90
+ driver: 'unknown',
91
+ });
92
+ const nextFunction = jest.fn(() => {});
93
+ const req = {
94
+ appInfo: {},
95
+ };
96
+ let status;
97
+ let isSend;
98
+ await rateLimiter.middleware(
99
+ req,
100
+ {
101
+ status(statusCode) {
102
+ status = statusCode;
103
+ return this;
104
+ },
105
+ send() {
106
+ isSend = true;
107
+ },
108
+ },
109
+ nextFunction,
110
+ );
111
+ expect(status).toBe(500);
112
+ expect(isSend).toBe(true);
113
+ });
114
+
115
+ const makeOneRequest = async ({ rateLimiter, driver, request }) => {
116
+ let realRateLimiter = rateLimiter;
117
+ if (!realRateLimiter) {
118
+ realRateLimiter = new RateLimiter(global.server.app, {
119
+ driver,
120
+ });
121
+ }
122
+ const req = {
123
+ appInfo: {},
124
+ ...request,
125
+ };
126
+ let status;
127
+ let isSend = false;
128
+ let isNextCalled = false;
129
+ await realRateLimiter.middleware(
130
+ req,
131
+ {
132
+ status(statusCode) {
133
+ status = statusCode;
134
+ return this;
135
+ },
136
+ send() {
137
+ isSend = true;
138
+ },
139
+ },
140
+ () => {
141
+ isNextCalled = true;
142
+ },
143
+ );
144
+ return { status, isSend, isNextCalled };
145
+ };
146
+
147
+ it('middleware should works with a mongo drivers', async () => {
148
+ expect.assertions(1);
149
+ const { isNextCalled } = await makeOneRequest({
150
+ rateLimiter: mongoRateLimiter,
151
+ request: { ip: '10.10.0.1' },
152
+ });
153
+ expect(isNextCalled).toBe(true);
154
+ });
155
+
156
+ it('middleware should works with a memory drivers', async () => {
157
+ expect.assertions(1);
158
+ const { isNextCalled } = await makeOneRequest({
159
+ driver: 'memory',
160
+ request: { ip: '10.10.0.1' },
161
+ });
162
+ expect(isNextCalled).toBe(true);
163
+ });
164
+
165
+ it('middleware should works with a redis drivers', async () => {
166
+ expect.assertions(1);
167
+ const { isNextCalled } = await makeOneRequest({
168
+ driver: 'redis',
169
+ request: { ip: '10.10.0.1' },
170
+ });
171
+ expect(isNextCalled).toBe(true);
172
+ });
173
+
174
+ it('middleware should rate limits for us. mongo driver', async () => {
175
+ expect.assertions(2);
176
+
177
+ const middlewares = Array.from({ length: 20 }, () =>
178
+ makeOneRequest({ rateLimiter: mongoRateLimiter }),
179
+ );
180
+
181
+ const data = await Promise.all(middlewares);
182
+
183
+ const status = data.find((obj) => obj.status === 429);
184
+ const isSend = data.find((obj) => obj.isSend);
185
+
186
+ expect(status.status).toBe(429);
187
+ expect(isSend.isSend).toBe(true);
188
+ });
189
+
190
+ it('middleware should rate limits for us. memory driver', async () => {
191
+ expect.assertions(2);
192
+
193
+ const rateLimiter = new RateLimiter(global.server.app, {
194
+ driver: 'memory',
195
+ });
196
+
197
+ const middlewares = Array.from({ length: 20 }, () =>
198
+ makeOneRequest({ rateLimiter }),
199
+ );
200
+
201
+ const data = await Promise.all(middlewares);
202
+
203
+ const status = data.find((obj) => obj.status === 429);
204
+ const isSend = data.find((obj) => obj.isSend);
205
+
206
+ expect(status.status).toBe(429);
207
+ expect(isSend.isSend).toBe(true);
208
+ });
209
+
210
+ it('middleware should rate limits for us. redis driver', async () => {
211
+ expect.assertions(2);
212
+
213
+ const rateLimiter = new RateLimiter(global.server.app, {
214
+ driver: 'redis',
215
+ });
216
+
217
+ const middlewares = Array.from({ length: 20 }, () =>
218
+ makeOneRequest({ rateLimiter }),
219
+ );
220
+
221
+ const data = await Promise.all(middlewares);
222
+
223
+ const status = data.find((obj) => obj.status === 429);
224
+ const isSend = data.find((obj) => obj.isSend);
225
+
226
+ expect(status.status).toBe(429);
227
+ expect(isSend.isSend).toBe(true);
228
+ });
50
229
  });
package/tests/setup.js CHANGED
@@ -14,7 +14,7 @@ jest.setTimeout(1000000);
14
14
  beforeAll(async () => {
15
15
  mongoMemoryServerInstance = await MongoMemoryReplSet.create({
16
16
  // binary: { version: '4.4.6' },
17
- replSet: { storageEngine: 'wiredTiger' },
17
+ replSet: { count: 1, storageEngine: 'wiredTiger' },
18
18
  });
19
19
  await mongoMemoryServerInstance.waitUntilRunning();
20
20
  process.env.LOGGER_CONSOLE_LEVEL = 'error';
@@ -46,6 +46,8 @@ beforeAll(async () => {
46
46
  if (!global.testSetup) {
47
47
  global.testSetup = {};
48
48
  }
49
+ global.server.testingGetUrl = (urlPart) =>
50
+ `http://127.0.0.1:${global.server.getConfig('http').port}${urlPart}`;
49
51
  if (!global.testSetup.disableUserCreate) {
50
52
  const User = global.server.app.getModel('User');
51
53
  global.user = await User.create({