meadow-connection-mongodb 1.0.1 → 1.0.2

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/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "meadow-connection-mongodb",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Meadow MongoDB Connection Plugin",
5
5
  "main": "source/Meadow-Connection-MongoDB.js",
6
6
  "scripts": {
7
7
  "coverage": "npx quack coverage",
8
- "test": "npx quack test"
8
+ "test": "npx quack test",
9
+ "docker-mongodb-start": "bash ./scripts/mongodb-test-db.sh start",
10
+ "docker-mongodb-stop": "bash ./scripts/mongodb-test-db.sh stop",
11
+ "docker-mongodb-status": "bash ./scripts/mongodb-test-db.sh status",
12
+ "test-docker": "bash ./scripts/mongodb-test-db.sh start && npm test"
9
13
  },
10
14
  "mocha": {
11
15
  "diff": true,
@@ -15,7 +19,7 @@
15
19
  "package": "./package.json",
16
20
  "reporter": "spec",
17
21
  "slow": "75",
18
- "timeout": "5000",
22
+ "timeout": "10000",
19
23
  "ui": "tdd",
20
24
  "watch-files": [
21
25
  "source/**/*.js",
@@ -0,0 +1,98 @@
1
+ #!/bin/bash
2
+ # MongoDB Test Database Management Script for meadow-connection-mongodb
3
+ #
4
+ # Usage:
5
+ # ./scripts/mongodb-test-db.sh start - Start MongoDB container and wait for readiness
6
+ # ./scripts/mongodb-test-db.sh stop - Stop and remove the container
7
+ # ./scripts/mongodb-test-db.sh status - Check if the container is running
8
+ #
9
+ # The container settings match the test configuration in
10
+ # test/MongoDB_tests.js:
11
+ # Host: 127.0.0.1, Port: 27117, Database: meadow_conn_test
12
+
13
+ CONTAINER_NAME="meadow-conn-mongodb-test"
14
+ MONGODB_PORT="27117"
15
+ MONGODB_IMAGE="mongo:7"
16
+
17
+ start_mongodb() {
18
+ # Check if container already exists
19
+ if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
20
+ if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
21
+ echo "MongoDB test container is already running."
22
+ return 0
23
+ else
24
+ echo "Removing stopped container..."
25
+ docker rm "${CONTAINER_NAME}" > /dev/null 2>&1
26
+ fi
27
+ fi
28
+
29
+ echo "Starting MongoDB test container..."
30
+ docker run -d \
31
+ --name "${CONTAINER_NAME}" \
32
+ -p "${MONGODB_PORT}:27017" \
33
+ "${MONGODB_IMAGE}"
34
+
35
+ if [ $? -ne 0 ]; then
36
+ echo "ERROR: Failed to start MongoDB container."
37
+ exit 1
38
+ fi
39
+
40
+ echo "Waiting for MongoDB to be ready..."
41
+ RETRIES=30
42
+ until docker exec "${CONTAINER_NAME}" mongosh --eval "db.adminCommand('ping')" > /dev/null 2>&1; do
43
+ RETRIES=$((RETRIES - 1))
44
+ if [ $RETRIES -le 0 ]; then
45
+ echo "ERROR: MongoDB failed to become ready in time."
46
+ docker logs "${CONTAINER_NAME}" 2>&1 | tail -20
47
+ exit 1
48
+ fi
49
+ echo " ...waiting (${RETRIES} retries left)"
50
+ sleep 2
51
+ done
52
+
53
+ echo ""
54
+ echo "MongoDB test database is ready!"
55
+ echo " Container: ${CONTAINER_NAME}"
56
+ echo " Host: 127.0.0.1:${MONGODB_PORT}"
57
+ echo " Database: meadow_conn_test"
58
+ echo ""
59
+ echo "Run tests with: npm test"
60
+ }
61
+
62
+ stop_mongodb() {
63
+ if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
64
+ echo "Stopping and removing MongoDB test container..."
65
+ docker stop "${CONTAINER_NAME}" > /dev/null 2>&1
66
+ docker rm "${CONTAINER_NAME}" > /dev/null 2>&1
67
+ echo "MongoDB test container removed."
68
+ else
69
+ echo "No MongoDB test container found."
70
+ fi
71
+ }
72
+
73
+ status_mongodb() {
74
+ if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
75
+ echo "MongoDB test container is running."
76
+ docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
77
+ elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
78
+ echo "MongoDB test container exists but is stopped."
79
+ else
80
+ echo "MongoDB test container is not running."
81
+ fi
82
+ }
83
+
84
+ case "${1}" in
85
+ start)
86
+ start_mongodb
87
+ ;;
88
+ stop)
89
+ stop_mongodb
90
+ ;;
91
+ status)
92
+ status_mongodb
93
+ ;;
94
+ *)
95
+ echo "Usage: $0 {start|stop|status}"
96
+ exit 1
97
+ ;;
98
+ esac
@@ -6,6 +6,8 @@ const libFableServiceProviderBase = require('fable-serviceproviderbase');
6
6
 
7
7
  const { MongoClient } = require('mongodb');
8
8
 
9
+ const libMeadowSchemaMongoDB = require('./Meadow-Schema-MongoDB.js');
10
+
9
11
  class MeadowConnectionMongoDB extends libFableServiceProviderBase
10
12
  {
11
13
  constructor(pFable, pManifest, pServiceHash)
@@ -66,12 +68,20 @@ class MeadowConnectionMongoDB extends libFableServiceProviderBase
66
68
  this._Database = false;
67
69
  this.connected = false;
68
70
 
71
+ // Schema provider handles DDL operations (create, drop, index, etc.)
72
+ this._SchemaProvider = new libMeadowSchemaMongoDB(this.fable, this.options, `${this.Hash}-Schema`);
73
+
69
74
  if (this.options.MeadowConnectionMongoDBAutoConnect)
70
75
  {
71
76
  this.connect();
72
77
  }
73
78
  }
74
79
 
80
+ get schemaProvider()
81
+ {
82
+ return this._SchemaProvider;
83
+ }
84
+
75
85
  /**
76
86
  * Build the MongoDB connection URI from options.
77
87
  */
@@ -96,136 +106,22 @@ class MeadowConnectionMongoDB extends libFableServiceProviderBase
96
106
 
97
107
  generateDropTableStatement(pTableName)
98
108
  {
99
- // Returns a descriptor; the actual drop uses the MongoDB driver
100
- return { operation: 'drop', collection: pTableName };
109
+ return this._SchemaProvider.generateDropTableStatement(pTableName);
101
110
  }
102
111
 
103
112
  generateCreateTableStatement(pMeadowTableSchema)
104
113
  {
105
- this.log.info(`--> Building the collection creation descriptor for ${pMeadowTableSchema.TableName} ...`);
106
-
107
- let tmpDescriptor = {
108
- operation: 'createCollection',
109
- collection: pMeadowTableSchema.TableName,
110
- indexes: []
111
- };
112
-
113
- for (let j = 0; j < pMeadowTableSchema.Columns.length; j++)
114
- {
115
- let tmpColumn = pMeadowTableSchema.Columns[j];
116
-
117
- switch (tmpColumn.DataType)
118
- {
119
- case 'ID':
120
- // AutoIdentity field gets a unique ascending index
121
- tmpDescriptor.indexes.push(
122
- {
123
- key: { [tmpColumn.Column]: 1 },
124
- unique: true,
125
- name: `idx_${tmpColumn.Column}_unique`
126
- });
127
- break;
128
- case 'GUID':
129
- // GUID field gets a unique index
130
- tmpDescriptor.indexes.push(
131
- {
132
- key: { [tmpColumn.Column]: 1 },
133
- unique: true,
134
- name: `idx_${tmpColumn.Column}_unique`
135
- });
136
- break;
137
- case 'ForeignKey':
138
- // Foreign key gets an index for lookups
139
- tmpDescriptor.indexes.push(
140
- {
141
- key: { [tmpColumn.Column]: 1 },
142
- name: `idx_${tmpColumn.Column}`
143
- });
144
- break;
145
- default:
146
- break;
147
- }
148
- }
149
-
150
- return tmpDescriptor;
114
+ return this._SchemaProvider.generateCreateTableStatement(pMeadowTableSchema);
151
115
  }
152
116
 
153
117
  createTables(pMeadowSchema, fCallback)
154
118
  {
155
- this.fable.Utility.eachLimit(pMeadowSchema.Tables, 1,
156
- (pTable, fCreateComplete) =>
157
- {
158
- return this.createTable(pTable, fCreateComplete);
159
- },
160
- (pCreateError) =>
161
- {
162
- if (pCreateError)
163
- {
164
- this.log.error(`Meadow-MongoDB Error creating collections from Schema: ${pCreateError}`, pCreateError);
165
- }
166
- this.log.info('Done creating collections!');
167
- return fCallback(pCreateError);
168
- });
119
+ return this._SchemaProvider.createTables(pMeadowSchema, fCallback);
169
120
  }
170
121
 
171
122
  createTable(pMeadowTableSchema, fCallback)
172
123
  {
173
- let tmpDescriptor = this.generateCreateTableStatement(pMeadowTableSchema);
174
-
175
- if (!this._Database)
176
- {
177
- this.log.error(`Meadow-MongoDB CREATE COLLECTION ${tmpDescriptor.collection} failed: not connected.`);
178
- return fCallback(new Error('Not connected to MongoDB'));
179
- }
180
-
181
- // Create collection (ignore error if it already exists)
182
- this._Database.createCollection(tmpDescriptor.collection)
183
- .then(() =>
184
- {
185
- this.log.info(`Meadow-MongoDB collection ${tmpDescriptor.collection} created or already exists.`);
186
-
187
- // Create indexes if any
188
- if (tmpDescriptor.indexes.length > 0)
189
- {
190
- let tmpCollection = this._Database.collection(tmpDescriptor.collection);
191
- return tmpCollection.createIndexes(tmpDescriptor.indexes);
192
- }
193
- })
194
- .then(() =>
195
- {
196
- this.log.info(`Meadow-MongoDB indexes for ${tmpDescriptor.collection} created successfully.`);
197
- return fCallback();
198
- })
199
- .catch((pError) =>
200
- {
201
- // 48 = NamespaceExists (collection already exists)
202
- if (pError.code === 48)
203
- {
204
- this.log.warn(`Meadow-MongoDB collection ${tmpDescriptor.collection} already existed.`);
205
-
206
- // Still try to create indexes
207
- if (tmpDescriptor.indexes.length > 0)
208
- {
209
- let tmpCollection = this._Database.collection(tmpDescriptor.collection);
210
- tmpCollection.createIndexes(tmpDescriptor.indexes)
211
- .then(() => fCallback())
212
- .catch((pIndexError) =>
213
- {
214
- this.log.warn(`Meadow-MongoDB index creation for ${tmpDescriptor.collection}: ${pIndexError.message}`);
215
- return fCallback();
216
- });
217
- }
218
- else
219
- {
220
- return fCallback();
221
- }
222
- }
223
- else
224
- {
225
- this.log.error(`Meadow-MongoDB CREATE COLLECTION ${tmpDescriptor.collection} failed!`, pError);
226
- return fCallback(pError);
227
- }
228
- });
124
+ return this._SchemaProvider.createTable(pMeadowTableSchema, fCallback);
229
125
  }
230
126
 
231
127
  connect()
@@ -248,6 +144,7 @@ class MeadowConnectionMongoDB extends libFableServiceProviderBase
248
144
  this._Client = new MongoClient(tmpURI, tmpOptions);
249
145
  this._Database = this._Client.db(this.options.MongoDB.database);
250
146
  this.connected = true;
147
+ this._SchemaProvider.setDatabase(this._Database);
251
148
  }
252
149
 
253
150
  connectAsync(fCallback)
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Meadow MongoDB Schema Provider
3
+ *
4
+ * Handles collection creation, dropping, and schema generation for MongoDB.
5
+ * Separated from the connection provider to allow independent extension
6
+ * for indexing and other schema operations.
7
+ *
8
+ * @author Steven Velozo <steven@velozo.com>
9
+ */
10
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
11
+
12
+ class MeadowSchemaMongoDB extends libFableServiceProviderBase
13
+ {
14
+ constructor(pFable, pOptions, pServiceHash)
15
+ {
16
+ super(pFable, pOptions, pServiceHash);
17
+
18
+ this.serviceType = 'MeadowSchemaMongoDB';
19
+
20
+ // Reference to the MongoDB database instance, set by the connection provider
21
+ this._Database = false;
22
+ }
23
+
24
+ /**
25
+ * Set the database reference for executing schema operations.
26
+ * @param {object} pDatabase - MongoDB Db instance
27
+ * @returns {MeadowSchemaMongoDB} this (for chaining)
28
+ */
29
+ setDatabase(pDatabase)
30
+ {
31
+ this._Database = pDatabase;
32
+ return this;
33
+ }
34
+
35
+ generateDropTableStatement(pTableName)
36
+ {
37
+ // Returns a descriptor; the actual drop uses the MongoDB driver
38
+ return { operation: 'drop', collection: pTableName };
39
+ }
40
+
41
+ generateCreateTableStatement(pMeadowTableSchema)
42
+ {
43
+ this.log.info(`--> Building the collection creation descriptor for ${pMeadowTableSchema.TableName} ...`);
44
+
45
+ let tmpDescriptor = {
46
+ operation: 'createCollection',
47
+ collection: pMeadowTableSchema.TableName,
48
+ indexes: []
49
+ };
50
+
51
+ for (let j = 0; j < pMeadowTableSchema.Columns.length; j++)
52
+ {
53
+ let tmpColumn = pMeadowTableSchema.Columns[j];
54
+
55
+ switch (tmpColumn.DataType)
56
+ {
57
+ case 'ID':
58
+ // AutoIdentity field gets a unique ascending index
59
+ tmpDescriptor.indexes.push(
60
+ {
61
+ key: { [tmpColumn.Column]: 1 },
62
+ unique: true,
63
+ name: `idx_${tmpColumn.Column}_unique`
64
+ });
65
+ break;
66
+ case 'GUID':
67
+ // GUID field gets a unique index
68
+ tmpDescriptor.indexes.push(
69
+ {
70
+ key: { [tmpColumn.Column]: 1 },
71
+ unique: true,
72
+ name: `idx_${tmpColumn.Column}_unique`
73
+ });
74
+ break;
75
+ case 'ForeignKey':
76
+ // Foreign key gets an index for lookups
77
+ tmpDescriptor.indexes.push(
78
+ {
79
+ key: { [tmpColumn.Column]: 1 },
80
+ name: `idx_${tmpColumn.Column}`
81
+ });
82
+ break;
83
+ default:
84
+ break;
85
+ }
86
+ }
87
+
88
+ return tmpDescriptor;
89
+ }
90
+
91
+ createTables(pMeadowSchema, fCallback)
92
+ {
93
+ this.fable.Utility.eachLimit(pMeadowSchema.Tables, 1,
94
+ (pTable, fCreateComplete) =>
95
+ {
96
+ return this.createTable(pTable, fCreateComplete);
97
+ },
98
+ (pCreateError) =>
99
+ {
100
+ if (pCreateError)
101
+ {
102
+ this.log.error(`Meadow-MongoDB Error creating collections from Schema: ${pCreateError}`, pCreateError);
103
+ }
104
+ this.log.info('Done creating collections!');
105
+ return fCallback(pCreateError);
106
+ });
107
+ }
108
+
109
+ createTable(pMeadowTableSchema, fCallback)
110
+ {
111
+ let tmpDescriptor = this.generateCreateTableStatement(pMeadowTableSchema);
112
+
113
+ if (!this._Database)
114
+ {
115
+ this.log.error(`Meadow-MongoDB CREATE COLLECTION ${tmpDescriptor.collection} failed: not connected.`);
116
+ return fCallback(new Error('Not connected to MongoDB'));
117
+ }
118
+
119
+ // Create collection (ignore error if it already exists)
120
+ this._Database.createCollection(tmpDescriptor.collection)
121
+ .then(() =>
122
+ {
123
+ this.log.info(`Meadow-MongoDB collection ${tmpDescriptor.collection} created or already exists.`);
124
+
125
+ // Create indexes if any
126
+ if (tmpDescriptor.indexes.length > 0)
127
+ {
128
+ let tmpCollection = this._Database.collection(tmpDescriptor.collection);
129
+ return tmpCollection.createIndexes(tmpDescriptor.indexes);
130
+ }
131
+ })
132
+ .then(() =>
133
+ {
134
+ this.log.info(`Meadow-MongoDB indexes for ${tmpDescriptor.collection} created successfully.`);
135
+ return fCallback();
136
+ })
137
+ .catch((pError) =>
138
+ {
139
+ // 48 = NamespaceExists (collection already exists)
140
+ if (pError.code === 48)
141
+ {
142
+ this.log.warn(`Meadow-MongoDB collection ${tmpDescriptor.collection} already existed.`);
143
+
144
+ // Still try to create indexes
145
+ if (tmpDescriptor.indexes.length > 0)
146
+ {
147
+ let tmpCollection = this._Database.collection(tmpDescriptor.collection);
148
+ tmpCollection.createIndexes(tmpDescriptor.indexes)
149
+ .then(() => fCallback())
150
+ .catch((pIndexError) =>
151
+ {
152
+ this.log.warn(`Meadow-MongoDB index creation for ${tmpDescriptor.collection}: ${pIndexError.message}`);
153
+ return fCallback();
154
+ });
155
+ }
156
+ else
157
+ {
158
+ return fCallback();
159
+ }
160
+ }
161
+ else
162
+ {
163
+ this.log.error(`Meadow-MongoDB CREATE COLLECTION ${tmpDescriptor.collection} failed!`, pError);
164
+ return fCallback(pError);
165
+ }
166
+ });
167
+ }
168
+ }
169
+
170
+ module.exports = MeadowSchemaMongoDB;
@@ -32,14 +32,40 @@ const _FableConfig = (
32
32
  "MongoDB":
33
33
  {
34
34
  "Server": "127.0.0.1",
35
- "Port": 27017,
35
+ "Port": 27117,
36
36
  "User": "",
37
37
  "Password": "",
38
- "Database": "testdb",
38
+ "Database": "meadow_conn_test",
39
39
  "ConnectionPoolLimit": 20
40
40
  }
41
41
  });
42
42
 
43
+ const _AnimalSchema = {
44
+ TableName: 'Animal',
45
+ Columns: [
46
+ { Column: 'IDAnimal', DataType: 'ID' },
47
+ { Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
48
+ { Column: 'Name', DataType: 'String', Size: 128 },
49
+ { Column: 'Age', DataType: 'Numeric' },
50
+ { Column: 'Cost', DataType: 'Decimal', Size: '10,2' },
51
+ { Column: 'Description', DataType: 'Text' },
52
+ { Column: 'Birthday', DataType: 'DateTime' },
53
+ { Column: 'Active', DataType: 'Boolean' },
54
+ { Column: 'IDFarm', DataType: 'ForeignKey' }
55
+ ]
56
+ };
57
+
58
+ const _VehicleSchema = {
59
+ TableName: 'Vehicle',
60
+ Columns: [
61
+ { Column: 'IDVehicle', DataType: 'ID' },
62
+ { Column: 'GUIDVehicle', DataType: 'GUID', Size: 36 },
63
+ { Column: 'Make', DataType: 'String', Size: 64 },
64
+ { Column: 'Model', DataType: 'String', Size: 64 },
65
+ { Column: 'Year', DataType: 'Numeric' }
66
+ ]
67
+ };
68
+
43
69
  suite
44
70
  (
45
71
  'Meadow-Connection-MongoDB',
@@ -96,22 +122,7 @@ suite
96
122
  _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
97
123
  _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
98
124
 
99
- let tmpSchema = {
100
- TableName: 'Animal',
101
- Columns: [
102
- { Column: 'IDAnimal', DataType: 'ID' },
103
- { Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
104
- { Column: 'Name', DataType: 'String', Size: 128 },
105
- { Column: 'Age', DataType: 'Numeric' },
106
- { Column: 'Cost', DataType: 'Decimal', Size: '10,2' },
107
- { Column: 'Description', DataType: 'Text' },
108
- { Column: 'Birthday', DataType: 'DateTime' },
109
- { Column: 'Active', DataType: 'Boolean' },
110
- { Column: 'IDFarm', DataType: 'ForeignKey' }
111
- ]
112
- };
113
-
114
- let tmpResult = _Fable.MeadowMongoDBProvider.generateCreateTableStatement(tmpSchema);
125
+ let tmpResult = _Fable.MeadowMongoDBProvider.generateCreateTableStatement(_AnimalSchema);
115
126
  Expect(tmpResult.operation).to.equal('createCollection');
116
127
  Expect(tmpResult.collection).to.equal('Animal');
117
128
  Expect(tmpResult.indexes).to.be.an('array');
@@ -152,7 +163,7 @@ suite
152
163
  _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
153
164
 
154
165
  let tmpURI = _Fable.MeadowMongoDBProvider._buildConnectionURI();
155
- Expect(tmpURI).to.equal('mongodb://127.0.0.1:27017/testdb');
166
+ Expect(tmpURI).to.equal('mongodb://127.0.0.1:27117/meadow_conn_test');
156
167
  }
157
168
  );
158
169
  test
@@ -180,5 +191,440 @@ suite
180
191
  );
181
192
  }
182
193
  );
194
+ suite
195
+ (
196
+ 'Connection',
197
+ () =>
198
+ {
199
+ test
200
+ (
201
+ 'connect with default fable.settings',
202
+ (fDone) =>
203
+ {
204
+ let _Fable = new libFable(_FableConfig);
205
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
206
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
207
+
208
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(false);
209
+
210
+ _Fable.MeadowMongoDBProvider.connect();
211
+
212
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(true);
213
+ Expect(_Fable.MeadowMongoDBProvider.pool).to.be.an('object');
214
+
215
+ // Verify we can actually communicate with MongoDB
216
+ _Fable.MeadowMongoDBProvider.pool.admin().ping()
217
+ .then((pResult) =>
218
+ {
219
+ Expect(pResult).to.have.property('ok');
220
+ Expect(pResult.ok).to.equal(1);
221
+ _Fable.MeadowMongoDBProvider.client.close();
222
+ return fDone();
223
+ })
224
+ .catch((pError) =>
225
+ {
226
+ _Fable.MeadowMongoDBProvider.client.close();
227
+ return fDone(pError);
228
+ });
229
+ }
230
+ );
231
+ test
232
+ (
233
+ 'connectAsync callback',
234
+ (fDone) =>
235
+ {
236
+ let _Fable = new libFable(_FableConfig);
237
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
238
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
239
+
240
+ _Fable.MeadowMongoDBProvider.connectAsync(
241
+ (pError, pDatabase) =>
242
+ {
243
+ Expect(pError).to.equal(null);
244
+ Expect(pDatabase).to.be.an('object');
245
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(true);
246
+ Expect(_Fable.MeadowMongoDBProvider.pool).to.equal(pDatabase);
247
+
248
+ _Fable.MeadowMongoDBProvider.client.close();
249
+ return fDone();
250
+ });
251
+ }
252
+ );
253
+ test
254
+ (
255
+ 'autoconnect via MeadowConnectionMongoDBAutoConnect',
256
+ (fDone) =>
257
+ {
258
+ let tmpConfig = JSON.parse(JSON.stringify(_FableConfig));
259
+ tmpConfig.MeadowConnectionMongoDBAutoConnect = true;
260
+
261
+ let _Fable = new libFable(tmpConfig);
262
+ _Fable.serviceManager.addAndInstantiateServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
263
+
264
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(true);
265
+ Expect(_Fable.MeadowMongoDBProvider.pool).to.be.an('object');
266
+
267
+ _Fable.MeadowMongoDBProvider.pool.admin().ping()
268
+ .then((pResult) =>
269
+ {
270
+ Expect(pResult.ok).to.equal(1);
271
+ _Fable.MeadowMongoDBProvider.client.close();
272
+ return fDone();
273
+ })
274
+ .catch((pError) =>
275
+ {
276
+ _Fable.MeadowMongoDBProvider.client.close();
277
+ return fDone(pError);
278
+ });
279
+ }
280
+ );
281
+ test
282
+ (
283
+ 'connect when already connected logs error and does not throw',
284
+ (fDone) =>
285
+ {
286
+ let _Fable = new libFable(_FableConfig);
287
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
288
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
289
+
290
+ _Fable.MeadowMongoDBProvider.connect();
291
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(true);
292
+
293
+ // Second connect should not throw
294
+ _Fable.MeadowMongoDBProvider.connect();
295
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(true);
296
+
297
+ _Fable.MeadowMongoDBProvider.client.close();
298
+ return fDone();
299
+ }
300
+ );
301
+ test
302
+ (
303
+ 'pass in your own settings and connect',
304
+ (fDone) =>
305
+ {
306
+ let _Fable = new libFable();
307
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
308
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider', {MongoDB: _FableConfig.MongoDB});
309
+
310
+ _Fable.MeadowMongoDBProvider.connect();
311
+
312
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(true);
313
+
314
+ _Fable.MeadowMongoDBProvider.pool.admin().ping()
315
+ .then((pResult) =>
316
+ {
317
+ Expect(pResult.ok).to.equal(1);
318
+ _Fable.MeadowMongoDBProvider.client.close();
319
+ return fDone();
320
+ })
321
+ .catch((pError) =>
322
+ {
323
+ _Fable.MeadowMongoDBProvider.client.close();
324
+ return fDone(pError);
325
+ });
326
+ }
327
+ );
328
+ }
329
+ );
330
+ suite
331
+ (
332
+ 'Collection & Index Creation',
333
+ () =>
334
+ {
335
+ let _Fable = null;
336
+
337
+ suiteSetup
338
+ (
339
+ (fDone) =>
340
+ {
341
+ _Fable = new libFable(_FableConfig);
342
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
343
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
344
+ _Fable.MeadowMongoDBProvider.connect();
345
+
346
+ // Drop any leftover test collections
347
+ let tmpDb = _Fable.MeadowMongoDBProvider.pool;
348
+ Promise.all([
349
+ tmpDb.collection('Animal').drop().catch(() => {}),
350
+ tmpDb.collection('Vehicle').drop().catch(() => {})
351
+ ])
352
+ .then(() => fDone())
353
+ .catch(() => fDone());
354
+ }
355
+ );
356
+
357
+ suiteTeardown
358
+ (
359
+ (fDone) =>
360
+ {
361
+ let tmpDb = _Fable.MeadowMongoDBProvider.pool;
362
+ Promise.all([
363
+ tmpDb.collection('Animal').drop().catch(() => {}),
364
+ tmpDb.collection('Vehicle').drop().catch(() => {})
365
+ ])
366
+ .then(() =>
367
+ {
368
+ _Fable.MeadowMongoDBProvider.client.close();
369
+ fDone();
370
+ })
371
+ .catch(() =>
372
+ {
373
+ _Fable.MeadowMongoDBProvider.client.close();
374
+ fDone();
375
+ });
376
+ }
377
+ );
378
+
379
+ test
380
+ (
381
+ 'createTable creates collection and indexes',
382
+ (fDone) =>
383
+ {
384
+ _Fable.MeadowMongoDBProvider.createTable(_AnimalSchema,
385
+ (pError) =>
386
+ {
387
+ Expect(pError).to.not.be.an('error');
388
+
389
+ let tmpDb = _Fable.MeadowMongoDBProvider.pool;
390
+
391
+ // Verify collection exists
392
+ tmpDb.listCollections({ name: 'Animal' }).toArray()
393
+ .then((pCollections) =>
394
+ {
395
+ Expect(pCollections.length).to.equal(1);
396
+ Expect(pCollections[0].name).to.equal('Animal');
397
+
398
+ // Verify indexes were created
399
+ return tmpDb.collection('Animal').indexes();
400
+ })
401
+ .then((pIndexes) =>
402
+ {
403
+ // _id (default) + IDAnimal + GUIDAnimal + IDFarm = 4
404
+ Expect(pIndexes.length).to.equal(4);
405
+
406
+ // Find our custom indexes
407
+ let tmpIdAnimalIdx = pIndexes.find((pIdx) => pIdx.name === 'idx_IDAnimal_unique');
408
+ Expect(tmpIdAnimalIdx).to.be.an('object');
409
+ Expect(tmpIdAnimalIdx.unique).to.equal(true);
410
+
411
+ let tmpGuidIdx = pIndexes.find((pIdx) => pIdx.name === 'idx_GUIDAnimal_unique');
412
+ Expect(tmpGuidIdx).to.be.an('object');
413
+ Expect(tmpGuidIdx.unique).to.equal(true);
414
+
415
+ let tmpForeignKeyIdx = pIndexes.find((pIdx) => pIdx.name === 'idx_IDFarm');
416
+ Expect(tmpForeignKeyIdx).to.be.an('object');
417
+
418
+ return fDone();
419
+ })
420
+ .catch((pError) => fDone(pError));
421
+ });
422
+ }
423
+ );
424
+ test
425
+ (
426
+ 'createTable is idempotent',
427
+ (fDone) =>
428
+ {
429
+ // Call createTable again for the same schema — should not error
430
+ _Fable.MeadowMongoDBProvider.createTable(_AnimalSchema,
431
+ (pError) =>
432
+ {
433
+ Expect(pError).to.not.be.an('error');
434
+ return fDone();
435
+ });
436
+ }
437
+ );
438
+ test
439
+ (
440
+ 'createTables creates multiple collections',
441
+ (fDone) =>
442
+ {
443
+ let tmpMultiSchema = {
444
+ Tables: [_AnimalSchema, _VehicleSchema]
445
+ };
446
+
447
+ _Fable.MeadowMongoDBProvider.createTables(tmpMultiSchema,
448
+ (pError) =>
449
+ {
450
+ Expect(pError).to.not.be.an('error');
451
+
452
+ let tmpDb = _Fable.MeadowMongoDBProvider.pool;
453
+ tmpDb.listCollections().toArray()
454
+ .then((pCollections) =>
455
+ {
456
+ let tmpNames = pCollections.map((pC) => pC.name);
457
+ Expect(tmpNames).to.include('Animal');
458
+ Expect(tmpNames).to.include('Vehicle');
459
+ return fDone();
460
+ })
461
+ .catch((pError) => fDone(pError));
462
+ });
463
+ }
464
+ );
465
+ test
466
+ (
467
+ 'createTable when not connected returns error',
468
+ (fDone) =>
469
+ {
470
+ // Create a disconnected provider (no connect() call)
471
+ let tmpFable = new libFable(_FableConfig);
472
+ tmpFable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
473
+ tmpFable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
474
+
475
+ tmpFable.MeadowMongoDBProvider.createTable(_AnimalSchema,
476
+ (pError) =>
477
+ {
478
+ Expect(pError).to.be.an('error');
479
+ Expect(pError.message).to.contain('Not connected');
480
+ return fDone();
481
+ });
482
+ }
483
+ );
484
+ }
485
+ );
486
+ suite
487
+ (
488
+ 'Raw MongoDB Operations',
489
+ () =>
490
+ {
491
+ let _Fable = null;
492
+
493
+ suiteSetup
494
+ (
495
+ (fDone) =>
496
+ {
497
+ _Fable = new libFable(_FableConfig);
498
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
499
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
500
+ _Fable.MeadowMongoDBProvider.connect();
501
+
502
+ // Set up a clean test collection with indexes
503
+ let tmpDb = _Fable.MeadowMongoDBProvider.pool;
504
+ tmpDb.collection('RawTestAnimal').drop()
505
+ .catch(() => {})
506
+ .then(() =>
507
+ {
508
+ return tmpDb.createCollection('RawTestAnimal');
509
+ })
510
+ .then(() =>
511
+ {
512
+ return fDone();
513
+ })
514
+ .catch((pError) => fDone(pError));
515
+ }
516
+ );
517
+
518
+ suiteTeardown
519
+ (
520
+ (fDone) =>
521
+ {
522
+ let tmpDb = _Fable.MeadowMongoDBProvider.pool;
523
+ tmpDb.collection('RawTestAnimal').drop()
524
+ .catch(() => {})
525
+ .then(() =>
526
+ {
527
+ _Fable.MeadowMongoDBProvider.client.close();
528
+ return fDone();
529
+ })
530
+ .catch(() =>
531
+ {
532
+ _Fable.MeadowMongoDBProvider.client.close();
533
+ return fDone();
534
+ });
535
+ }
536
+ );
537
+
538
+ test
539
+ (
540
+ 'insert and find documents',
541
+ (fDone) =>
542
+ {
543
+ let tmpCollection = _Fable.MeadowMongoDBProvider.pool.collection('RawTestAnimal');
544
+
545
+ tmpCollection.insertMany([
546
+ { IDAnimal: 1, Name: 'Fido', Type: 'Dog' },
547
+ { IDAnimal: 2, Name: 'Whiskers', Type: 'Cat' },
548
+ { IDAnimal: 3, Name: 'Polly', Type: 'Parrot' }
549
+ ])
550
+ .then(() =>
551
+ {
552
+ return tmpCollection.find({}).sort({ IDAnimal: 1 }).toArray();
553
+ })
554
+ .then((pDocs) =>
555
+ {
556
+ Expect(pDocs).to.be.an('array');
557
+ Expect(pDocs.length).to.equal(3);
558
+ Expect(pDocs[0].Name).to.equal('Fido');
559
+ Expect(pDocs[1].Name).to.equal('Whiskers');
560
+ Expect(pDocs[2].Name).to.equal('Polly');
561
+ return fDone();
562
+ })
563
+ .catch((pError) => fDone(pError));
564
+ }
565
+ );
566
+ test
567
+ (
568
+ 'update a document',
569
+ (fDone) =>
570
+ {
571
+ let tmpCollection = _Fable.MeadowMongoDBProvider.pool.collection('RawTestAnimal');
572
+
573
+ tmpCollection.updateOne(
574
+ { IDAnimal: 1 },
575
+ { $set: { Name: 'Rex' } }
576
+ )
577
+ .then((pResult) =>
578
+ {
579
+ Expect(pResult.modifiedCount).to.equal(1);
580
+ return tmpCollection.findOne({ IDAnimal: 1 });
581
+ })
582
+ .then((pDoc) =>
583
+ {
584
+ Expect(pDoc.Name).to.equal('Rex');
585
+ return fDone();
586
+ })
587
+ .catch((pError) => fDone(pError));
588
+ }
589
+ );
590
+ test
591
+ (
592
+ 'delete a document',
593
+ (fDone) =>
594
+ {
595
+ let tmpCollection = _Fable.MeadowMongoDBProvider.pool.collection('RawTestAnimal');
596
+
597
+ tmpCollection.deleteOne({ IDAnimal: 3 })
598
+ .then((pResult) =>
599
+ {
600
+ Expect(pResult.deletedCount).to.equal(1);
601
+ return tmpCollection.countDocuments();
602
+ })
603
+ .then((pCount) =>
604
+ {
605
+ Expect(pCount).to.equal(2);
606
+ return fDone();
607
+ })
608
+ .catch((pError) => fDone(pError));
609
+ }
610
+ );
611
+ test
612
+ (
613
+ 'count documents with filter',
614
+ (fDone) =>
615
+ {
616
+ let tmpCollection = _Fable.MeadowMongoDBProvider.pool.collection('RawTestAnimal');
617
+
618
+ tmpCollection.countDocuments({ Type: 'Dog' })
619
+ .then((pCount) =>
620
+ {
621
+ Expect(pCount).to.equal(1);
622
+ return fDone();
623
+ })
624
+ .catch((pError) => fDone(pError));
625
+ }
626
+ );
627
+ }
628
+ );
183
629
  }
184
630
  );