diff --git a/lib/mongodb.js b/lib/mongodb.js index e81516e69..c0036cb77 100644 --- a/lib/mongodb.js +++ b/lib/mongodb.js @@ -332,13 +332,11 @@ MongoDB.prototype.connect = function(callback) { if (callback) callback(err); } - const urlObj = new URL(self.settings.url); - - if ((urlObj.pathname === '' || - urlObj.pathname.split('/')[1] === '') && - typeof self.settings.database === 'string') { - urlObj.pathname = self.settings.database; - self.settings.url = urlObj.toString(); + // This is special processing if database is not part of url, but is in settings + if (self.settings.url && self.settings.database) { + if (self.settings.url.indexOf('/' + self.settings.database) === -1) { + self.settings.url = processMongoDBURL(self.settings.database, self.settings.url); + } } new mongodb.MongoClient(self.settings.url, validOptions).connect(function( @@ -2118,6 +2116,105 @@ MongoDB.prototype.rollback = function(tx, cb) { }); }; +exports.processMongoDBURL = processMongoDBURL; +/** + * This method parses a Mongo connection url string and refers the formats + * specified at: https://www.mongodb.com/docs/manual/reference/connection-string/. + * Since there are cases where database is not specified in the url, but as a settings property, + * the code has to reflect that in the url otherwise the MongoDB driver defaults to 'admin' db. + * @param {string} settingsDatabase - the database that will be added if url doesn't have a db specified + * @param {string} mongoUrl - the url to be processed for database manipulation + */ +function processMongoDBURL(settingsDatabase, mongoUrl) { + // Reference: https://www.mongodb.com/docs/manual/reference/connection-string/ + // Standard format::: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] + // DNS SeedList format::: mongodb+srv://server.example.com/?connectTimeoutMS=300000&authSource=aDifferentAuthDB + // Actual replicaset example::: mongodb://mongodb1.example.com:27317,mongodb2.example.com:27017/?connectTimeoutMS=300000&replicaSet=mySet&authSource=aDifferentAuthDB + + if (mongoUrl) { + // 1. Know the protocol + let baseUrl = ''; + if (mongoUrl.startsWith('mongodb:')) + baseUrl = 'mongodb://'; + else if (mongoUrl.startsWith('mongodb+srv:')) + baseUrl = 'mongodb+srv://'; + else if (mongoUrl.startsWith('loopback-connector-mongodb:')) + baseUrl = 'loopback-connector-mongodb://'; + else if (mongoUrl.startsWith('loopback-connector-mongodb+srv:')) + baseUrl = 'loopback-connector-mongodb+srv://'; + else + return mongoUrl; // Not a MongoURL that we can process + + let remainderUrl = mongoUrl.substring(baseUrl.length); + // 2. Check if userId/password is present + let uidPassword = ''; + if (remainderUrl.indexOf('@') !== -1) { + const parts = remainderUrl.split('@'); + uidPassword = parts[0]; + if (parts.length === 2) + remainderUrl = parts[1]; + else + remainderUrl = ''; + } + let hosts = ''; + let dbName = ''; + let options = ''; + let hostsArray = []; + // 3. Check if comma separated replicas are specified + if (remainderUrl.indexOf(',') !== -1) { + hostsArray = remainderUrl.split(','); + remainderUrl = hostsArray[hostsArray.length - 1]; + } + + // 4. Check if authDB is specified in the URL + const slashIndex = remainderUrl.indexOf('/'); + if ((slashIndex !== -1)) { + if (slashIndex !== (remainderUrl.length - 1)) { + const optionsIndex = remainderUrl.indexOf('?'); + if (optionsIndex !== -1) { + options = remainderUrl.substring(optionsIndex + 1); + dbName = remainderUrl.substring(slashIndex + 1, optionsIndex); + } else { + // No DB options specified + dbName = remainderUrl.substring(slashIndex + 1); + } + } + + if (hostsArray.length > 1) { + const newHosts = hostsArray; + newHosts.pop(); + newHosts.push(remainderUrl.substring(0, slashIndex)); + hosts = newHosts.join(','); + } else { + hosts = remainderUrl.substring(0, slashIndex); + } + } else { + // No database specified + if (hostsArray.length > 1) + hosts = hostsArray.join(','); + else + hosts = remainderUrl; + } + + // 5. Reconstruct url, but this time add database from settings if URL didn't have it + // The below code has an overlap with generateMongoDBURL() + let modifiedUrl = baseUrl; + + if (uidPassword) + modifiedUrl += uidPassword + '@'; + if (hosts) + modifiedUrl += hosts; + + modifiedUrl += '/' + (dbName ? dbName : settingsDatabase); + + if (options) + modifiedUrl += '?' + options; + + return modifiedUrl; + } + return mongoUrl; +} + function isInTransation(options) { const ops = {}; if (options && options.transaction && options.transaction.isInTransation) diff --git a/package-lock.json b/package-lock.json index 6adca159e..e3707c80e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "loopback-connector-mongodb", - "version": "6.1.0", + "version": "6.2.0", "license": "MIT", "dependencies": { "async": "^3.2.2", @@ -1292,10 +1292,16 @@ } }, "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1523,9 +1529,9 @@ } }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -2268,9 +2274,9 @@ } }, "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3246,9 +3252,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minimist-options": { @@ -3266,32 +3272,32 @@ } }, "node_modules/mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "4.2.1", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -3338,6 +3344,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/mocha/node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3345,9 +3363,9 @@ "dev": true }, "node_modules/mocha/node_modules/nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -4031,9 +4049,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" @@ -5233,9 +5251,9 @@ } }, "node_modules/workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "node_modules/wrap-ansi": { @@ -6390,9 +6408,9 @@ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -6582,9 +6600,9 @@ "dev": true }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -7133,9 +7151,9 @@ } }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7876,9 +7894,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minimist-options": { @@ -7893,32 +7911,32 @@ } }, "mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "4.2.1", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -7945,6 +7963,15 @@ "argparse": "^2.0.1" } }, + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7952,9 +7979,9 @@ "dev": true }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, "supports-color": { @@ -8479,9 +8506,9 @@ "dev": true }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -9431,9 +9458,9 @@ "dev": true }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { diff --git a/test/mongodb.test.js b/test/mongodb.test.js index 8e6402688..2a89d1118 100644 --- a/test/mongodb.test.js +++ b/test/mongodb.test.js @@ -449,6 +449,81 @@ describe('mongodb connector', function() { }); }); }); + + it("should honor the settings database if url doesn't have db", function(done) { + const cfg = JSON.parse(JSON.stringify(global.config)); + const testDb = cfg.database; + cfg.url = 'mongodb://' + cfg.host + ':' + cfg.port; + const ds = global.getDataSource(cfg); + ds.once('connected', function() { + const db = ds.connector.db; + let validationError = null; + try { + db.should.have.property('databaseName', testDb); // check the db name in the db instance + } catch (err) { + // async error + validationError = err; + } + ds.ping(function(err) { + if (err && !validationError) validationError = err; + ds.disconnect(function(disconnectError) { + if (disconnectError && !validationError) + validationError = disconnectError; + done(validationError); + }); + }); + }); + }); + + it('should honor the url database if both replicaset url and settings has db', function(done) { + const cfg = JSON.parse(JSON.stringify(global.config)); + const testDb = 'lb-ds-overriden-test-1'; + cfg.url = 'mongodb://' + cfg.host + ':' + cfg.port + ',' + cfg.host + ':' + cfg.port + '/' + testDb; + const ds = global.getDataSource(cfg); + ds.once('connected', function() { + const db = ds.connector.db; + let validationError = null; + try { + db.should.have.property('databaseName', testDb); // check the db name in the db instance + } catch (err) { + // async error + validationError = err; + } + ds.ping(function(err) { + if (err && !validationError) validationError = err; + ds.disconnect(function(disconnectError) { + if (disconnectError && !validationError) + validationError = disconnectError; + done(validationError); + }); + }); + }); + }); + + it("should honor the settings database if replicaset url doesn't have db has slash", function(done) { + const cfg = JSON.parse(JSON.stringify(global.config)); + const testDb = cfg.database; + cfg.url = 'mongodb://' + cfg.host + ':' + cfg.port + ',' + cfg.host + ':' + cfg.port + '/'; + const ds = global.getDataSource(cfg); + ds.once('connected', function() { + const db = ds.connector.db; + let validationError = null; + try { + db.should.have.property('databaseName', testDb); // check the db name in the db instance + } catch (err) { + // async error + validationError = err; + } + ds.ping(function(err) { + if (err && !validationError) validationError = err; + ds.disconnect(function(disconnectError) { + if (disconnectError && !validationError) + validationError = disconnectError; + done(validationError); + }); + }); + }); + }); }); describe('order filters', function() { @@ -1331,9 +1406,12 @@ describe('mongodb connector', function() { function(err, updatedusers) { should.exist(err); err.name.should.equal('MongoError'); - err.errmsg.should.equal( - 'The dollar ($) prefixed ' + - "field '$rename' in '$rename' is not valid for storage.", + err.errmsg.should.equalOneOf( + ("The dollar ($) prefixed field '$rename' in '$rename' is not " + + "allowed in the context of an update's replacement document. Consider using an " + + 'aggregation pipeline with $replaceWith.'), + ('The dollar ($) prefixed ' + + "field '$rename' in '$rename' is not valid for storage."), ); done(); }, @@ -1356,9 +1434,12 @@ describe('mongodb connector', function() { function(err, updatedusers) { should.exist(err); err.name.should.equal('MongoError'); - err.errmsg.should.equal( - 'The dollar ($) prefixed ' + - "field '$rename' in '$rename' is not valid for storage.", + err.errmsg.should.equalOneOf( + ("The dollar ($) prefixed field '$rename' in '$rename' is not " + + "allowed in the context of an update's replacement document. Consider using an " + + 'aggregation pipeline with $replaceWith.'), + ('The dollar ($) prefixed ' + + "field '$rename' in '$rename' is not valid for storage."), ); done(); }, @@ -1413,9 +1494,12 @@ describe('mongodb connector', function() { function(err, updatedusers) { should.exist(err); err.name.should.equal('MongoError'); - err.errmsg.should.equal( - 'The dollar ($) prefixed ' + - "field '$rename' in '$rename' is not valid for storage.", + err.errmsg.should.equalOneOf( + ("The dollar ($) prefixed field '$rename' in '$rename' is not " + + "allowed in the context of an update's replacement document. Consider using an " + + 'aggregation pipeline with $replaceWith.'), + ('The dollar ($) prefixed ' + + "field '$rename' in '$rename' is not valid for storage."), ); done(); }, @@ -3372,6 +3456,161 @@ describe('mongodb connector', function() { }); }); + describe('Test processMongoDBUrl function', function() { + const module = require('../'); + + context('should process mongodb url for default database manipulation', function() { + it('when no seetings db, but url has db, multiple hosts, user credentials and options', function() { + const url = 'mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = ''; + module.processMongoDBURL('', url).should.be.eql('mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when settings has db, url too has db, multiple hosts, user credentials and options', function() { + const url = 'mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when settings has db, url too has db, multiple hosts, user credentials and no options', function() { + const url = 'mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/testdb'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/testdb'); + }); + + it("when settings has db, url doesn't have db, multiple hosts, user credentials and no options", function() { + const url = 'mongodb://userid:password@db1.example.com:27017,db2.example.com:32667'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/mydb'); + }); + + it("when settings has db, url doesn't have db, has slash, multiple hosts, user creds and no options", function() { + const url = 'mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://userid:password@db1.example.com:27017,db2.example.com:32667/mydb'); + }); + + it('when no seetings db, but url has db, user id but no passwordm multiple hosts, and options', function() { + const url = 'mongodb://userid:@db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://userid:@db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when no seetings db, but url has db, no user credentials, multiple hosts, and options', function() { + const url = 'mongodb://db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://db1.example.com:27017,db2.example.com:32667/testdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when no seetings db, url too has no db, no user credentials, multiple hosts, and options', function() { + const url = 'mongodb://db1.example.com:27017,db2.example.com:32667/?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://db1.example.com:27017,db2.example.com:32667/?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when seetings has db, url has no db, no user credentials, multiple hosts, and options', function() { + const url = 'mongodb://db1.example.com:27017,db2.example.com:32667/?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://db1.example.com:27017,db2.example.com:32667/mydb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when no seetings db, lb4 url has no db, no user credentials, single host, and no options', function() { + const url = 'loopback-connector-mongodb://localhost:27017'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('loopback-connector-mongodb://localhost:27017/'); + }); + + it('when no seetings db, lb4 srv url has no db, no user credentials, single host, and no options', function() { + const url = 'loopback-connector-mongodb+srv://localhost:27017'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('loopback-connector-mongodb+srv://localhost:27017/'); + }); + + it('when it is not mongo url', function() { + const url = 'http://localhost:27017'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('http://localhost:27017'); + }); + + it('when no seetings db, url has no db, no user credentials, single host, and no options', function() { + const url = 'mongodb://localhost:27017'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/'); + }); + + it('when seetings has db, url has no db, no user credentials, single host, and no options', function() { + const url = 'mongodb://localhost:27017'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/mydb'); + }); + + it('when no seetings db, url has no db, a slash, no user credentials, single host, and no options', function() { + const url = 'mongodb://localhost:27017/'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/'); + }); + + it('when seetings has db, url has no db, a slash, no user credentials, single host, and no options', function() { + const url = 'mongodb://localhost:27017/'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/mydb'); + }); + + it('when no seetings db, url has db, no user credentials, single host, and np options', function() { + const url = 'mongodb://localhost:27017/yourdb'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/yourdb'); + }); + + it('when seetings has db, url has db, no user credentials, single host, and no options', function() { + const url = 'mongodb://localhost:27017/yourdb'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/yourdb'); + }); + + it('when no seetings db, url has db, no user credentials, single host, and options', function() { + const url = 'mongodb://localhost:27017/yourdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/yourdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when seetings has db, url has db, no user credentials, single host, and options', function() { + const url = 'mongodb://localhost:27017/yourdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/yourdb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when seetings has db, url has no db, no user credentials, single host, and options', function() { + const url = 'mongodb://localhost:27017/?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb://localhost:27017/mydb?authSource=admin&replicaSet=replset&readPreference=primary&ssl=true'); + }); + + it('when seetings has no db, DNSSeedList url has no db, no user creds, single host, and no options', function() { + const url = 'mongodb+srv://server.example.com/'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb+srv://server.example.com/'); + }); + + it('when seetings has db, DNSSeedList url has no db, no user creds, single host, and no options', function() { + const url = 'mongodb+srv://server.example.com/'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb+srv://server.example.com/mydb'); + }); + + it('when seetings has no db, DNSSeedList url has no db, no user creds, single host, and options', function() { + const url = 'mongodb+srv://server.example.com/?connectTimeoutMS=300000&authSource=aDifferentAuthDB'; + const database = ''; + module.processMongoDBURL(database, url).should.be.eql('mongodb+srv://server.example.com/?connectTimeoutMS=300000&authSource=aDifferentAuthDB'); + }); + + it('when seetings has db, DNSSeedList url has no db, no user creds, single host, and optionsß', function() { + const url = 'mongodb+srv://server.example.com/?connectTimeoutMS=300000&authSource=aDifferentAuthDB'; + const database = 'mydb'; + module.processMongoDBURL(database, url).should.be.eql('mongodb+srv://server.example.com/mydb?connectTimeoutMS=300000&authSource=aDifferentAuthDB'); + }); + }); + }); context('fieldsArrayToObj', function() { const fieldsArrayToObj = require('../').fieldsArrayToObj; it('should export the fieldsArrayToObj function', function() {