@adaptivestone/framework 3.4.2 → 4.0.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENCE +21 -0
  3. package/cluster.js +3 -3
  4. package/commands/CreateUser.js +27 -0
  5. package/commands/Documentation.js +1 -1
  6. package/commands/GetOpenApiJson.js +53 -23
  7. package/commands/migration/Create.js +2 -2
  8. package/config/auth.js +1 -1
  9. package/config/i18n.js +4 -3
  10. package/config/mail.js +5 -1
  11. package/controllers/Home.js +2 -2
  12. package/controllers/Home.test.js +11 -0
  13. package/controllers/index.js +15 -15
  14. package/folderConfig.js +1 -1
  15. package/helpers/yup.js +24 -0
  16. package/index.js +8 -0
  17. package/models/User.js +38 -27
  18. package/models/User.test.js +68 -18
  19. package/modules/AbstractController.js +144 -208
  20. package/modules/AbstractModel.js +2 -1
  21. package/modules/Base.js +3 -2
  22. package/modules/BaseCli.js +6 -2
  23. package/package.json +18 -14
  24. package/server.d.ts +1 -1
  25. package/server.js +25 -8
  26. package/services/cache/Cache.d.ts +3 -3
  27. package/services/cache/Cache.js +17 -3
  28. package/services/documentation/DocumentationGenerator.js +171 -0
  29. package/services/http/HttpServer.js +16 -96
  30. package/services/http/middleware/AbstractMiddleware.js +20 -0
  31. package/services/http/middleware/GetUserByToken.js +4 -0
  32. package/services/http/middleware/I18n.js +119 -0
  33. package/services/http/middleware/I18n.test.js +77 -0
  34. package/services/http/middleware/Pagination.js +56 -0
  35. package/services/http/middleware/PrepareAppInfo.test.js +22 -0
  36. package/services/http/middleware/{Middlewares.test.js → RateLimiter.test.js} +1 -1
  37. package/services/http/middleware/RequestLogger.js +22 -0
  38. package/services/http/middleware/RequestParser.js +36 -0
  39. package/services/messaging/email/index.js +141 -41
  40. package/services/messaging/email/resources/.gitkeep +1 -0
  41. package/services/validate/ValidateService.js +161 -0
  42. package/services/validate/ValidateService.test.js +105 -0
  43. package/services/validate/drivers/AbstractValidator.js +37 -0
  44. package/services/validate/drivers/CustomValidator.js +52 -0
  45. package/services/validate/drivers/YupValidator.js +103 -0
  46. package/tests/setup.js +13 -5
  47. package/services/messaging/email/templates/emptyTemplate/style.less +0 -0
  48. package/services/messaging/email/templates/password/html.handlebars +0 -13
  49. package/services/messaging/email/templates/password/style.less +0 -0
  50. package/services/messaging/email/templates/password/subject.handlebars +0 -1
  51. package/services/messaging/email/templates/password/text.handlebars +0 -1
  52. package/services/messaging/email/templates/verification/style.less +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ### 4.0.0 - DEV
2
+
3
+ [BREAKING] change bcrypt encryption with scrypt
4
+ [BREAKING] change internal express parser to formidable parser. Affect you if external formidable is used
5
+ [BREAKING] should not affect any user. Changed email-templates module to internal implementation. Idea to keep dependensy list smaller
6
+ [BREAKING] change i18n middleware to internal one. Nothing should be affected
7
+ [BREAKING] now validation of request splitted between request and query
8
+ [BREAKING] supportedLngs option added to i18n config
9
+ [BREAKING] email inliner now looking for src/services/messaging/email/resources folder instead of 'build' folder.
10
+
11
+ [BREAKING] Mongoose v7. https://mongoosejs.com/docs/migrating_to_7.html
12
+ [BREAKING] Yup validation was updated to v1 https://github.com/jquense/yup/issues/1906
13
+
14
+ [DEPRECATED] getExpress path is deprecated. Renamed to getHttpPath
15
+
16
+ [NEW] pagination middleware
17
+ [NEW] requestLogger middleware. Migrated from core server to be an middleware
18
+ [NEW] CreateUser command
19
+ [NEW] custom yup validator for validate File requests
20
+ [UPDATE] updated deps
21
+ [UPDATE] openApi generator support files
22
+ [UPDATE] updated 18n middleware. Introduced internal cachce. Speed up of request processing up to 100%
23
+ [UPDATE] cache drivers to JSON support BigInt numbers
24
+
25
+ ### 3.4.3
26
+
27
+ [UPDATE] updated deps
28
+ [FIX] fix tests for redis
29
+ [FIX] support in tests TEST_FOLDER_EMAILS
30
+
1
31
  ### 3.4.2
2
32
 
3
33
  [UPDATE] updated deps
package/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016-2023 Andrei Lahunou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/cluster.js CHANGED
@@ -1,5 +1,5 @@
1
- const cluster = require('cluster');
2
- const numCPUs = require('os').cpus().length;
1
+ const cluster = require('node:cluster');
2
+ const numCPUs = require('node:os').cpus().length;
3
3
 
4
4
  if (cluster.isMaster) {
5
5
  // eslint-disable-next-line no-console
@@ -22,5 +22,5 @@ if (cluster.isMaster) {
22
22
  });
23
23
  } else {
24
24
  // eslint-disable-next-line global-require
25
- require('./server');
25
+ require('./index');
26
26
  }
@@ -0,0 +1,27 @@
1
+ const AbstractCommand = require('../modules/AbstractCommand');
2
+
3
+ // Example: node src/cli createuser --email=somemail@gmail.com --password=somePassword --roles=user,admin,someOtherRoles
4
+ class CreateUser extends AbstractCommand {
5
+ async run() {
6
+ const User = this.app.getModel('User');
7
+ const { email, password, roles } = this.args;
8
+
9
+ if (!email || !password) {
10
+ this.logger.error('Input validation failded');
11
+ this.logger.error('Please add "email" and "password" variables');
12
+ return false;
13
+ }
14
+ const user = await User.create({
15
+ email,
16
+ password,
17
+ roles: roles?.split(','),
18
+ });
19
+ await user.generateToken();
20
+
21
+ this.logger.info(`User was created ${JSON.stringify(user, 0, 4)}`);
22
+
23
+ return user;
24
+ }
25
+ }
26
+
27
+ module.exports = CreateUser;
@@ -5,7 +5,7 @@ class Documentation extends AbstractCommand {
5
5
  async run() {
6
6
  const CM = new ControllerManager(this.app);
7
7
  this.app.documentation = [];
8
- await CM.initControllers({ folders: this.app.foldersConfig });
8
+ await CM.initControllers();
9
9
  return this.app.documentation;
10
10
  }
11
11
  }
@@ -1,4 +1,4 @@
1
- const fs = require('fs').promises;
1
+ const fs = require('node:fs').promises;
2
2
  const AbstractCommand = require('../modules/AbstractCommand');
3
3
 
4
4
  /**
@@ -165,6 +165,7 @@ class GetOpenApiJson extends AbstractCommand {
165
165
  const routeTitle = route[Object.keys(route)[0]].name;
166
166
 
167
167
  const routeFields = route[Object.keys(route)[0]].fields;
168
+ const routeQueryFields = route[Object.keys(route)[0]].queryFields;
168
169
 
169
170
  if (!openApi.paths[routeName][methodName]) {
170
171
  openApi.paths[routeName][methodName] = {};
@@ -202,6 +203,17 @@ class GetOpenApiJson extends AbstractCommand {
202
203
  },
203
204
  };
204
205
 
206
+ for (const queryField of routeQueryFields) {
207
+ openApi.paths[routeName][methodName].parameters.push({
208
+ name: queryField.name,
209
+ in: 'query',
210
+ required: queryField?.required,
211
+ schema: {
212
+ type: queryField.type,
213
+ },
214
+ });
215
+ }
216
+
205
217
  for (const routeField of routeParameters) {
206
218
  openApi.paths[routeName][methodName].parameters.push({
207
219
  name: routeField,
@@ -216,34 +228,36 @@ class GetOpenApiJson extends AbstractCommand {
216
228
  if (routeFields.length) {
217
229
  const groupBodyFields = {};
218
230
  const requiredFields = [];
231
+ let isMultipartFormaData = false;
219
232
  for (const field of routeFields) {
220
- if (field.isRequired) {
233
+ if (field.required) {
221
234
  requiredFields.push(field.name);
222
235
  }
223
236
 
224
- if (field.type === 'object') {
225
- groupBodyFields[field.name] = {};
226
- const objectFields = {};
227
- for (const objField of field.fields) {
228
- objectFields[objField.name] = {
229
- // fields file has mixed type but openApi doesnt have this type
230
- type: objField.type === 'mixed' ? 'string' : objField.type,
237
+ switch (field.type) {
238
+ case 'object':
239
+ groupBodyFields[field.name] = {
240
+ properties: {},
231
241
  };
232
- }
233
242
 
234
- groupBodyFields[field.name].properties = objectFields;
235
- } else {
236
- groupBodyFields[field.name] = {
237
- type: field.type,
238
- };
243
+ for (const objField of field.fields) {
244
+ groupBodyFields[field.name].properties[objField.name] = {
245
+ // fields file has mixed type but openApi doesnt have this type
246
+ type: objField.type === 'mixed' ? 'string' : objField.type,
247
+ };
248
+ }
249
+
250
+ break;
239
251
 
240
- if (field.type === 'array') {
241
- groupBodyFields[field.name].items = {
242
- type: field.innerType,
252
+ case 'array':
253
+ groupBodyFields[field.name] = {
254
+ items: {
255
+ type: field.innerType,
256
+ },
243
257
  };
244
- }
258
+ break;
245
259
 
246
- if (field.type === 'lazy') {
260
+ case 'lazy':
247
261
  groupBodyFields[field.name] = {
248
262
  oneOf: [
249
263
  {
@@ -254,13 +268,29 @@ class GetOpenApiJson extends AbstractCommand {
254
268
  },
255
269
  ],
256
270
  };
257
- }
271
+ break;
272
+
273
+ case 'file':
274
+ groupBodyFields[field.name] = {
275
+ type: 'string',
276
+ format: 'binary',
277
+ };
278
+ isMultipartFormaData = true;
279
+ break;
280
+ default:
281
+ groupBodyFields[field.name] = {
282
+ type: field.type,
283
+ };
258
284
  }
259
285
  }
260
286
 
287
+ const contentType = isMultipartFormaData
288
+ ? 'multipart/form-data'
289
+ : 'application/json';
290
+
261
291
  openApi.paths[routeName][methodName].requestBody = {
262
292
  content: {
263
- 'application/json': {
293
+ [contentType]: {
264
294
  schema: {
265
295
  type: 'object',
266
296
  properties: groupBodyFields,
@@ -271,7 +301,7 @@ class GetOpenApiJson extends AbstractCommand {
271
301
 
272
302
  if (requiredFields.length) {
273
303
  openApi.paths[routeName][methodName].requestBody.content[
274
- 'application/json'
304
+ contentType
275
305
  ].schema.required = requiredFields;
276
306
  }
277
307
  }
@@ -1,5 +1,5 @@
1
- const path = require('path');
2
- const fs = require('fs').promises;
1
+ const path = require('node:path');
2
+ const fs = require('node:fs').promises;
3
3
 
4
4
  const AbstractCommand = require('../../modules/AbstractCommand');
5
5
 
package/config/auth.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- saltRounds: 10,
2
+ hashRounds: 64,
3
3
  saltSecret: process.env.AUTH_SALT || 'gdfg45667_%%^trterte',
4
4
  isAuthWithVefificationFlow: true,
5
5
  };
package/config/i18n.js CHANGED
@@ -1,11 +1,12 @@
1
+ /**
2
+ * You can use any options from https://www.i18next.com/overview/configuration-options
3
+ */
1
4
  module.exports = {
2
5
  enabled: true,
3
6
  preload: ['en', 'ru'],
7
+ supportedLngs: ['en', 'ru'], // should be at least one supported
4
8
  fallbackLng: 'en',
5
9
  saveMissing: false,
6
10
  debug: false,
7
- // https://github.com/i18next/i18next-http-middleware#detector-options
8
- // in additional we have 'xLang' that detect language based on header 'xLang'
9
- langDetectionOders: ['xLang'], // from order option
10
11
  lookupQuerystring: 'lng', // string to detect language on query
11
12
  };
package/config/mail.js CHANGED
@@ -1,3 +1,5 @@
1
+ const path = require('node:path');
2
+
1
3
  module.exports = {
2
4
  from: 'Localhost <info@localhost>',
3
5
  transports: {
@@ -20,6 +22,8 @@ module.exports = {
20
22
  transport: process.env.EMAIL_TRANSPORT || 'smtp',
21
23
  webResources: {
22
24
  // https://github.com/jrit/web-resource-inliner path to find resources
23
- relativeTo: 'build',
25
+ relativeTo: path.resolve('src/services/messaging/email/resources'),
26
+ images: false,
24
27
  },
28
+ globalVariablesToTemplates: {},
25
29
  };
@@ -12,11 +12,11 @@ class Home extends AbstractController {
12
12
 
13
13
  // eslint-disable-next-line class-methods-use-this
14
14
  async home(req, res) {
15
- res.render('home');
15
+ return res.json({ message: 'Home' });
16
16
  }
17
17
 
18
18
  // eslint-disable-next-line class-methods-use-this
19
- getExpressPath() {
19
+ getHttpPath() {
20
20
  return '/';
21
21
  }
22
22
 
@@ -0,0 +1,11 @@
1
+ const request = require('supertest');
2
+
3
+ describe('home', () => {
4
+ it('can open home have', async () => {
5
+ expect.assertions(1);
6
+ const { status } = await request(global.server.app.httpServer.express).get(
7
+ '/',
8
+ );
9
+ expect(status).toBe(200);
10
+ });
11
+ });
@@ -1,25 +1,22 @@
1
- const path = require('path');
1
+ const path = require('node:path');
2
2
  const Base = require('../modules/Base');
3
3
 
4
4
  /**
5
- * Class do autoloading a http comntrollers
5
+ * Class do autoloading a http controllers
6
6
  */
7
7
  class ControllerManager extends Base {
8
8
  constructor(app) {
9
9
  super(app);
10
- this.app.controllers = {};
10
+ this.controllers = {};
11
11
  }
12
12
 
13
13
  /**
14
14
  * Load controllers
15
- * @param {object} folderConfig
16
- * @param {object} folderConfig.folders server folder config
17
- * @param {string} folderConfig.controllers controller folder path
18
15
  */
19
- async initControllers(folderConfig) {
16
+ async initControllers() {
20
17
  const controllersToLoad = await this.getFilesPathWithInheritance(
21
18
  __dirname,
22
- folderConfig.folders.controllers,
19
+ this.app.foldersConfig.controllers,
23
20
  );
24
21
 
25
22
  controllersToLoad.sort((a, b) => {
@@ -31,9 +28,12 @@ class ControllerManager extends Base {
31
28
  }
32
29
  return 0;
33
30
  });
34
-
31
+ // const controllers = [];
35
32
  for (const controller of controllersToLoad) {
36
- // eslint-disable-next-line global-require, import/no-dynamic-require
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
37
  const ControllerModule = require(controller.path);
38
38
  const contollerName = ControllerModule.name.toLowerCase();
39
39
  let prefix = path.dirname(controller.file);
@@ -41,13 +41,13 @@ class ControllerManager extends Base {
41
41
  prefix = '';
42
42
  }
43
43
  const controllePath = prefix
44
- ? `${contollerName}/${contollerName}`
44
+ ? `${prefix}/${contollerName}`
45
45
  : contollerName;
46
- this.app.controllers[controllePath] = new ControllerModule(
47
- this.app,
48
- prefix,
49
- );
46
+ this.controllers[controllePath] = new ControllerModule(this.app, prefix);
47
+ // }),
48
+ // );
50
49
  }
50
+ // await Promise.all(controllers);
51
51
  }
52
52
 
53
53
  static get loggerGroup() {
package/folderConfig.js CHANGED
@@ -1,4 +1,4 @@
1
- const path = require('path');
1
+ const path = require('node:path');
2
2
 
3
3
  module.exports = {
4
4
  folders: {
package/helpers/yup.js ADDED
@@ -0,0 +1,24 @@
1
+ const { Schema } = require('yup');
2
+ const formidable = require('formidable');
3
+
4
+ /**
5
+ * Validator for file
6
+ * use as
7
+ * @example
8
+ * request: yup.object().shape({
9
+ * someFile: new YupFile().required(),
10
+ * })
11
+ */
12
+ class YupFile extends Schema {
13
+ constructor() {
14
+ super({
15
+ type: 'file',
16
+ check: (value) => value instanceof formidable.PersistentFile,
17
+ });
18
+ }
19
+ }
20
+
21
+ module.exports = {
22
+ // eslint-disable-next-line import/prefer-default-export
23
+ YupFile,
24
+ };
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ const Server = require('./server');
2
+ const folderConfig = require('./folderConfig');
3
+
4
+ const server = new Server(folderConfig);
5
+
6
+ server.startServer().then(() => {
7
+ console.log(server.app.controllerManager.controllers);
8
+ });
package/models/User.js CHANGED
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable no-param-reassign */
2
- const bcrypt = require('bcrypt');
2
+
3
+ const { scrypt } = require('node:crypto');
4
+ const { promisify } = require('node:util');
3
5
 
4
6
  const AbstractModel = require('../modules/AbstractModel');
5
7
 
@@ -9,12 +11,12 @@ class User extends AbstractModel {
9
11
  constructor(app) {
10
12
  super(app);
11
13
  const authConfig = this.app.getConfig('auth');
12
- this.saltRounds = authConfig.saltRounds;
14
+ this.hashRounds = authConfig.hashRounds;
13
15
  this.saltSecret = authConfig.saltSecret;
14
16
  }
15
17
 
16
18
  initHooks() {
17
- this.mongooseSchema.pre('save', async function () {
19
+ this.mongooseSchema.pre('save', async function userPreSaveHook() {
18
20
  if (this.isModified('password')) {
19
21
  this.password = await this.constructor.hashPassword(this.password);
20
22
  }
@@ -72,12 +74,9 @@ class User extends AbstractModel {
72
74
  if (!data) {
73
75
  return false;
74
76
  }
75
- const same = await bcrypt.compare(
76
- String(password) + data.constructor.getSuper().saltSecret,
77
- data.password,
78
- );
77
+ const hashedPasswords = await this.hashPassword(password);
79
78
 
80
- if (!same) {
79
+ if (data.password !== hashedPasswords) {
81
80
  return false;
82
81
  }
83
82
  return data;
@@ -86,10 +85,13 @@ class User extends AbstractModel {
86
85
  async generateToken() {
87
86
  const timestamp = new Date();
88
87
  timestamp.setDate(timestamp.getDate() + 30);
89
- const token = await bcrypt.hash(
88
+ const scryptAsync = promisify(scrypt);
89
+ const data = await scryptAsync(
90
90
  this.email + Date.now(),
91
- this.constructor.getSuper().saltRounds,
91
+ this.constructor.getSuper().saltSecret,
92
+ this.constructor.getSuper().hashRounds,
92
93
  );
94
+ const token = data.toString('base64url');
93
95
  this.sessionTokens.push({ token, valid: timestamp });
94
96
  await this.save();
95
97
  return { token, valid: timestamp };
@@ -108,10 +110,13 @@ class User extends AbstractModel {
108
110
  }
109
111
 
110
112
  static async hashPassword(password) {
111
- return bcrypt.hash(
112
- String(password) + this.getSuper().saltSecret,
113
- this.getSuper().saltRounds,
113
+ const scryptAsync = promisify(scrypt);
114
+ const data = await scryptAsync(
115
+ String(password),
116
+ this.getSuper().saltSecret,
117
+ this.getSuper().hashRounds,
114
118
  );
119
+ return data.toString('base64url');
115
120
  }
116
121
 
117
122
  static async getUserByToken(token) {
@@ -130,10 +135,13 @@ class User extends AbstractModel {
130
135
  static async generateUserPasswordRecoveryToken(userMongoose) {
131
136
  const date = new Date();
132
137
  date.setDate(date.getDate() + 14);
133
- const token = await bcrypt.hash(
138
+ const scryptAsync = promisify(scrypt);
139
+ const data = await scryptAsync(
134
140
  userMongoose.email + Date.now(),
135
- userMongoose.constructor.getSuper().saltRounds,
141
+ userMongoose.constructor.getSuper().saltSecret,
142
+ userMongoose.constructor.getSuper().hashRounds,
136
143
  );
144
+ const token = data.toString('base64url');
137
145
  // if (err) {
138
146
  // this.logger.error("Hash 2 error ", err);
139
147
  // reject(err);
@@ -184,10 +192,13 @@ class User extends AbstractModel {
184
192
  static async generateUserVerificationToken(userMongoose) {
185
193
  const date = new Date();
186
194
  date.setDate(date.getDate() + 14);
187
- const token = await bcrypt.hash(
195
+ const scryptAsync = promisify(scrypt);
196
+ const data = await scryptAsync(
188
197
  userMongoose.email + Date.now(),
189
- userMongoose.constructor.getSuper().saltRounds,
198
+ userMongoose.constructor.getSuper().saltSecret,
199
+ userMongoose.constructor.getSuper().hashRounds,
190
200
  );
201
+ const token = data.toString('base64url');
191
202
  // if (err) {
192
203
  // this.logger.error("Hash 2 error ", err);
193
204
  // reject(err);
@@ -219,16 +230,16 @@ class User extends AbstractModel {
219
230
  return result;
220
231
  }
221
232
 
222
- removeVerificationToken(verificationToken) {
223
- this.mongooseModel.updateOne(
224
- {
225
- verificationTokens: {
226
- $elemMatch: { token: String(verificationToken) },
227
- },
228
- },
229
- { $pop: { verificationTokens: 1 } },
230
- );
231
- }
233
+ // async removeVerificationToken(verificationToken) {
234
+ // this.mongooseModel.updateOne(
235
+ // {
236
+ // verificationTokens: {
237
+ // $elemMatch: { token: String(verificationToken) },
238
+ // },
239
+ // },
240
+ // { $pop: { verificationTokens: 1 } },
241
+ // );
242
+ // }
232
243
 
233
244
  async sendVerificationEmail(i18n) {
234
245
  const verificationToken = await User.generateUserVerificationToken(this);
@@ -22,7 +22,7 @@ describe('user model', () => {
22
22
  const user = await global.server.app.getModel('User').findOne({
23
23
  email: userEmail,
24
24
  });
25
- expect(user.password !== userPassword).toBe(true);
25
+ expect(user.password).not.toBe(userPassword);
26
26
  });
27
27
 
28
28
  it('passwords should not be changed on other fields save', async () => {
@@ -32,22 +32,74 @@ describe('user model', () => {
32
32
  });
33
33
  const psw = user.password;
34
34
  user.email = 'rrrr';
35
- user.save();
35
+ await user.save();
36
+ user.email = userEmail;
37
+ await user.save();
36
38
 
37
- expect(user.password === psw).toBe(true);
39
+ expect(user.password).toBe(psw);
38
40
  });
39
41
 
40
- describe('getUserByVerificationToken', () => {
42
+ describe('getUserByEmailAndPassword', () => {
43
+ it('should WORK with valid creds', async () => {
44
+ expect.assertions(1);
45
+ const userModel = await global.server.app.getModel('User');
46
+ const user = await userModel.getUserByEmailAndPassword(
47
+ userEmail,
48
+ userPassword,
49
+ );
50
+ expect(user.id).toBe(globalUser.id);
51
+ });
52
+
53
+ it('should NOT with INvalid creds', async () => {
54
+ expect.assertions(1);
55
+ const userModel = await global.server.app.getModel('User');
56
+ const user = await userModel.getUserByEmailAndPassword(
57
+ userEmail,
58
+ 'wrongPassword',
59
+ );
60
+ expect(user).toBe(false);
61
+ });
62
+
63
+ it('should NOT with wrong email', async () => {
64
+ expect.assertions(1);
65
+ const userModel = await global.server.app.getModel('User');
66
+ const user = await userModel.getUserByEmailAndPassword(
67
+ 'not@exists.com',
68
+ userPassword,
69
+ );
70
+ expect(user).toBe(false);
71
+ });
72
+ });
73
+
74
+ describe('getUserByToken', () => {
41
75
  it('should NOT work for non valid token', async () => {
42
- expect.assertions(2);
76
+ expect.assertions(1);
77
+
78
+ const user = await global.server.app
79
+ .getModel('User')
80
+ .getUserByToken('fake one');
81
+ expect(user).toBe(false);
82
+ });
43
83
 
84
+ it('should work for VALID token', async () => {
85
+ expect.assertions(1);
86
+ const token = await globalUser.generateToken();
44
87
  const user = await global.server.app
45
88
  .getModel('User')
46
- .getUserByVerificationToken('fake one')
47
- .catch((e) => {
48
- expect(e).toBeDefined();
49
- });
50
- expect(user).toBeUndefined();
89
+ .getUserByToken(token.token);
90
+ expect(user.id).toBe(globalUser.id);
91
+ });
92
+ });
93
+
94
+ describe('getUserByVerificationToken', () => {
95
+ it('should NOT work for non valid token', async () => {
96
+ expect.assertions(1);
97
+
98
+ await expect(
99
+ global.server.app
100
+ .getModel('User')
101
+ .getUserByVerificationToken('fake one'),
102
+ ).rejects.toStrictEqual(new Error('User not exists'));
51
103
  });
52
104
 
53
105
  it('should work for VALID token', async () => {
@@ -65,15 +117,13 @@ describe('user model', () => {
65
117
 
66
118
  describe('getUserByPasswordRecoveryToken', () => {
67
119
  it('should NOT work for non valid token', async () => {
68
- expect.assertions(2);
120
+ expect.assertions(1);
69
121
 
70
- const user = await global.server.app
71
- .getModel('User')
72
- .getUserByPasswordRecoveryToken('fake one')
73
- .catch((e) => {
74
- expect(e).toBeDefined();
75
- });
76
- expect(user).toBeUndefined();
122
+ await expect(
123
+ global.server.app
124
+ .getModel('User')
125
+ .getUserByPasswordRecoveryToken('fake one'),
126
+ ).rejects.toStrictEqual(new Error('User not exists'));
77
127
  });
78
128
 
79
129
  it('should work for VALID token', async () => {