@villedemontreal/mongo 6.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.
Files changed (69) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +226 -0
  3. package/dist/src/config/configs.d.ts +16 -0
  4. package/dist/src/config/configs.js +26 -0
  5. package/dist/src/config/configs.js.map +1 -0
  6. package/dist/src/config/constants.d.ts +85 -0
  7. package/dist/src/config/constants.js +104 -0
  8. package/dist/src/config/constants.js.map +1 -0
  9. package/dist/src/config/init.d.ts +9 -0
  10. package/dist/src/config/init.js +24 -0
  11. package/dist/src/config/init.js.map +1 -0
  12. package/dist/src/config/mongooseConfigs.d.ts +73 -0
  13. package/dist/src/config/mongooseConfigs.js +107 -0
  14. package/dist/src/config/mongooseConfigs.js.map +1 -0
  15. package/dist/src/index.d.ts +6 -0
  16. package/dist/src/index.js +24 -0
  17. package/dist/src/index.js.map +1 -0
  18. package/dist/src/mongoClient.d.ts +19 -0
  19. package/dist/src/mongoClient.js +111 -0
  20. package/dist/src/mongoClient.js.map +1 -0
  21. package/dist/src/mongoUpdater.d.ts +103 -0
  22. package/dist/src/mongoUpdater.js +297 -0
  23. package/dist/src/mongoUpdater.js.map +1 -0
  24. package/dist/src/mongoUpdater.test.d.ts +1 -0
  25. package/dist/src/mongoUpdater.test.js +232 -0
  26. package/dist/src/mongoUpdater.test.js.map +1 -0
  27. package/dist/src/mongoUtils.d.ts +68 -0
  28. package/dist/src/mongoUtils.js +280 -0
  29. package/dist/src/mongoUtils.js.map +1 -0
  30. package/dist/src/mongoUtils.test.d.ts +1 -0
  31. package/dist/src/mongoUtils.test.js +24 -0
  32. package/dist/src/mongoUtils.test.js.map +1 -0
  33. package/dist/src/plugins/pagination/index.d.ts +11 -0
  34. package/dist/src/plugins/pagination/index.js +79 -0
  35. package/dist/src/plugins/pagination/index.js.map +1 -0
  36. package/dist/src/plugins/pagination/index.test.d.ts +1 -0
  37. package/dist/src/plugins/pagination/index.test.js +129 -0
  38. package/dist/src/plugins/pagination/index.test.js.map +1 -0
  39. package/dist/src/plugins/pagination/specs/IPaginateOptions.d.ts +51 -0
  40. package/dist/src/plugins/pagination/specs/IPaginateOptions.js +3 -0
  41. package/dist/src/plugins/pagination/specs/IPaginateOptions.js.map +1 -0
  42. package/dist/src/utils/logger.d.ts +11 -0
  43. package/dist/src/utils/logger.js +54 -0
  44. package/dist/src/utils/logger.js.map +1 -0
  45. package/dist/src/utils/testingConfigurations.d.ts +8 -0
  46. package/dist/src/utils/testingConfigurations.js +17 -0
  47. package/dist/src/utils/testingConfigurations.js.map +1 -0
  48. package/dist/tests/testingMongoUpdates/1.0.0.d.ts +5 -0
  49. package/dist/tests/testingMongoUpdates/1.0.0.js +27 -0
  50. package/dist/tests/testingMongoUpdates/1.0.0.js.map +1 -0
  51. package/dist/tests/testingMongoUpdates/1.0.1.d.ts +5 -0
  52. package/dist/tests/testingMongoUpdates/1.0.1.js +22 -0
  53. package/dist/tests/testingMongoUpdates/1.0.1.js.map +1 -0
  54. package/package.json +63 -0
  55. package/src/config/configs.ts +27 -0
  56. package/src/config/constants.ts +122 -0
  57. package/src/config/init.ts +23 -0
  58. package/src/config/mongooseConfigs.ts +178 -0
  59. package/src/index.ts +13 -0
  60. package/src/mongoClient.ts +122 -0
  61. package/src/mongoUpdater.test.ts +286 -0
  62. package/src/mongoUpdater.ts +423 -0
  63. package/src/mongoUtils.test.ts +23 -0
  64. package/src/mongoUtils.ts +322 -0
  65. package/src/plugins/pagination/index.test.ts +140 -0
  66. package/src/plugins/pagination/index.ts +96 -0
  67. package/src/plugins/pagination/specs/IPaginateOptions.ts +51 -0
  68. package/src/utils/logger.ts +53 -0
  69. package/src/utils/testingConfigurations.ts +13 -0
@@ -0,0 +1,122 @@
1
+ import { defaultsDeep } from 'lodash';
2
+ import * as mongoose from 'mongoose';
3
+ import { constants } from './config/constants';
4
+ import { IMongooseConfigs, MongooseConfigs } from './config/mongooseConfigs';
5
+ import { IMongoUpdater, MongoUpdater } from './mongoUpdater';
6
+ import { mongoUtils } from './mongoUtils';
7
+ import { createLogger } from './utils/logger';
8
+
9
+ const logger = createLogger('mongoClient');
10
+
11
+ let mongooseConnection: mongoose.Connection;
12
+
13
+ /**
14
+ * This is the entry point to use this library to manage your
15
+ * Mongoose connections.
16
+ *
17
+ * It *must* be called when the application starts, before any
18
+ * connection is made to Mongo.
19
+ *
20
+ * @returns the Mongoose connection to Mongo.
21
+ */
22
+ export async function initMongoose(mongooseConfig: IMongooseConfigs): Promise<mongoose.Connection> {
23
+ // ==========================================
24
+ // Uses the MongooseConfigs to make sure unspecified
25
+ // configs have a default value.
26
+ // ==========================================
27
+ const mongooseConfigClean = new MongooseConfigs(mongooseConfig);
28
+
29
+ return new Promise<mongoose.Connection>(async (resolve, reject) => {
30
+ let connectionString = mongooseConfigClean.connectionString;
31
+
32
+ // ==========================================
33
+ // Mocked Mongo server?
34
+ // ==========================================
35
+ if (connectionString === constants.mongo.testing.MOCK_CONNECTION_STRING) {
36
+ // ==========================================
37
+ // Mock!
38
+ // ==========================================
39
+ const mongoServer = await mongoUtils.mockMongoose(null, mongooseConfigClean.mockServer.serverVersion);
40
+
41
+ connectionString = mongoServer.getUri();
42
+ }
43
+
44
+ try {
45
+ if (mongooseConnection) {
46
+ await mongooseConnection.close();
47
+ mongooseConnection = undefined;
48
+ }
49
+
50
+ // Updates Promise for mongoose, avoid warning log emit by mongoose
51
+ (mongoose as any).Promise = global.Promise;
52
+ const mongoOptions: mongoose.ConnectionOptions = defaultsDeep(mongooseConfigClean.connectionOptions, {
53
+ promiseLibrary: global.Promise
54
+ });
55
+
56
+ // Creates the connection
57
+ mongooseConnection = mongoose.createConnection(connectionString, mongoOptions);
58
+
59
+ // Triggered if an error occured
60
+ mongooseConnection.on('error', (err: any) => {
61
+ mongooseConnection = null;
62
+ reject('Mongo Database: Error connecting to Mongo: ' + err);
63
+ });
64
+
65
+ // Triggered when the connection is made.
66
+ mongooseConnection.on('connected', async () => {
67
+ // Check for schema updates once the connexion is made
68
+ if (mongooseConfigClean.applyUpdates) {
69
+ try {
70
+ await checkForUpdates(mongooseConfigClean);
71
+ } catch (err) {
72
+ try {
73
+ await mongooseConnection.close();
74
+ mongooseConnection = undefined;
75
+ } catch (err) {
76
+ logger.warning(`Error closing connection to Mongo : ${err}`);
77
+ }
78
+
79
+ return reject('Error updating Mongo: ' + err);
80
+ }
81
+ } else {
82
+ logger.info(`Mongo updates skipped`);
83
+ }
84
+
85
+ // All good!
86
+ resolve(mongooseConnection);
87
+ });
88
+ } catch (err) {
89
+ return reject('Error initializing Mongo: ' + err);
90
+ }
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Uses the MongoUpdater to validate if updates are required
96
+ * to run the application on the target Mongo database.
97
+ */
98
+ async function checkForUpdates(mongooseConfig: IMongooseConfigs): Promise<void> {
99
+ const connection = await getMongooseConnection();
100
+ const updater: IMongoUpdater = new MongoUpdater(
101
+ connection.db,
102
+ mongooseConfig.updater.mongoSchemaUpdatesDirPath,
103
+ mongooseConfig.updater.lockMaxAgeSeconds,
104
+ mongooseConfig.updater.appSchemaCollectionName
105
+ );
106
+ await updater.checkInstallation();
107
+ await updater.checkUpdates();
108
+ }
109
+
110
+ /**
111
+ * Returns the Mongoose connection.
112
+ *
113
+ * Will throw an error if Mongo haass not been initialized
114
+ * using the "initMongo()" function.
115
+ */
116
+ export function getMongooseConnection() {
117
+ if (!mongooseConnection) {
118
+ throw new Error("Mongo is not initialized! Please call the 'initMongo()' method first...");
119
+ }
120
+
121
+ return mongooseConnection;
122
+ }
@@ -0,0 +1,286 @@
1
+ // Ok for test files :
2
+ // tslint:disable:max-func-body-length
3
+ // tslint:disable:no-string-literal
4
+
5
+ import { Timer } from '@villedemontreal/general-utils';
6
+ import { assert } from 'chai';
7
+ import * as MongoDb from 'mongodb';
8
+ import * as sinon from 'sinon';
9
+ import { constants as mongodbConstants } from './config/constants';
10
+ import { IMongooseConfigs } from './config/mongooseConfigs';
11
+ import { initMongoose } from './mongoClient';
12
+ import { IMongoUpdater, MongoUpdater } from './mongoUpdater';
13
+ import { mongoUtils } from './mongoUtils';
14
+ import { setTestingConfigurations } from './utils/testingConfigurations';
15
+
16
+ let mongoDb: MongoDb.Db;
17
+ let mongoUpdater: IMongoUpdater;
18
+
19
+ setTestingConfigurations();
20
+
21
+ // ==========================================
22
+ // Mongo Updater
23
+ // ==========================================
24
+ describe('Mongo Updater', () => {
25
+ // A regular function is *required* to get the proper
26
+ // "this" value for "MongoUtils.mockMongoose(...)"
27
+ const testconfig: IMongooseConfigs = mongodbConstants.testsConfig;
28
+
29
+ before(async function() {
30
+ // Makes sure Mongoose is mocked, but not in Jenkins as we will start a dedicated mongodb container.
31
+ await mongoUtils.mockMongoose(this, testconfig.mockServer.serverVersion);
32
+ const connection = await initMongoose(testconfig);
33
+
34
+ assert.isOk(connection);
35
+ mongoDb = connection.db;
36
+
37
+ // Creates the Updater directly.
38
+ mongoUpdater = new MongoUpdater(
39
+ mongoDb,
40
+ testconfig.updater.mongoSchemaUpdatesDirPath,
41
+ testconfig.updater.lockMaxAgeSeconds,
42
+ testconfig.updater.appSchemaCollectionName
43
+ );
44
+ });
45
+
46
+ after(async () => {
47
+ // ==========================================
48
+ // Drops the mocked databases
49
+ // ==========================================
50
+ await mongoDb.dropDatabase();
51
+ });
52
+
53
+ describe('getSchemaVersion', async () => {
54
+ it('should contain schema version 0.0.0', async () => {
55
+ const version: string = await mongoUpdater.getAppSchemaVersion();
56
+ assert.strictEqual(version, '0.0.0');
57
+ });
58
+ });
59
+
60
+ describe('checkInstall', async () => {
61
+ let installAppSchemaCollectionSpy: sinon.SinonSpy;
62
+
63
+ beforeEach(async () => {
64
+ installAppSchemaCollectionSpy = sinon.spy(mongoUpdater, 'installAppSchemaCollection');
65
+ });
66
+
67
+ afterEach(async () => {
68
+ installAppSchemaCollectionSpy.restore();
69
+ });
70
+
71
+ it('should not contains app schema collection', async () => {
72
+ const collections: any[] = await mongoDb
73
+ .listCollections({ name: testconfig.updater.appSchemaCollectionName })
74
+ .toArray();
75
+ assert.strictEqual(collections.length, 0);
76
+ });
77
+
78
+ it('should call installAppSchemaCollection to create schema collection', async () => {
79
+ await mongoUpdater.checkInstallation();
80
+
81
+ assert.strictEqual(installAppSchemaCollectionSpy.callCount, 1);
82
+
83
+ const collections: any[] = await mongoDb
84
+ .listCollections({ name: testconfig.updater.appSchemaCollectionName })
85
+ .toArray();
86
+ assert.strictEqual(collections.length, 1);
87
+ });
88
+
89
+ it('should not call installAppSchemaCollection again', async () => {
90
+ await mongoUpdater.checkInstallation();
91
+ assert.strictEqual(installAppSchemaCollectionSpy.callCount, 0);
92
+ });
93
+
94
+ it('should contain schema version 0.0.0', async () => {
95
+ await mongoUpdater.checkInstallation();
96
+
97
+ const collections: any[] = await mongoDb
98
+ .listCollections({ name: testconfig.updater.appSchemaCollectionName })
99
+ .toArray();
100
+ assert.strictEqual(collections.length, 1);
101
+ assert.strictEqual(collections[0].name, testconfig.updater.appSchemaCollectionName);
102
+
103
+ const schema: MongoDb.Collection = mongoDb.collection(testconfig.updater.appSchemaCollectionName);
104
+ const schemaDb: any[] = await schema.find().toArray();
105
+ assert.strictEqual(schemaDb[0].version, '0.0.0');
106
+ });
107
+ });
108
+
109
+ describe('getSchemaVersion', async () => {
110
+ it('should contain schema version 0.0.0', async () => {
111
+ const version: string = await mongoUpdater.getAppSchemaVersion();
112
+ assert.strictEqual(version, '0.0.0');
113
+ });
114
+ });
115
+
116
+ describe('lock', async () => {
117
+ it('lock should be equal to false', async () => {
118
+ const schema: MongoDb.Collection = mongoDb.collection(testconfig.updater.appSchemaCollectionName);
119
+ const schemaDb: any[] = await schema.find().toArray();
120
+ assert.strictEqual(schemaDb[0].lock, false);
121
+ });
122
+
123
+ it('should success to lock', async () => {
124
+ const isLocked: boolean = await mongoUpdater.lockAppSchemaDocument();
125
+ assert.strictEqual(isLocked, true);
126
+ });
127
+
128
+ it('lock should be equal to true', async () => {
129
+ const schema: MongoDb.Collection = mongoDb.collection(testconfig.updater.appSchemaCollectionName);
130
+ const schemaDb: any[] = await schema.find().toArray();
131
+ assert.strictEqual(schemaDb[0].lock, true);
132
+ });
133
+
134
+ it('should fail to lock again', async () => {
135
+ const isLocked: boolean = await mongoUpdater.lockAppSchemaDocument();
136
+ assert.strictEqual(isLocked, false);
137
+ });
138
+ });
139
+
140
+ describe('unlock', async () => {
141
+ it('lock should be equal to true', async () => {
142
+ const schema: MongoDb.Collection = mongoDb.collection(testconfig.updater.appSchemaCollectionName);
143
+ const schemaDb: any[] = await schema.find().toArray();
144
+ assert.strictEqual(schemaDb[0].lock, true);
145
+ });
146
+
147
+ it('should success to unlock', async () => {
148
+ const isUnlocked: boolean = await mongoUpdater.unlockAppSchemaDocument();
149
+ assert.strictEqual(isUnlocked, true);
150
+ });
151
+
152
+ it('lock should be equal to false', async () => {
153
+ const schema: MongoDb.Collection = mongoDb.collection(testconfig.updater.appSchemaCollectionName);
154
+ const schemaDb: any[] = await schema.find().toArray();
155
+ assert.strictEqual(schemaDb[0].lock, false);
156
+ });
157
+
158
+ it('should fail to unlock again', async () => {
159
+ const isUnlocked: boolean = await mongoUpdater.unlockAppSchemaDocument();
160
+ assert.strictEqual(isUnlocked, false);
161
+
162
+ const schema: MongoDb.Collection = mongoDb.collection(testconfig.updater.appSchemaCollectionName);
163
+ const schemaDb: any[] = await schema.find().toArray();
164
+ assert.strictEqual(schemaDb[0].lock, false);
165
+ });
166
+ });
167
+
168
+ describe('updateSchemaVersion', async () => {
169
+ it('should contain schema version 0.0.0', async () => {
170
+ const version: string = await mongoUpdater.getAppSchemaVersion();
171
+ assert.strictEqual(version, '0.0.0');
172
+ });
173
+
174
+ it('should success to update the schema version to 6.5.4', async () => {
175
+ await mongoUpdater.updateAppSchemaVersion('0.0.0', '6.5.4');
176
+
177
+ const version: string = await mongoUpdater.getAppSchemaVersion();
178
+ assert.strictEqual(version, '6.5.4');
179
+ });
180
+
181
+ it('should success to update the schema version to 0.0.0', async () => {
182
+ await mongoUpdater.updateAppSchemaVersion('6.5.4', '0.0.0');
183
+
184
+ const version: string = await mongoUpdater.getAppSchemaVersion();
185
+ assert.strictEqual(version, '0.0.0');
186
+ });
187
+ });
188
+
189
+ describe('checkUpdate', async () => {
190
+ let lockSpy: sinon.SinonSpy;
191
+ let applyUpdateSchemasSpy: sinon.SinonSpy;
192
+ let updateSchemaVersionSpy: sinon.SinonSpy;
193
+ let unlockSpy: sinon.SinonSpy;
194
+ beforeEach(async () => {
195
+ lockSpy = sinon.spy(mongoUpdater, 'lockAppSchemaDocument');
196
+ applyUpdateSchemasSpy = sinon.spy(mongoUpdater, 'applyAppSchemaUpdates');
197
+ updateSchemaVersionSpy = sinon.spy(mongoUpdater, 'updateAppSchemaVersion');
198
+ unlockSpy = sinon.spy(mongoUpdater, 'unlockAppSchemaDocument');
199
+ });
200
+ afterEach(async () => {
201
+ await mongoDb.dropCollection('test');
202
+ lockSpy.restore();
203
+ applyUpdateSchemasSpy.restore();
204
+ updateSchemaVersionSpy.restore();
205
+ unlockSpy.restore();
206
+ });
207
+
208
+ it('should work when not already locked', async () => {
209
+ const timer = new Timer();
210
+ await mongoUpdater.checkUpdates();
211
+ const elapsed = timer.getMillisecondsElapsed();
212
+
213
+ assert.isTrue(elapsed < 2000);
214
+
215
+ assert.strictEqual(lockSpy.callCount, 1);
216
+ assert.strictEqual(applyUpdateSchemasSpy.callCount, 1);
217
+ assert.strictEqual(updateSchemaVersionSpy.callCount, 1);
218
+ assert.strictEqual(unlockSpy.callCount, 1);
219
+ });
220
+
221
+ // ==========================================
222
+ // A regular function is *required* to get the proper
223
+ // "this" value to call ".timeout(...)"
224
+ // ==========================================
225
+ it('should wait when is already locked and should delete a lock that is too old', async function() {
226
+ this.timeout(5000);
227
+
228
+ // Resets version to 0.0.0
229
+ await mongoUpdater.updateAppSchemaVersion('X.X.X', '0.0.0');
230
+
231
+ // ==========================================
232
+ // Temporarly changes the lock max age.
233
+ // ==========================================
234
+ const lockMaxAgeSecondsBackup = testconfig.updater.lockMaxAgeSeconds;
235
+ mongoUpdater['lockMaxAgeSeconds'] = 3;
236
+ assert.strictEqual(mongoUpdater['lockMaxAgeSeconds'], 3);
237
+
238
+ try {
239
+ const isLocked: boolean = await mongoUpdater.lockAppSchemaDocument();
240
+ assert.strictEqual(isLocked, true);
241
+
242
+ const timer = new Timer();
243
+ await mongoUpdater.checkUpdates();
244
+ const elapsed = timer.getMillisecondsElapsed();
245
+
246
+ assert.isTrue(elapsed > 3000);
247
+ } finally {
248
+ mongoUpdater['lockMaxAgeSeconds'] = lockMaxAgeSecondsBackup;
249
+ }
250
+ });
251
+ });
252
+
253
+ describe('getUpdateFiles', async () => {
254
+ it('should not contain files for version between 0.0.0 and 0.0.0', async () => {
255
+ const files: string[] = await mongoUpdater.getAppSchemaUpdateFiles('0.0.0', '0.0.0');
256
+ assert.strictEqual(files.length, 0);
257
+ });
258
+
259
+ it('should not contain files for version between 1.0.0 and 1.0.0', async () => {
260
+ const files: string[] = await mongoUpdater.getAppSchemaUpdateFiles('1.0.0', '1.0.0');
261
+ assert.strictEqual(files.length, 0);
262
+ });
263
+
264
+ it('should contain one file for version between 0.0.0 and 1.0.0', async () => {
265
+ const files: string[] = await mongoUpdater.getAppSchemaUpdateFiles('0.0.0', '1.0.0');
266
+
267
+ assert.strictEqual(files.length, 1);
268
+ assert.strictEqual(files[0], '1.0.0');
269
+ });
270
+ });
271
+
272
+ describe('applyUpdateSchemas', async () => {
273
+ let getUpdateFilesSpy: sinon.SinonSpy;
274
+ beforeEach(async () => {
275
+ getUpdateFilesSpy = sinon.spy(mongoUpdater, 'getAppSchemaUpdateFiles');
276
+ });
277
+ afterEach(async () => {
278
+ getUpdateFilesSpy.restore();
279
+ });
280
+
281
+ it('should call getUpdateFilesSpy one time', async () => {
282
+ await mongoUpdater.applyAppSchemaUpdates('0.0.0', '1.0.0');
283
+ assert.strictEqual(getUpdateFilesSpy.callCount, 1);
284
+ });
285
+ });
286
+ });