From 79e2e83692db0aeb84bac2ba8afdd5933ba95df8 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 15 Dec 2021 22:09:07 +0100 Subject: [PATCH 01/15] refactor: switch to new data service and connection model VSCODE-312 --- .vscode/settings.json | 8 +- package-lock.json | 1814 +++++------------ package.json | 16 +- src/commands/index.ts | 1 - src/commands/launchMongoShell.ts | 5 +- src/connectionController.ts | 514 ++--- src/editors/collectionDocumentsProvider.ts | 15 +- src/editors/mongoDBDocumentService.ts | 36 +- src/editors/playgroundController.ts | 102 +- src/explorer/collectionTreeItem.ts | 82 +- src/explorer/connectionTreeItem.ts | 64 +- src/explorer/databaseTreeItem.ts | 62 +- src/explorer/documentListTreeItem.ts | 41 +- src/explorer/explorerTreeController.ts | 28 +- src/explorer/indexListTreeItem.ts | 71 +- src/explorer/schemaTreeItem.ts | 38 +- src/language/README.md | 17 - src/language/languageServerController.ts | 48 +- src/language/mongoDBService.ts | 8 +- src/mdbExtensionController.ts | 28 +- src/storage/storageController.ts | 205 +- src/telemetry/connectionTelemetry.ts | 87 +- src/telemetry/telemetryService.ts | 27 +- src/test/fixture/.vscode/settings.json | 6 - .../suite/commands/launchMongoShell.test.ts | 174 +- src/test/suite/connectionController.test.ts | 173 +- src/test/suite/dbTestHelper.ts | 42 +- .../editors/activeDBCodeLensProvider.test.ts | 14 +- .../suite/editors/codeActionProvider.test.ts | 9 +- .../collectionDocumentsProvider.test.ts | 36 +- .../editors/playgroundController.test.ts | 52 +- .../suite/explorer/collectionTreeItem.test.ts | 9 +- .../suite/explorer/connectionTreeItem.test.ts | 192 +- .../suite/explorer/databaseTreeItem.test.ts | 15 +- .../suite/explorer/explorerController.test.ts | 11 +- src/test/suite/explorer/fieldTreeItem.test.ts | 18 +- .../suite/explorer/indexListTreeItem.test.ts | 31 +- .../suite/explorer/schemaTreeItem.test.ts | 23 +- .../language/languageServerController.test.ts | 12 +- src/test/suite/mdbExtensionController.test.ts | 82 +- .../suite/storage/storageController.test.ts | 144 +- src/test/suite/stubs.ts | 18 +- .../telemetry/connectionTelemetry.test.ts | 105 +- .../suite/telemetry/telemetryService.test.ts | 60 +- .../suite/utils/connection-secrets.test.ts | 167 ++ .../suite/views/webviewController.test.ts | 27 +- src/types/connectionModelType.ts | 28 - src/types/connectionOptionsType.ts | 22 - src/types/dataServiceType.ts | 48 - src/utils/connectionSecrets.ts | 120 ++ src/views/webviewController.ts | 16 +- 51 files changed, 1809 insertions(+), 3162 deletions(-) create mode 100644 src/test/suite/utils/connection-secrets.test.ts delete mode 100644 src/types/connectionModelType.ts delete mode 100644 src/types/connectionOptionsType.ts delete mode 100644 src/types/dataServiceType.ts create mode 100644 src/utils/connectionSecrets.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index a75ff94f6..14311786c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,11 +17,5 @@ "out": true }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off", - // Language server logging for - // https://github.com/Microsoft/language-server-protocol-inspector - "mongodbLanguageServer.trace.server": { - "format": "json", - "verbosity": "verbose" - } + "typescript.tsc.autoDetect": "off" } diff --git a/package-lock.json b/package-lock.json index 87d5f41f1..0de9a518d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,12 +28,13 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.6.0", + "lodash": "^4.17.21", "micromatch": "^4.0.4", "mongodb": "^4.2.0", "mongodb-cloud-info": "^1.1.3", - "mongodb-connection-model": "^21.5.4", - "mongodb-data-service": "^21.5.4", - "mongodb-js-errors": "^0.5.0", + "mongodb-connection-model": "^21.9.0", + "mongodb-connection-string-url": "^1.1.0", + "mongodb-data-service": "^21.13.0", "mongodb-ns": "^2.2.0", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", @@ -45,8 +46,7 @@ "uuid": "^8.3.2", "vscode-languageclient": "^7.0.0", "vscode-languageserver": "^7.0.0", - "vscode-languageserver-textdocument": "^1.0.2", - "ws": "^7.5.5" + "vscode-languageserver-textdocument": "^1.0.2" }, "devDependencies": { "@types/analytics-node": "^3.1.7", @@ -59,6 +59,7 @@ "@types/enzyme": "^3.10.10", "@types/glob": "^7.2.0", "@types/jest": "^26.0.24", + "@types/lodash": "^4.14.178", "@types/mocha": "^8.2.3", "@types/node": "^14.17.33", "@types/react": "^17.0.35", @@ -66,7 +67,6 @@ "@types/sinon": "^9.0.11", "@types/uuid": "^8.3.3", "@types/vscode": "^1.58.1", - "@types/ws": "^7.4.7", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^9.8.8", @@ -1546,10 +1546,40 @@ "@leafygreen-ui/palette": "^3.2.2" } }, + "node_modules/@mongodb-js/compass-logging": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.4.0.tgz", + "integrity": "sha512-7zahgRFbD9bGM595zIHQc5wOOwQUrf+V38LL+rS71/LZjFrWKsGBW+u0Hmxe7XnwM04AJoZilAcBhSxWwhPllQ==", + "dependencies": { + "debug": "4.3.0", + "is-electron-renderer": "^2.0.1", + "mongodb-log-writer": "^1.1.2" + }, + "peerDependencies": { + "hadron-ipc": "*" + } + }, + "node_modules/@mongodb-js/compass-logging/node_modules/debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@mongodb-js/ssh-tunnel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/ssh-tunnel/-/ssh-tunnel-1.2.0.tgz", - "integrity": "sha512-tG8CVPInP3TKUeaBFrNugQ14l5GwC4mIMuuX14aZmSCZ2olnBsPFreD5+CtMywQyUJqGkZWJDbAb6/grrn8vQw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/ssh-tunnel/-/ssh-tunnel-1.2.1.tgz", + "integrity": "sha512-6RvNV2RR7o/82FuxTfpHWX6Vi/JPLxNM3n9GF1MYPZVlw3zDP3bhdWAvbLDK9OnvwzUM+N80VjIuwqv/GoKs5Q==", "dependencies": { "ssh2": "^0.8.9" } @@ -2482,6 +2512,12 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -2628,15 +2664,6 @@ "@types/webidl-conversions": "*" } }, - "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", @@ -9762,6 +9789,17 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-proxy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", @@ -10046,17 +10084,34 @@ "optional": true }, "node_modules/hadron-ipc": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.3.1.tgz", - "integrity": "sha512-mVGNtMsoLULAnpA3JHXRBaMEnq3zOQT1f2Nu7qmGlM8EoWVQMaHigSmpP2H+YJlKVv5Y2P7oe8hrcs3oNipESA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.6.0.tgz", + "integrity": "sha512-+lHfkoPvnuCdXrTvmDrweZlrWPDis3aofif0P/2gLibpKZvRr0N3tD9YeOg/MYcfOJ1n91nIKWboXFHii5eKDA==", "dependencies": { - "debug": "^4.1.1", - "is-electron-renderer": "^2.0.0", + "debug": "4.3.0", + "is-electron-renderer": "^2.0.1", "is-promise": "^2.1.0", "lodash.forin": "^4.4.0", "lodash.isplainobject": "^4.0.6" } }, + "node_modules/hadron-ipc/node_modules/debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -14000,20 +14055,15 @@ "integrity": "sha1-XqKa/CUppCWThDfWlVtxTOapeR8=" }, "node_modules/keytar": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz", - "integrity": "sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz", + "integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==", "hasInstallScript": true, "dependencies": { - "nan": "2.14.1", - "prebuild-install": "5.3.3" + "node-addon-api": "^3.0.0", + "prebuild-install": "^6.0.0" } }, - "node_modules/keytar/node_modules/nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" - }, "node_modules/keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", @@ -14286,204 +14336,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash._arraycopy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", - "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=" - }, - "node_modules/lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" - }, - "node_modules/lodash._arraymap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz", - "integrity": "sha1-Go/Q9MDfS2HeoHbXF83Jfwo8PmY=" - }, - "node_modules/lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash._baseclone": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", - "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", - "dependencies": { - "lodash._arraycopy": "^3.0.0", - "lodash._arrayeach": "^3.0.0", - "lodash._baseassign": "^3.0.0", - "lodash._basefor": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "node_modules/lodash._basedifference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz", - "integrity": "sha1-8sIEKWwqeOArOJCBtu3KyTPPYpw=", - "dependencies": { - "lodash._baseindexof": "^3.0.0", - "lodash._cacheindexof": "^3.0.0", - "lodash._createcache": "^3.0.0" - } - }, - "node_modules/lodash._baseeach": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", - "dependencies": { - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash._baseflatten": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", - "integrity": "sha1-B3D/gBMa9uNPO1EXlqe6UhTmX/c=", - "dependencies": { - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=" - }, - "node_modules/lodash._baseget": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/lodash._baseget/-/lodash._baseget-3.7.2.tgz", - "integrity": "sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=" - }, - "node_modules/lodash._baseindexof": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", - "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=" - }, - "node_modules/lodash._baseisequal": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", - "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", - "dependencies": { - "lodash.isarray": "^3.0.0", - "lodash.istypedarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash._baseslice": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._baseslice/-/lodash._baseslice-3.0.3.tgz", - "integrity": "sha1-qkrj3FPu1TsI3i4zYrOTV7XIfXU=" - }, - "node_modules/lodash._baseuniq": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-3.0.3.tgz", - "integrity": "sha1-ISP6DbLWnCjVvrHB821hUip0AjQ=", - "dependencies": { - "lodash._baseindexof": "^3.0.0", - "lodash._cacheindexof": "^3.0.0", - "lodash._createcache": "^3.0.0" - } - }, - "node_modules/lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" - }, - "node_modules/lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "node_modules/lodash._cacheindexof": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", - "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=" - }, - "node_modules/lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", - "dependencies": { - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "node_modules/lodash._createcache": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", - "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", - "dependencies": { - "lodash._getnative": "^3.0.0" - } - }, - "node_modules/lodash._createwrapper": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-3.2.0.tgz", - "integrity": "sha1-30U+ZkFjIXuJWkVAZa8cR6DqPE0=", - "dependencies": { - "lodash._root": "^3.0.0" - } - }, - "node_modules/lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "node_modules/lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "node_modules/lodash._pickbyarray": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz", - "integrity": "sha1-H4mNlgfrVgsOFnOEt3x8bRCKpMU=" - }, - "node_modules/lodash._pickbycallback": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz", - "integrity": "sha1-/2G5oBens699MObFPeKK+hm4dQo=", - "dependencies": { - "lodash._basefor": "^3.0.0", - "lodash.keysin": "^3.0.0" - } - }, "node_modules/lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "node_modules/lodash._replaceholders": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._replaceholders/-/lodash._replaceholders-3.0.0.tgz", - "integrity": "sha1-iru3EmxDH37XRPe6rznwi8m9nVg=" - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "node_modules/lodash._topath": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/lodash._topath/-/lodash._topath-3.8.1.tgz", - "integrity": "sha1-PsXiYGAU9MuX91X+aRTt2L/ADqw=", - "dependencies": { - "lodash.isarray": "^3.0.0" - } - }, "node_modules/lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", @@ -14495,21 +14353,6 @@ "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", "dev": true }, - "node_modules/lodash.before": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.before/-/lodash.before-3.0.3.tgz", - "integrity": "sha1-aHeDZg6hqPrE8B4ndN4PUgaxYc4=" - }, - "node_modules/lodash.bind": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-3.1.0.tgz", - "integrity": "sha1-+V9IY419i7tYVPkIJmUnmZ+/pLs=", - "dependencies": { - "lodash._createwrapper": "^3.0.0", - "lodash._replaceholders": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -14522,16 +14365,6 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "optional": true }, - "node_modules/lodash.clone": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz", - "integrity": "sha1-hGiMc9MrWpDKJWFpY/GJJSqZcEM=", - "dependencies": { - "lodash._baseclone": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -14544,19 +14377,6 @@ "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", "dev": true }, - "node_modules/lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dependencies": { - "lodash._root": "^3.0.0" - } - }, - "node_modules/lodash.every": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.every/-/lodash.every-4.6.0.tgz", - "integrity": "sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc=" - }, "node_modules/lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", @@ -14578,108 +14398,24 @@ "node_modules/lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true }, "node_modules/lodash.forin": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.forin/-/lodash.forin-4.4.0.tgz", "integrity": "sha1-XT8grlZAEfvog4H32YlJyclRlzE=" }, - "node_modules/lodash.forown": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-3.0.2.tgz", - "integrity": "sha1-kOKc33iTBI3QUpozPjBIlrC6wo4=", - "dependencies": { - "lodash._basefor": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, - "node_modules/lodash.has": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-3.2.1.tgz", - "integrity": "sha1-O3VHHY0gg9itnYoV4yHseEJJWOY=", - "dependencies": { - "lodash._baseget": "^3.0.0", - "lodash._baseslice": "^3.0.0", - "lodash._topath": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/lodash.includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-3.1.3.tgz", - "integrity": "sha1-wyLQScJ4krKaAbmVk25ZU4HrvBc=", - "dependencies": { - "lodash._baseindexof": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.isstring": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash.includes/node_modules/lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "node_modules/lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "node_modules/lodash.isdate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isdate/-/lodash.isdate-3.0.3.tgz", - "integrity": "sha1-vWub5kXmrmcWta0Aegj70avQsCA=" - }, - "node_modules/lodash.isempty": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-3.0.4.tgz", - "integrity": "sha1-O8Vb+BHW0jLV4zVM4shkR6IPGtU=", - "dependencies": { - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.isfunction": "^3.0.0", - "lodash.isstring": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash.isempty/node_modules/lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - }, - "node_modules/lodash.isequal": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-3.0.4.tgz", - "integrity": "sha1-HDXrO27wzR/1F0Pj6jz3/f/ay2Q=", - "dependencies": { - "lodash._baseisequal": "^3.0.0", - "lodash._bindcallback": "^3.0.0" - } - }, "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" - }, - "node_modules/lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "optional": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", @@ -14691,30 +14427,6 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "node_modules/lodash.istypedarray": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", - "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" - }, - "node_modules/lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dependencies": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/lodash.keysin": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", - "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=", - "dependencies": { - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "node_modules/lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -14727,29 +14439,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.omit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz", - "integrity": "sha1-iX/jguZBPZrJfGH3jtHgV6AK+fM=", - "dependencies": { - "lodash._arraymap": "^3.0.0", - "lodash._basedifference": "^3.0.0", - "lodash._baseflatten": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash._pickbyarray": "^3.0.0", - "lodash._pickbycallback": "^3.0.0", - "lodash.keysin": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "node_modules/lodash.once": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-3.0.1.tgz", - "integrity": "sha1-GBN+yW+jzOhoIm89q7nqcNC8Eo8=", - "dependencies": { - "lodash.before": "^3.0.0" - } - }, "node_modules/lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -14786,23 +14475,6 @@ "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", "dev": true }, - "node_modules/lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, - "node_modules/lodash.result": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.result/-/lodash.result-3.1.2.tgz", - "integrity": "sha1-IRcohOTaJEk5JHK1CGHFjD8a4nk=", - "dependencies": { - "lodash._baseget": "^3.0.0", - "lodash._baseslice": "^3.0.0", - "lodash._topath": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.isfunction": "^3.0.0" - } - }, "node_modules/lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -14840,24 +14512,6 @@ "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=", "optional": true }, - "node_modules/lodash.union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-3.1.0.tgz", - "integrity": "sha1-pKMGb8Fdan+BUczpvf5j3Of1vP8=", - "dependencies": { - "lodash._baseflatten": "^3.0.0", - "lodash._baseuniq": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "node_modules/lodash.uniqueid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.uniqueid/-/lodash.uniqueid-3.2.0.tgz", - "integrity": "sha1-WUFvE0EDziU9S0qoGCcr4/u8u9s=", - "dependencies": { - "lodash._root": "^3.0.0" - } - }, "node_modules/log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -16367,24 +16021,81 @@ } }, "node_modules/mongodb-connection-model": { - "version": "21.5.4", - "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.5.4.tgz", - "integrity": "sha512-X6tk81IStBSe0omu7kN5ao5qUzv6Gk7h6wMSorOf1fuaFDk3e3KDNdYkJ9LAesTcmCslAaY2DqlGwVNAELWPag==", + "version": "21.9.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.9.0.tgz", + "integrity": "sha512-V8rgd6ssauIbQGVvmdszGaLda8zRd3nVMJzDExniLVQQAZbCWeDvdcp4pIVWqJtD3dAMZMn/jyYJmYC4wqZpRg==", "dependencies": { - "@mongodb-js/ssh-tunnel": "^1.2.0", - "ampersand-model": "^8.0.0", + "@mongodb-js/ssh-tunnel": "^1.2.1", + "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", - "debug": "^4.1.1", + "debug": "4.3.0", "lodash": "^4.17.15", - "mongodb-connection-string-url": "^1.0.0", + "mongodb-connection-string-url": "^2.1.0", "mongodb3": "npm:mongodb@^3.6.3", "raf": "^3.4.1", "resolve-mongodb-srv": "^1.1.0", "ssh2": "^0.8.7", - "storage-mixin": "^4.5.1" + "storage-mixin": "^4.8.0" }, "peerDependencies": { - "mongodb": "4.x" + "mongodb": "^4.1.0" + } + }, + "node_modules/mongodb-connection-model/node_modules/debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-model/node_modules/mongodb-connection-string-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", + "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-model/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-model/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-model/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/mongodb-connection-string-url": { @@ -16420,25 +16131,28 @@ } }, "node_modules/mongodb-data-service": { - "version": "21.5.4", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.5.4.tgz", - "integrity": "sha512-W0frBajZJbTNBscUX+1hmUWfpjTgGDJy7s4wL09psnNdw7K+maoWKakCL5HrNmLVGsGIIwklYmTz5D4P3iPZdw==", + "version": "21.13.0", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.13.0.tgz", + "integrity": "sha512-dRZJ6+hvFJMeAYdayhX2x/BN2q9EjOaXEcWCRS2BND9WTtAum3QZvrEqROc2ViGldaiJfLcYjZorocSjPKwylw==", "dependencies": { + "@mongodb-js/compass-logging": "^0.4.0", + "@mongodb-js/ssh-tunnel": "^1.2.1", "async": "^3.2.0", - "debug": "^4.2.0", + "debug": "4.3.0", + "get-port": "^5.1.1", "lodash": "^4.17.20", "mongodb-build-info": "^1.1.1", - "mongodb-index-model": "^3.3.2", - "mongodb-js-errors": "^0.5.0", + "mongodb-connection-string-url": "^2.1.0", + "mongodb-index-model": "^3.6.0", "mongodb-ns": "^2.2.0", - "mongodb-security": "^1.2.1", - "mongodb-url": "^3.0.3" + "resolve-mongodb-srv": "^1.1.0", + "uuid": "^8.3.2" }, "engines": { - "node": ">=12" + "node": ">=14.17.5" }, "peerDependencies": { - "mongodb": "4.x", + "mongodb": "^4.1.0", "mongodb-connection-model": "*" } }, @@ -16447,6 +16161,63 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, + "node_modules/mongodb-data-service/node_modules/debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mongodb-data-service/node_modules/mongodb-connection-string-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", + "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-data-service/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-data-service/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-data-service/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/mongodb-dbpath": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/mongodb-dbpath/-/mongodb-dbpath-0.0.1.tgz", @@ -16549,12 +16320,12 @@ } }, "node_modules/mongodb-index-model": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.3.2.tgz", - "integrity": "sha512-0/dxalooEGpDe1AUx0sra1hfyQ0T0/gRlChiUMANcLGxcx8+vmC+4VIu1YszbfOa5benIoRIu1TtakFYdLQH3g==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.6.0.tgz", + "integrity": "sha512-tOx0joqNPOKN9mEsSOk2hUQ018uflL/gD5oqCF6LhwQMx7hVG3o3VBGZRTLpObEICH1KqoNW9wmU+P2PLj4xvQ==", "dependencies": { - "ampersand-collection": "^1.6.1", - "ampersand-model": "^6.0.2", + "ampersand-collection": "^2.0.1", + "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "async": "^3.2.0", "lodash": "^4.17.15", @@ -16562,144 +16333,11 @@ "mongodb-ns": "^2.2.0" } }, - "node_modules/mongodb-index-model/node_modules/ampersand-class-extend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ampersand-class-extend/-/ampersand-class-extend-1.0.2.tgz", - "integrity": "sha1-jjqxJdCb976UO1DpjM5G/1F8vpE=", - "dependencies": { - "lodash.assign": "^3.0.0" - } - }, - "node_modules/mongodb-index-model/node_modules/ampersand-collection": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ampersand-collection/-/ampersand-collection-1.6.1.tgz", - "integrity": "sha1-Qw9lTPcC4Z0drGzSEXVdUDFyoMk=", - "dependencies": { - "ampersand-class-extend": "^1.0.0", - "ampersand-events": "^1.0.1", - "ampersand-version": "^1.0.2", - "lodash.assign": "^3.0.0", - "lodash.bind": "^3.1.0", - "lodash.isarray": "^3.0.1" - } - }, - "node_modules/mongodb-index-model/node_modules/ampersand-events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ampersand-events/-/ampersand-events-1.1.1.tgz", - "integrity": "sha1-pQDpjMrorZKqHZV0TFM9nBChZTA=", - "dependencies": { - "ampersand-version": "^1.0.2", - "lodash.assign": "^3.0.0", - "lodash.bind": "^3.1.0", - "lodash.foreach": "^3.0.2", - "lodash.isempty": "^3.0.1", - "lodash.keys": "^3.0.5", - "lodash.once": "^3.0.0", - "lodash.uniqueid": "^3.0.0" - } - }, - "node_modules/mongodb-index-model/node_modules/ampersand-model": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ampersand-model/-/ampersand-model-6.0.3.tgz", - "integrity": "sha1-x+zlpnFVFT4TxyR1rPF4szhKVXw=", - "dependencies": { - "ampersand-state": "^4.6.0", - "ampersand-sync": "^4.0.3", - "ampersand-version": "^1.0.2", - "lodash.assign": "^3.2.0", - "lodash.clone": "^3.0.3", - "lodash.isobject": "^3.0.2", - "lodash.result": "^3.1.2" - } - }, - "node_modules/mongodb-index-model/node_modules/ampersand-state": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/ampersand-state/-/ampersand-state-4.8.2.tgz", - "integrity": "sha1-qPrjZakZ54S8icbqk6PzXvcvLDk=", - "dependencies": { - "ampersand-events": "^1.1.1", - "ampersand-version": "^1.0.0", - "array-next": "~0.0.1", - "key-tree-store": "^1.3.0", - "lodash.assign": "^3.2.0", - "lodash.bind": "^3.1.0", - "lodash.escape": "^3.0.0", - "lodash.forown": "^3.0.2", - "lodash.has": "^3.0.0", - "lodash.includes": "^3.1.3", - "lodash.isarray": "^3.0.4", - "lodash.isdate": "^3.0.1", - "lodash.isequal": "^3.0.1", - "lodash.isfunction": "^3.0.6", - "lodash.isobject": "^3.0.1", - "lodash.isstring": "^3.0.1", - "lodash.omit": "^3.1.0", - "lodash.result": "^3.0.0", - "lodash.union": "^3.1.0", - "lodash.uniqueid": "^3.0.0" - } - }, - "node_modules/mongodb-index-model/node_modules/ampersand-sync": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ampersand-sync/-/ampersand-sync-4.0.3.tgz", - "integrity": "sha1-wlXWBYhZUxRyEJ+KULocCtnY1yU=", - "dependencies": { - "ampersand-version": "^1.0.0", - "lodash.assign": "^3.0.0", - "lodash.defaults": "^3.1.0", - "lodash.includes": "^3.1.0", - "lodash.result": "^3.0.0", - "media-type": "0.3.0", - "qs": "^4.0.0", - "request": "^2.55.0", - "xhr": "^2.0.5" - } - }, "node_modules/mongodb-index-model/node_modules/async": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, - "node_modules/mongodb-index-model/node_modules/lodash.assign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", - "dependencies": { - "lodash._baseassign": "^3.0.0", - "lodash._createassigner": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/mongodb-index-model/node_modules/lodash.defaults": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", - "dependencies": { - "lodash.assign": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "node_modules/mongodb-index-model/node_modules/lodash.foreach": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", - "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", - "dependencies": { - "lodash._arrayeach": "^3.0.0", - "lodash._baseeach": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/mongodb-index-model/node_modules/lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - }, - "node_modules/mongodb-index-model/node_modules/qs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", - "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" - }, "node_modules/mongodb-js-errors": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/mongodb-js-errors/-/mongodb-js-errors-0.5.0.tgz", @@ -16709,6 +16347,14 @@ "debug": "^4.1.1" } }, + "node_modules/mongodb-log-writer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.2.tgz", + "integrity": "sha512-SkurMSeQDZw9o+gG6Gz38okEsV5890HBBHDSpTgHeWZukKvSLQIpMqRc4MJSWKvXr8B+Y7iByHebykcjhd/RBA==", + "dependencies": { + "bson": "^4.5.1" + } + }, "node_modules/mongodb-ns": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.2.0.tgz", @@ -16909,16 +16555,6 @@ } } }, - "node_modules/mongodb-security": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/mongodb-security/-/mongodb-security-1.2.1.tgz", - "integrity": "sha512-Jkc7xX7sq4O42r9qybtNeR/RjvYJIF62QI97zBccAcxz4IOuEtJ7rIImzmd6Idechx0l0LiRmjI4rjV84utn6w==", - "dependencies": { - "debug": "^4.1.1", - "lodash.every": "^4.6.0", - "lodash.foreach": "^4.5.0" - } - }, "node_modules/mongodb-tools": { "resolved": "git+ssh://git@github.com/mongodb-js/mongodb-tools.git#0d1a90f49796c41f6d47c7c7999fe384014a16a0", "dev": true, @@ -16972,11 +16608,6 @@ "rimraf": "bin.js" } }, - "node_modules/mongodb-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/mongodb-url/-/mongodb-url-3.0.3.tgz", - "integrity": "sha512-ky8FX1uz1i7EpGY2hQgD9Zw4ISrFfFTbHJAXzis+i7lMJVEvHLb/WdzO0jg0i5hJnoekhQU45cvanIq9iHri1A==" - }, "node_modules/mongodb-version-list": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mongodb-version-list/-/mongodb-version-list-1.0.0.tgz", @@ -17670,8 +17301,7 @@ "node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "devOptional": true + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" }, "node_modules/node-fetch": { "version": "2.6.1", @@ -17836,7 +17466,8 @@ "node_modules/noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "optional": true }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -19540,25 +19171,23 @@ "dev": true }, "node_modules/prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "dependencies": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", + "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" + "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" @@ -19567,17 +19196,6 @@ "node": ">=6" } }, - "node_modules/prebuild-install/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -21456,17 +21074,17 @@ } }, "node_modules/storage-mixin": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.5.1.tgz", - "integrity": "sha512-7Muq1+on0jNad2QPRo18+UNARtS1P6WGpKp8r+9rfLon6wLFBcFW9FFwlh9xcNV4+B33I7XhEP08K+FRfYNFnA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.8.0.tgz", + "integrity": "sha512-qwpGc069ORwNJHMnvxM+xP7qLlXfhCvxkxb4vqVFNeM2UR1IyuRCW51bAahVmrrDGOAWShg7CY7Z7E7NK6bCGA==", "dependencies": { "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "ampersand-sync": "^5.1.0", - "async": "^3.1.0", - "debug": "^4.1.1", - "hadron-ipc": "^2.3.1", - "keytar": "^5.0.0", + "async": "^3.2.0", + "debug": "4.3.0", + "hadron-ipc": "^2.6.0", + "keytar": "^7.7.0", "localforage": "^1.7.3", "lodash": "^4.17.15", "rimraf": "^3.0.0", @@ -21479,6 +21097,23 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, + "node_modules/storage-mixin/node_modules/debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/storage-mixin/node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -23026,17 +22661,6 @@ "node": ">=10" } }, - "node_modules/vsce/node_modules/keytar": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz", - "integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^3.0.0", - "prebuild-install": "^6.0.0" - } - }, "node_modules/vsce/node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -23046,33 +22670,6 @@ "node": ">=6" } }, - "node_modules/vsce/node_modules/prebuild-install": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", - "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", - "dev": true, - "dependencies": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.21.0", - "npmlog": "^4.0.1", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^3.0.3", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/vsce/node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -24231,11 +23828,6 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" }, - "node_modules/which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, "node_modules/wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -24397,6 +23989,7 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -25847,10 +25440,30 @@ "@leafygreen-ui/palette": "^3.2.2" } }, + "@mongodb-js/compass-logging": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.4.0.tgz", + "integrity": "sha512-7zahgRFbD9bGM595zIHQc5wOOwQUrf+V38LL+rS71/LZjFrWKsGBW+u0Hmxe7XnwM04AJoZilAcBhSxWwhPllQ==", + "requires": { + "debug": "4.3.0", + "is-electron-renderer": "^2.0.1", + "mongodb-log-writer": "^1.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "requires": { + "ms": "2.1.2" + } + } + } + }, "@mongodb-js/ssh-tunnel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/ssh-tunnel/-/ssh-tunnel-1.2.0.tgz", - "integrity": "sha512-tG8CVPInP3TKUeaBFrNugQ14l5GwC4mIMuuX14aZmSCZ2olnBsPFreD5+CtMywQyUJqGkZWJDbAb6/grrn8vQw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/ssh-tunnel/-/ssh-tunnel-1.2.1.tgz", + "integrity": "sha512-6RvNV2RR7o/82FuxTfpHWX6Vi/JPLxNM3n9GF1MYPZVlw3zDP3bhdWAvbLDK9OnvwzUM+N80VjIuwqv/GoKs5Q==", "requires": { "ssh2": "^0.8.9" } @@ -26630,6 +26243,12 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -26776,15 +26395,6 @@ "@types/webidl-conversions": "*" } }, - "@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", @@ -32750,6 +32360,11 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==" + }, "get-proxy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", @@ -32979,15 +32594,25 @@ "optional": true }, "hadron-ipc": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.3.1.tgz", - "integrity": "sha512-mVGNtMsoLULAnpA3JHXRBaMEnq3zOQT1f2Nu7qmGlM8EoWVQMaHigSmpP2H+YJlKVv5Y2P7oe8hrcs3oNipESA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.6.0.tgz", + "integrity": "sha512-+lHfkoPvnuCdXrTvmDrweZlrWPDis3aofif0P/2gLibpKZvRr0N3tD9YeOg/MYcfOJ1n91nIKWboXFHii5eKDA==", "requires": { - "debug": "^4.1.1", - "is-electron-renderer": "^2.0.0", + "debug": "4.3.0", + "is-electron-renderer": "^2.0.1", "is-promise": "^2.1.0", "lodash.forin": "^4.4.0", "lodash.isplainobject": "^4.0.6" + }, + "dependencies": { + "debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "requires": { + "ms": "2.1.2" + } + } } }, "har-schema": { @@ -36284,19 +35909,12 @@ "integrity": "sha1-XqKa/CUppCWThDfWlVtxTOapeR8=" }, "keytar": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz", - "integrity": "sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz", + "integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==", "requires": { - "nan": "2.14.1", - "prebuild-install": "5.3.3" - }, - "dependencies": { - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" - } + "node-addon-api": "^3.0.0", + "prebuild-install": "^6.0.0" } }, "keyv": { @@ -36523,204 +36141,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash._arraycopy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", - "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=" - }, - "lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" - }, - "lodash._arraymap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz", - "integrity": "sha1-Go/Q9MDfS2HeoHbXF83Jfwo8PmY=" - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._baseclone": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", - "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", - "requires": { - "lodash._arraycopy": "^3.0.0", - "lodash._arrayeach": "^3.0.0", - "lodash._baseassign": "^3.0.0", - "lodash._basefor": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "lodash._basedifference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz", - "integrity": "sha1-8sIEKWwqeOArOJCBtu3KyTPPYpw=", - "requires": { - "lodash._baseindexof": "^3.0.0", - "lodash._cacheindexof": "^3.0.0", - "lodash._createcache": "^3.0.0" - } - }, - "lodash._baseeach": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", - "requires": { - "lodash.keys": "^3.0.0" - } - }, - "lodash._baseflatten": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", - "integrity": "sha1-B3D/gBMa9uNPO1EXlqe6UhTmX/c=", - "requires": { - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=" - }, - "lodash._baseget": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/lodash._baseget/-/lodash._baseget-3.7.2.tgz", - "integrity": "sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=" - }, - "lodash._baseindexof": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", - "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=" - }, - "lodash._baseisequal": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", - "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", - "requires": { - "lodash.isarray": "^3.0.0", - "lodash.istypedarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._baseslice": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._baseslice/-/lodash._baseslice-3.0.3.tgz", - "integrity": "sha1-qkrj3FPu1TsI3i4zYrOTV7XIfXU=" - }, - "lodash._baseuniq": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-3.0.3.tgz", - "integrity": "sha1-ISP6DbLWnCjVvrHB821hUip0AjQ=", - "requires": { - "lodash._baseindexof": "^3.0.0", - "lodash._cacheindexof": "^3.0.0", - "lodash._createcache": "^3.0.0" - } - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "lodash._cacheindexof": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", - "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=" - }, - "lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", - "requires": { - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "lodash._createcache": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", - "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", - "requires": { - "lodash._getnative": "^3.0.0" - } - }, - "lodash._createwrapper": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-3.2.0.tgz", - "integrity": "sha1-30U+ZkFjIXuJWkVAZa8cR6DqPE0=", - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "lodash._pickbyarray": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz", - "integrity": "sha1-H4mNlgfrVgsOFnOEt3x8bRCKpMU=" - }, - "lodash._pickbycallback": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz", - "integrity": "sha1-/2G5oBens699MObFPeKK+hm4dQo=", - "requires": { - "lodash._basefor": "^3.0.0", - "lodash.keysin": "^3.0.0" - } - }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._replaceholders": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._replaceholders/-/lodash._replaceholders-3.0.0.tgz", - "integrity": "sha1-iru3EmxDH37XRPe6rznwi8m9nVg=" - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "lodash._topath": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/lodash._topath/-/lodash._topath-3.8.1.tgz", - "integrity": "sha1-PsXiYGAU9MuX91X+aRTt2L/ADqw=", - "requires": { - "lodash.isarray": "^3.0.0" - } - }, "lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", @@ -36732,21 +36158,6 @@ "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", "dev": true }, - "lodash.before": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.before/-/lodash.before-3.0.3.tgz", - "integrity": "sha1-aHeDZg6hqPrE8B4ndN4PUgaxYc4=" - }, - "lodash.bind": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-3.1.0.tgz", - "integrity": "sha1-+V9IY419i7tYVPkIJmUnmZ+/pLs=", - "requires": { - "lodash._createwrapper": "^3.0.0", - "lodash._replaceholders": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -36759,16 +36170,6 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "optional": true }, - "lodash.clone": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz", - "integrity": "sha1-hGiMc9MrWpDKJWFpY/GJJSqZcEM=", - "requires": { - "lodash._baseclone": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -36781,19 +36182,6 @@ "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", "dev": true }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.every": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.every/-/lodash.every-4.6.0.tgz", - "integrity": "sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc=" - }, "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", @@ -36815,112 +36203,24 @@ "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true }, "lodash.forin": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.forin/-/lodash.forin-4.4.0.tgz", "integrity": "sha1-XT8grlZAEfvog4H32YlJyclRlzE=" }, - "lodash.forown": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-3.0.2.tgz", - "integrity": "sha1-kOKc33iTBI3QUpozPjBIlrC6wo4=", - "requires": { - "lodash._basefor": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, - "lodash.has": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-3.2.1.tgz", - "integrity": "sha1-O3VHHY0gg9itnYoV4yHseEJJWOY=", - "requires": { - "lodash._baseget": "^3.0.0", - "lodash._baseslice": "^3.0.0", - "lodash._topath": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-3.1.3.tgz", - "integrity": "sha1-wyLQScJ4krKaAbmVk25ZU4HrvBc=", - "requires": { - "lodash._baseindexof": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.isstring": "^3.0.0", - "lodash.keys": "^3.0.0" - }, - "dependencies": { - "lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - } - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.isdate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isdate/-/lodash.isdate-3.0.3.tgz", - "integrity": "sha1-vWub5kXmrmcWta0Aegj70avQsCA=" - }, - "lodash.isempty": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-3.0.4.tgz", - "integrity": "sha1-O8Vb+BHW0jLV4zVM4shkR6IPGtU=", - "requires": { - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.isfunction": "^3.0.0", - "lodash.isstring": "^3.0.0", - "lodash.keys": "^3.0.0" - }, - "dependencies": { - "lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - } - } - }, - "lodash.isequal": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-3.0.4.tgz", - "integrity": "sha1-HDXrO27wzR/1F0Pj6jz3/f/ay2Q=", - "requires": { - "lodash._baseisequal": "^3.0.0", - "lodash._bindcallback": "^3.0.0" - } - }, "lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" - }, - "lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "optional": true }, "lodash.isplainobject": { "version": "4.0.6", @@ -36932,30 +36232,6 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.istypedarray": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", - "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.keysin": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", - "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=", - "requires": { - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -36968,29 +36244,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.omit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz", - "integrity": "sha1-iX/jguZBPZrJfGH3jtHgV6AK+fM=", - "requires": { - "lodash._arraymap": "^3.0.0", - "lodash._basedifference": "^3.0.0", - "lodash._baseflatten": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash._pickbyarray": "^3.0.0", - "lodash._pickbycallback": "^3.0.0", - "lodash.keysin": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "lodash.once": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-3.0.1.tgz", - "integrity": "sha1-GBN+yW+jzOhoIm89q7nqcNC8Eo8=", - "requires": { - "lodash.before": "^3.0.0" - } - }, "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -37027,23 +36280,6 @@ "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", "dev": true }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, - "lodash.result": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.result/-/lodash.result-3.1.2.tgz", - "integrity": "sha1-IRcohOTaJEk5JHK1CGHFjD8a4nk=", - "requires": { - "lodash._baseget": "^3.0.0", - "lodash._baseslice": "^3.0.0", - "lodash._topath": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.isfunction": "^3.0.0" - } - }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -37081,24 +36317,6 @@ "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=", "optional": true }, - "lodash.union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-3.1.0.tgz", - "integrity": "sha1-pKMGb8Fdan+BUczpvf5j3Of1vP8=", - "requires": { - "lodash._baseflatten": "^3.0.0", - "lodash._baseuniq": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "lodash.uniqueid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.uniqueid/-/lodash.uniqueid-3.2.0.tgz", - "integrity": "sha1-WUFvE0EDziU9S0qoGCcr4/u8u9s=", - "requires": { - "lodash._root": "^3.0.0" - } - }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -38355,21 +37573,62 @@ } }, "mongodb-connection-model": { - "version": "21.5.4", - "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.5.4.tgz", - "integrity": "sha512-X6tk81IStBSe0omu7kN5ao5qUzv6Gk7h6wMSorOf1fuaFDk3e3KDNdYkJ9LAesTcmCslAaY2DqlGwVNAELWPag==", + "version": "21.9.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.9.0.tgz", + "integrity": "sha512-V8rgd6ssauIbQGVvmdszGaLda8zRd3nVMJzDExniLVQQAZbCWeDvdcp4pIVWqJtD3dAMZMn/jyYJmYC4wqZpRg==", "requires": { - "@mongodb-js/ssh-tunnel": "^1.2.0", - "ampersand-model": "^8.0.0", + "@mongodb-js/ssh-tunnel": "^1.2.1", + "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", - "debug": "^4.1.1", + "debug": "4.3.0", "lodash": "^4.17.15", - "mongodb-connection-string-url": "^1.0.0", + "mongodb-connection-string-url": "^2.1.0", "mongodb3": "npm:mongodb@^3.6.3", "raf": "^3.4.1", "resolve-mongodb-srv": "^1.1.0", "ssh2": "^0.8.7", - "storage-mixin": "^4.5.1" + "storage-mixin": "^4.8.0" + }, + "dependencies": { + "debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "requires": { + "ms": "2.1.2" + } + }, + "mongodb-connection-string-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", + "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } } }, "mongodb-connection-string-url": { @@ -38402,25 +37661,67 @@ } }, "mongodb-data-service": { - "version": "21.5.4", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.5.4.tgz", - "integrity": "sha512-W0frBajZJbTNBscUX+1hmUWfpjTgGDJy7s4wL09psnNdw7K+maoWKakCL5HrNmLVGsGIIwklYmTz5D4P3iPZdw==", + "version": "21.13.0", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.13.0.tgz", + "integrity": "sha512-dRZJ6+hvFJMeAYdayhX2x/BN2q9EjOaXEcWCRS2BND9WTtAum3QZvrEqROc2ViGldaiJfLcYjZorocSjPKwylw==", "requires": { + "@mongodb-js/compass-logging": "^0.4.0", + "@mongodb-js/ssh-tunnel": "^1.2.1", "async": "^3.2.0", - "debug": "^4.2.0", + "debug": "4.3.0", + "get-port": "^5.1.1", "lodash": "^4.17.20", "mongodb-build-info": "^1.1.1", - "mongodb-index-model": "^3.3.2", - "mongodb-js-errors": "^0.5.0", + "mongodb-connection-string-url": "^2.1.0", + "mongodb-index-model": "^3.6.0", "mongodb-ns": "^2.2.0", - "mongodb-security": "^1.2.1", - "mongodb-url": "^3.0.3" + "resolve-mongodb-srv": "^1.1.0", + "uuid": "^8.3.2" }, "dependencies": { "async": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "requires": { + "ms": "2.1.2" + } + }, + "mongodb-connection-string-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", + "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } } } }, @@ -38515,12 +37816,12 @@ } }, "mongodb-index-model": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.3.2.tgz", - "integrity": "sha512-0/dxalooEGpDe1AUx0sra1hfyQ0T0/gRlChiUMANcLGxcx8+vmC+4VIu1YszbfOa5benIoRIu1TtakFYdLQH3g==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.6.0.tgz", + "integrity": "sha512-tOx0joqNPOKN9mEsSOk2hUQ018uflL/gD5oqCF6LhwQMx7hVG3o3VBGZRTLpObEICH1KqoNW9wmU+P2PLj4xvQ==", "requires": { - "ampersand-collection": "^1.6.1", - "ampersand-model": "^6.0.2", + "ampersand-collection": "^2.0.1", + "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "async": "^3.2.0", "lodash": "^4.17.15", @@ -38528,143 +37829,10 @@ "mongodb-ns": "^2.2.0" }, "dependencies": { - "ampersand-class-extend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ampersand-class-extend/-/ampersand-class-extend-1.0.2.tgz", - "integrity": "sha1-jjqxJdCb976UO1DpjM5G/1F8vpE=", - "requires": { - "lodash.assign": "^3.0.0" - } - }, - "ampersand-collection": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ampersand-collection/-/ampersand-collection-1.6.1.tgz", - "integrity": "sha1-Qw9lTPcC4Z0drGzSEXVdUDFyoMk=", - "requires": { - "ampersand-class-extend": "^1.0.0", - "ampersand-events": "^1.0.1", - "ampersand-version": "^1.0.2", - "lodash.assign": "^3.0.0", - "lodash.bind": "^3.1.0", - "lodash.isarray": "^3.0.1" - } - }, - "ampersand-events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ampersand-events/-/ampersand-events-1.1.1.tgz", - "integrity": "sha1-pQDpjMrorZKqHZV0TFM9nBChZTA=", - "requires": { - "ampersand-version": "^1.0.2", - "lodash.assign": "^3.0.0", - "lodash.bind": "^3.1.0", - "lodash.foreach": "^3.0.2", - "lodash.isempty": "^3.0.1", - "lodash.keys": "^3.0.5", - "lodash.once": "^3.0.0", - "lodash.uniqueid": "^3.0.0" - } - }, - "ampersand-model": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ampersand-model/-/ampersand-model-6.0.3.tgz", - "integrity": "sha1-x+zlpnFVFT4TxyR1rPF4szhKVXw=", - "requires": { - "ampersand-state": "^4.6.0", - "ampersand-sync": "^4.0.3", - "ampersand-version": "^1.0.2", - "lodash.assign": "^3.2.0", - "lodash.clone": "^3.0.3", - "lodash.isobject": "^3.0.2", - "lodash.result": "^3.1.2" - } - }, - "ampersand-state": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/ampersand-state/-/ampersand-state-4.8.2.tgz", - "integrity": "sha1-qPrjZakZ54S8icbqk6PzXvcvLDk=", - "requires": { - "ampersand-events": "^1.1.1", - "ampersand-version": "^1.0.0", - "array-next": "~0.0.1", - "key-tree-store": "^1.3.0", - "lodash.assign": "^3.2.0", - "lodash.bind": "^3.1.0", - "lodash.escape": "^3.0.0", - "lodash.forown": "^3.0.2", - "lodash.has": "^3.0.0", - "lodash.includes": "^3.1.3", - "lodash.isarray": "^3.0.4", - "lodash.isdate": "^3.0.1", - "lodash.isequal": "^3.0.1", - "lodash.isfunction": "^3.0.6", - "lodash.isobject": "^3.0.1", - "lodash.isstring": "^3.0.1", - "lodash.omit": "^3.1.0", - "lodash.result": "^3.0.0", - "lodash.union": "^3.1.0", - "lodash.uniqueid": "^3.0.0" - } - }, - "ampersand-sync": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ampersand-sync/-/ampersand-sync-4.0.3.tgz", - "integrity": "sha1-wlXWBYhZUxRyEJ+KULocCtnY1yU=", - "requires": { - "ampersand-version": "^1.0.0", - "lodash.assign": "^3.0.0", - "lodash.defaults": "^3.1.0", - "lodash.includes": "^3.1.0", - "lodash.result": "^3.0.0", - "media-type": "0.3.0", - "qs": "^4.0.0", - "request": "^2.55.0", - "xhr": "^2.0.5" - } - }, "async": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" - }, - "lodash.assign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", - "requires": { - "lodash._baseassign": "^3.0.0", - "lodash._createassigner": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash.defaults": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", - "requires": { - "lodash.assign": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "lodash.foreach": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", - "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", - "requires": { - "lodash._arrayeach": "^3.0.0", - "lodash._baseeach": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - }, - "qs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", - "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" } } }, @@ -38677,6 +37845,14 @@ "debug": "^4.1.1" } }, + "mongodb-log-writer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.2.tgz", + "integrity": "sha512-SkurMSeQDZw9o+gG6Gz38okEsV5890HBBHDSpTgHeWZukKvSLQIpMqRc4MJSWKvXr8B+Y7iByHebykcjhd/RBA==", + "requires": { + "bson": "^4.5.1" + } + }, "mongodb-ns": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.2.0.tgz", @@ -38811,16 +37987,6 @@ } } }, - "mongodb-security": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/mongodb-security/-/mongodb-security-1.2.1.tgz", - "integrity": "sha512-Jkc7xX7sq4O42r9qybtNeR/RjvYJIF62QI97zBccAcxz4IOuEtJ7rIImzmd6Idechx0l0LiRmjI4rjV84utn6w==", - "requires": { - "debug": "^4.1.1", - "lodash.every": "^4.6.0", - "lodash.foreach": "^4.5.0" - } - }, "mongodb-tools": { "version": "git+ssh://git@github.com/mongodb-js/mongodb-tools.git#0d1a90f49796c41f6d47c7c7999fe384014a16a0", "dev": true, @@ -38871,11 +38037,6 @@ } } }, - "mongodb-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/mongodb-url/-/mongodb-url-3.0.3.tgz", - "integrity": "sha512-ky8FX1uz1i7EpGY2hQgD9Zw4ISrFfFTbHJAXzis+i7lMJVEvHLb/WdzO0jg0i5hJnoekhQU45cvanIq9iHri1A==" - }, "mongodb-version-list": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mongodb-version-list/-/mongodb-version-list-1.0.0.tgz", @@ -39400,8 +38561,7 @@ "node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "devOptional": true + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" }, "node-fetch": { "version": "2.6.1", @@ -39565,7 +38725,8 @@ "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "optional": true }, "normalize-package-data": { "version": "2.5.0", @@ -40910,35 +40071,23 @@ } }, "prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", + "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - } + "tunnel-agent": "^0.6.0" } }, "prelude-ls": { @@ -42532,17 +41681,17 @@ "dev": true }, "storage-mixin": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.5.1.tgz", - "integrity": "sha512-7Muq1+on0jNad2QPRo18+UNARtS1P6WGpKp8r+9rfLon6wLFBcFW9FFwlh9xcNV4+B33I7XhEP08K+FRfYNFnA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.8.0.tgz", + "integrity": "sha512-qwpGc069ORwNJHMnvxM+xP7qLlXfhCvxkxb4vqVFNeM2UR1IyuRCW51bAahVmrrDGOAWShg7CY7Z7E7NK6bCGA==", "requires": { "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "ampersand-sync": "^5.1.0", - "async": "^3.1.0", - "debug": "^4.1.1", - "hadron-ipc": "^2.3.1", - "keytar": "^5.0.0", + "async": "^3.2.0", + "debug": "4.3.0", + "hadron-ipc": "^2.6.0", + "keytar": "^7.7.0", "localforage": "^1.7.3", "lodash": "^4.17.15", "rimraf": "^3.0.0", @@ -42555,6 +41704,14 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, + "debug": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", + "requires": { + "ms": "2.1.2" + } + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -43857,43 +43014,12 @@ "lru-cache": "^6.0.0" } }, - "keytar": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz", - "integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==", - "dev": true, - "requires": { - "node-addon-api": "^3.0.0", - "prebuild-install": "^6.0.0" - } - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, - "prebuild-install": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", - "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.21.0", - "npmlog": "^4.0.1", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^3.0.3", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - } - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -44868,11 +43994,6 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -45004,7 +44125,8 @@ "ws": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==" + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true }, "xhr": { "version": "2.6.0", diff --git a/package.json b/package.json index 1f59f31b1..a53d87535 100644 --- a/package.json +++ b/package.json @@ -389,10 +389,6 @@ "light": "images/light/plus-circle.svg", "dark": "images/dark/plus-circle.svg" } - }, - { - "command": "mdb.startStreamLanguageServerLogs", - "title": "LSP Inspector: Start Stream LSP Logs" } ], "menus": { @@ -920,12 +916,13 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.6.0", + "lodash": "^4.17.21", "micromatch": "^4.0.4", "mongodb": "^4.2.0", "mongodb-cloud-info": "^1.1.3", - "mongodb-connection-model": "^21.5.4", - "mongodb-data-service": "^21.5.4", - "mongodb-js-errors": "^0.5.0", + "mongodb-connection-model": "^21.9.0", + "mongodb-connection-string-url": "^1.1.0", + "mongodb-data-service": "^21.13.0", "mongodb-ns": "^2.2.0", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", @@ -937,8 +934,7 @@ "uuid": "^8.3.2", "vscode-languageclient": "^7.0.0", "vscode-languageserver": "^7.0.0", - "vscode-languageserver-textdocument": "^1.0.2", - "ws": "^7.5.5" + "vscode-languageserver-textdocument": "^1.0.2" }, "devDependencies": { "@types/analytics-node": "^3.1.7", @@ -951,6 +947,7 @@ "@types/enzyme": "^3.10.10", "@types/glob": "^7.2.0", "@types/jest": "^26.0.24", + "@types/lodash": "^4.14.178", "@types/mocha": "^8.2.3", "@types/node": "^14.17.33", "@types/react": "^17.0.35", @@ -958,7 +955,6 @@ "@types/sinon": "^9.0.11", "@types/uuid": "^8.3.3", "@types/vscode": "^1.58.1", - "@types/ws": "^7.4.7", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^9.8.8", diff --git a/src/commands/index.ts b/src/commands/index.ts index 60c06fee2..007c144d0 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -26,7 +26,6 @@ enum EXTENSION_COMMANDS { MDB_CHANGE_ACTIVE_CONNECTION = 'mdb.changeActiveConnection', MDB_REFRESH_PLAYGROUNDS = 'mdb.refreshPlaygrounds', - MDB_START_LANGUAGE_STREAM_LOGS = 'mdb.startStreamLanguageServerLogs', MDB_CODELENS_SHOW_MORE_DOCUMENTS = 'mdb.codeLens.showMoreDocumentsClicked', diff --git a/src/commands/launchMongoShell.ts b/src/commands/launchMongoShell.ts index 95a7492b6..6c63fd521 100644 --- a/src/commands/launchMongoShell.ts +++ b/src/commands/launchMongoShell.ts @@ -120,10 +120,7 @@ const openMongoDBShell = (connectionController: ConnectionController): Promise { + private _migratePreviouslySavedConnection () {} + + private async _getConnectionOptionsWithSecrets(connectionId: string): Promise { if (!ext.keytarModule) { + log.error('VSCode extension keytar module is undefined.'); return; } - let loadedSavedConnection: LoadedConnection; - try { - const unparsedConnectionInformation = await ext.keytarModule.getPassword( + const unparsedSecrets = await ext.keytarModule.getPassword( this._serviceName, connectionId ); - if (!unparsedConnectionInformation) { - // Ignore empty connections. + if (!unparsedSecrets) { + // Ignore empty secrets. return; } - const connectionInformation: SavedConnectionInformation = JSON.parse( - unparsedConnectionInformation - ); + const secrets = JSON.parse(unparsedSecrets); - if (!connectionInformation.connectionModel) { - // Ignore empty connections. - return; + if (secrets.connectionModel) { + this._migratePreviouslySavedConnection(); } - loadedSavedConnection = { - id: connectionId, - name: savedConnection.name, - connectionModel: connectionInformation.connectionModel, - storageLocation: savedConnection.storageLocation - }; + const connectionOptions = this._connections[connectionId].connectionOptions; + const connectionInfoWithSecrets = mergeSecrets({ connectionOptions }, secrets); + + return connectionInfoWithSecrets.connectionOptions; } catch (error) { // Here we're leniant when loading connections in case their // connections have become corrupted. return; } - - this._connections[connectionId] = { - ...loadedSavedConnection, - connectionModel: new Connection(loadedSavedConnection.connectionModel) - }; - this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); - - return; } - async loadSavedConnections(): Promise { - if (!ext.keytarModule) { + private async _loadSavedConnectionsByStore( + savedConnections: ConnectionsFromStorage + ): Promise { + if (Object.keys(savedConnections).length < 1) { return; } - // Load saved connections from storage. - const existingGlobalConnections: SavedConnection[] = + await Promise.all( + Object.keys(savedConnections).map(async (connectionId) => { + this._connections[connectionId] = { ...savedConnections[connectionId] }; + + const connectionOptionsWithSecrets = await this._getConnectionOptionsWithSecrets( + connectionId + ); + + if (connectionOptionsWithSecrets) { + this._connections[connectionId].connectionOptions = connectionOptionsWithSecrets; + } + + this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); + }) + ); + } + + async loadSavedConnections(): Promise { + // Try to pull in the connections previously saved in the global storage of vscode. + const existingGlobalConnections = this._storageController.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS) || {}; + await this._loadSavedConnectionsByStore(existingGlobalConnections); - if (Object.keys(existingGlobalConnections).length > 0) { - // Try to pull in the connections previously saved globally on vscode. - await Promise.all( - Object.keys(existingGlobalConnections).map((connectionId) => - this._loadSavedConnection( - connectionId, - existingGlobalConnections[connectionId] - ) - ) - ); - } - - const existingWorkspaceConnections: SavedConnection[] = + // Try to pull in the connections previously saved in the workspace storage of vscode. + const existingWorkspaceConnections = this._storageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE ) || {}; - - if (Object.keys(existingWorkspaceConnections).length > 0) { - // Try to pull in the connections previously saved on the workspace. - await Promise.all( - Object.keys(existingWorkspaceConnections).map((connectionId) => - this._loadSavedConnection( - connectionId, - existingWorkspaceConnections[connectionId] - ) - ) - ); - } + await this._loadSavedConnectionsByStore(existingWorkspaceConnections); } async connectWithURI(): Promise { @@ -192,7 +187,7 @@ export default class ConnectionController { 'e.g. mongodb+srv://username:password@cluster0.mongodb.net/admin', prompt: 'Enter your connection string (SRV or standard)', validateInput: (uri: string) => { - if (!Connection.isURI(uri)) { + if (!ConnectionModel.isURI(uri)) { return 'MongoDB connection strings begin with "mongodb://" or "mongodb+srv://"'; } @@ -225,12 +220,18 @@ export default class ConnectionController { ): Promise { log.info('Trying to connect to a new connection configuration'); - const connectionFrom = promisify(Connection.from.bind(Connection)); + const connectionFrom = promisify(ConnectionModel.from.bind(ConnectionModel)); try { - const newConnectionModel = await connectionFrom(connectionString); + const connectionModel = await connectionFrom(connectionString); + + if (typeof connectionModel.appname === 'undefined') { + connectionModel.appname = `${name} ${version}`; + } + + const connectionInfo = convertConnectionModelToInfo(connectionModel); const connectResult = await this.saveNewConnectionAndConnect( - newConnectionModel, + connectionInfo, ConnectionTypes.CONNECTION_STRING ); @@ -240,140 +241,75 @@ export default class ConnectionController { } } - public sendTelemetry( - newDataService: DataServiceType, - connectionModel: ConnectionModel, - connectionType: ConnectionTypes - ): void { - void this._telemetryService.trackNewConnection( - newDataService.client.client, - connectionModel, - connectionType - ); + public sendTelemetry(newDataService: DataService, connectionType: ConnectionTypes): void { + void this._telemetryService.trackNewConnection(newDataService, connectionType); } - parseNewConnection(newConnectionModel: ConnectionModel): ConnectionModel { - // Here we re-parse the connection, as it can be loaded from storage or - // passed by the connection model without the class methods. - const connectionModel: ConnectionModel = new Connection( - newConnectionModel - ); + parseNewConnection(rawConnectionModel: RawConnectionModel): ConnectionInfo { + const connectionModel = new ConnectionModel(rawConnectionModel); - return connectionModel; + return convertConnectionModelToInfo(connectionModel); } - getConnectionNameFromConnectionModel(connectionModel: ConnectionModel): string { - const { sshTunnelOptions } = connectionModel.getAttributes({ - derived: true - }); - - if ( - connectionModel.sshTunnel && - connectionModel.sshTunnel !== SSH_TUNNEL_TYPES.NONE && - sshTunnelOptions.host && - sshTunnelOptions.port - ) { - return `SSH to ${connectionModel.hosts - .map(({ host, port }) => `${host}:${port}`) - .join(',')}`; - } - - if (connectionModel.isSrvRecord) { - return connectionModel.hostname; - } + private async _saveSecretsToKeychain( + { connectionId, secrets }: { connectionId: string, secrets: ConnectionSecrets } + ): Promise { + if (ext.keytarModule) { + const secretsAsString = JSON.stringify(secrets); - if (connectionModel.hosts && connectionModel.hosts.length > 0) { - return connectionModel.hosts - .map(({ host, port }) => `${host}:${port}`) - .join(','); + await ext.keytarModule.setPassword( + this._serviceName, + connectionId, + secretsAsString + ); } - - return connectionModel.hostname; } async saveNewConnectionAndConnect( - connectionModel: ConnectionModel, + originalConnectionInfo: ConnectionInfo, connectionType: ConnectionTypes ): Promise { - const connectionId = uuidv4(); - const connectionInformation: SavedConnectionInformation = { - connectionModel - }; - const connectionName = this.getConnectionNameFromConnectionModel( - connectionModel + // We don't want to store secrets to disc. + const { connectionInfo: safeConnectionInfo, secrets } = extractSecrets( + originalConnectionInfo + ); + const savedConnectionInfo = await this._storageController.storeNewConnection( + safeConnectionInfo // The connection info without secrtes. ); - const savedConnection: SavedConnection = { - id: connectionId, - name: connectionName, - // To begin we just store it on the session, the storage controller - // handles changing this based on user preference. - storageLocation: StorageScope.NONE - }; - const newLoadedConnection = { - ...savedConnection, - ...connectionInformation - }; - - this._connections[connectionId] = newLoadedConnection; - if (ext.keytarModule) { - const connectionInfoAsString = JSON.stringify(connectionInformation); + // We want to keep secrets in memory tho. + this._connections[savedConnectionInfo.id] = { + ...savedConnectionInfo, + connectionOptions: originalConnectionInfo.connectionOptions // The connection options with secrtes. + }; - await ext.keytarModule.setPassword( - this._serviceName, - connectionId, - connectionInfoAsString - ); - void this._storageController.storeNewConnection(newLoadedConnection); - } + // We also want to store secrets to keychain. + await this._saveSecretsToKeychain({ + connectionId: savedConnectionInfo.id, + secrets + }); - return this.connect(connectionId, connectionModel, connectionType); + log.info(`Connect called to connect to instance: ${savedConnectionInfo.name}`); + return this._connect(savedConnectionInfo.id, connectionType); } - private _endPrevConnectAttempt (attempt: { - connectionId: string, - connectingAttemptVersion: null | string, - newDataService: DataServiceType - }): Boolean { - const { connectionId, connectingAttemptVersion, newDataService } = attempt; + async getDataServiceAndConnect(connectionOptions: ConnectionOptions) { + const dataService = await connect(connectionOptions); + await dataService.connect(); - if ( - connectingAttemptVersion !== this._connectingVersion || - !this._connections[connectionId] - ) { - // If the current attempt is no longer the most recent attempt - // or the connection no longer exists we silently end the connection - // and return. - try { - newDataService.disconnect(() => {}); - } catch (e) { - /* */ - } - - return true; - } - - return false; + return dataService; } - async connect( + private async _connect( connectionId: string, - connectionModel: ConnectionModel, connectionType: ConnectionTypes ): Promise { - log.info( - 'Connect called to connect to instance:', - this.getConnectionNameFromConnectionModel(connectionModel) - ); - // Store a version of this connection, so we can see when the conection // is successful if it is still the most recent connection attempt. this._connectingVersion = connectionId; const connectingAttemptVersion = this._connectingVersion; - this._connecting = true; this._connectingConnectionId = connectionId; - this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); if (this._activeDataService) { @@ -382,15 +318,17 @@ export default class ConnectionController { this._statusView.showMessage('Connecting to MongoDB...'); - // Override the default connection `appname`. - connectionModel.appname = `${name} ${version}`; + const connectionOptions = this._connections[connectionId].connectionOptions; + + if (!connectionOptions) { + throw new Error('Connection not found.'); + } - const newDataService: any = new DataService(connectionModel); - const _connect = promisify(newDataService.connect.bind(newDataService)); + let newDataService; let connectError; try { - await _connect(); + newDataService = await this.getDataServiceAndConnect(connectionOptions); } catch (error) { connectError = error; } @@ -416,7 +354,7 @@ export default class ConnectionController { void vscode.window.showInformationMessage('MongoDB connection successful.'); this._activeDataService = newDataService; - this._activeConnectionModel = connectionModel; + this._activeConnectionModel = await convertConnectionInfoToModel({ connectionOptions }); this._currentConnectionId = connectionId; this._connecting = false; this._connectingConnectionId = null; @@ -424,7 +362,7 @@ export default class ConnectionController { this.eventEmitter.emit(DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED); // Send metrics to Segment - this.sendTelemetry(newDataService, connectionModel, connectionType); + this.sendTelemetry(newDataService, connectionType); return { successfullyConnected: true, @@ -432,42 +370,43 @@ export default class ConnectionController { }; } - async connectWithConnectionId(connectionId: string): Promise { - if (!this._connections[connectionId]) { - throw new Error('Connection not found.'); - } + private _endPrevConnectAttempt (attempt: { + connectionId: string, + connectingAttemptVersion: null | string, + newDataService: DataService + }): Boolean { + const { connectionId, connectingAttemptVersion, newDataService } = attempt; + + if ( + connectingAttemptVersion !== this._connectingVersion || + !this._connections[connectionId] + ) { + // If the current attempt is no longer the most recent attempt + // or the connection no longer exists we silently end the connection + // and return. + try { + void newDataService.disconnect(); + } catch (e) { + /* */ + } - let connectionModel: ConnectionModel; + return true; + } - try { - const savedConnectionModel = this._connections[connectionId].connectionModel; - - // Here we rebuild the connection model to ensure it's up to date and - // contains the connection model class methods (not just attributes). - connectionModel = new Connection( - savedConnectionModel.getAttributes - ? savedConnectionModel.getAttributes({ props: true }) - : savedConnectionModel - ); - } catch (error) { - void vscode.window.showErrorMessage(`Unable to load connection: ${error}`); + return false; + } - return false; + async connectWithConnectionId(connectionId: string): Promise { + if (!this._connections[connectionId]) { + throw new Error('Connection not found.'); } try { - const connectResult = await this.connect( - connectionId, - connectionModel, - ConnectionTypes.CONNECTION_ID - ); + await this._connect(connectionId, ConnectionTypes.CONNECTION_ID); - return connectResult.successfullyConnected; + return true; } catch (error) { - const printableError = error as { message: string }; - void vscode.window.showErrorMessage(printableError.message); - - return false; + throw new Error(`Unable to connection: ${error}`); } } @@ -477,6 +416,12 @@ export default class ConnectionController { this._currentConnectionId ); + this._currentConnectionId = null; + this._disconnecting = true; + + this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); + this.eventEmitter.emit(DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED); + if (!this._activeDataService) { void vscode.window.showErrorMessage( 'Unable to disconnect: no active connection.' @@ -485,26 +430,15 @@ export default class ConnectionController { return false; } - const dataServiceToDisconnectFrom = this._activeDataService; - - this._activeDataService = null; - this._currentConnectionId = null; - this._activeConnectionModel = null; - this._disconnecting = true; - - this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); - this.eventEmitter.emit(DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED); - - // Disconnect from the active connection. - const _disconnect = promisify( - dataServiceToDisconnectFrom.disconnect.bind(dataServiceToDisconnectFrom) - ); - this._statusView.showMessage('Disconnecting from current connection...'); try { - await _disconnect(); + // Disconnect from the active connection. + await this._activeDataService.disconnect(); void vscode.window.showInformationMessage('MongoDB disconnected.'); + + this._activeDataService = null; + this._activeConnectionModel = null; } catch (error) { // Show an error, however we still reset the active connection to free up the extension. void vscode.window.showErrorMessage( @@ -518,15 +452,17 @@ export default class ConnectionController { return true; } - async removeSavedConnection(connectionId: string): Promise { - delete this._connections[connectionId]; - + private async _removeSecretsFromKeychain(connectionId: string) { if (ext.keytarModule) { await ext.keytarModule.deletePassword(this._serviceName, connectionId); - // We only remove the connection from the saved connections if we - // have deleted the connection information with keytar. - this._storageController.removeConnection(connectionId); } + } + + async removeSavedConnection(connectionId: string): Promise { + delete this._connections[connectionId]; + + await this._removeSecretsFromKeychain(connectionId); + this._storageController.removeConnection(connectionId); this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); } @@ -653,6 +589,7 @@ export default class ConnectionController { await this._storageController.saveConnectionToWorkspaceStore( this._connections[connectionId] ); + return true; } @@ -660,22 +597,6 @@ export default class ConnectionController { return true; } - getSavedConnections(): SavedConnection[] { - return Object.values(this._connections); - } - - getActiveConnectionId(): string | null { - return this._currentConnectionId; - } - - getActiveConnectionDriverUrl(): string | null { - if (!this._currentConnectionId) { - return null; - } - - return this._connections[this._currentConnectionId].connectionModel.driverUrl; - } - addEventListener( eventType: DataServiceEventTypes, listener: () => void @@ -698,12 +619,28 @@ export default class ConnectionController { return this._disconnecting; } + isCurrentlyConnected(): boolean { + return this._activeDataService !== null; + } + + getSavedConnections(): SavedConnectionInfo[] { + return Object.values(this._connections); + } + getSavedConnectionName(connectionId: string): string { return this._connections[connectionId] ? this._connections[connectionId].name : ''; } + getConnectingConnectionId(): string | null { + return this._connectingConnectionId; + } + + getActiveConnectionId(): string | null { + return this._currentConnectionId; + } + getActiveConnectionName(): string { if (!this._currentConnectionId) { return ''; @@ -714,32 +651,26 @@ export default class ConnectionController { : ''; } - isConnectionWithIdSaved(connectionId: string | null): boolean { - if (connectionId === null) { - return false; - } - - return !!this._connections[connectionId]; + getActiveDataService(): DataService | null { + return this._activeDataService; } - getConnectingConnectionId(): string | null { - return this._connectingConnectionId; + getActiveDerivedConnectionModel(): ConnectionModel { + return this._activeConnectionModel + ? this._activeConnectionModel.getAttributes({ derived: true }) + : null; } - getConnectionStringFromConnectionId(connectionId: string): string { - return this._connections[connectionId].connectionModel.driverUrlWithSsh; - } + async getConnectionStringByConnectionId(connectionId: string): Promise { + const connectionOptions = this._connections[connectionId].connectionOptions; - isCurrentlyConnected(): boolean { - return this._activeDataService !== null; - } + if (!connectionOptions) { + throw new Error('Connection not found.'); + } - getActiveDataService(): null | DataServiceType { - return this._activeDataService; - } + const connectionModel = await convertConnectionInfoToModel({ connectionOptions }); - getActiveConnectionModel(): null | ConnectionModel { - return this._activeConnectionModel; + return connectionModel.driverUrlWithSsh || ''; } getConnectionStatus(): CONNECTION_STATUS { @@ -793,23 +724,19 @@ export default class ConnectionController { return this._connectingVersion; } - setActiveConnection(newActiveConnection: any): void { - this._activeDataService = newActiveConnection; + setActiveDataService(newDataService: DataService): void { + this._activeDataService = newDataService; } setConnnecting(connecting: boolean): void { this._connecting = connecting; } - setConnnectingConnectionId(connectingConnectionId: string): void { - this._connectingConnectionId = connectingConnectionId; - } - setDisconnecting(disconnecting: boolean): void { this._disconnecting = disconnecting; } - getСonnectionQuickPicks(): any[] { + getСonnectionQuickPicks(): СonnectionQuickPicks[] { if (!this._connections) { return [ { @@ -829,10 +756,10 @@ export default class ConnectionController { } }, ...Object.values(this._connections) - .sort((connectionA: { name: string }, connectionB: any) => + .sort((connectionA: SavedConnectionInfo, connectionB: SavedConnectionInfo) => (connectionA.name || '').localeCompare(connectionB.name || '') ) - .map((item: any) => ({ + .map((item: SavedConnectionInfo) => ({ label: item.name, data: { type: NewConnectionType.SAVED_CONNECTION, @@ -858,9 +785,10 @@ export default class ConnectionController { return this.connectWithURI(); } - // Get the saved connection by id and return as the current connection. - return this.connectWithConnectionId( - selectedQuickPickItem.data.connectionId - ); + if (!selectedQuickPickItem.data.connectionId) { + return true; + } + + return this.connectWithConnectionId(selectedQuickPickItem.data.connectionId); } } diff --git a/src/editors/collectionDocumentsProvider.ts b/src/editors/collectionDocumentsProvider.ts index bb8feb605..7ca48169d 100644 --- a/src/editors/collectionDocumentsProvider.ts +++ b/src/editors/collectionDocumentsProvider.ts @@ -1,11 +1,11 @@ -import { URLSearchParams } from 'url'; import * as vscode from 'vscode'; +import * as util from 'util'; +import { URLSearchParams } from 'url'; import CollectionDocumentsOperationsStore from './collectionDocumentsOperationsStore'; import ConnectionController from '../connectionController'; -import { StatusView } from '../views'; -import * as util from 'util'; import EditDocumentCodeLensProvider from './editDocumentCodeLensProvider'; +import { StatusView } from '../views'; export const NAMESPACE_URI_IDENTIFIER = 'namespace'; export const OPERATION_ID_URI_IDENTIFIER = 'operationId'; @@ -79,15 +79,14 @@ implements vscode.TextDocumentContentProvider { throw new Error(errorMessage); } - const find = util.promisify(dataservice.find.bind(dataservice)); - try { + const find = util.promisify( + dataservice.find.bind(dataservice) + ); const documents = await find( namespace, {}, // No filter. - { - limit: documentLimit - } + { limit: documentLimit } ); operation.isCurrentlyFetchingMoreDocuments = false; diff --git a/src/editors/mongoDBDocumentService.ts b/src/editors/mongoDBDocumentService.ts index 7a7a2786a..8b7f5f0b6 100644 --- a/src/editors/mongoDBDocumentService.ts +++ b/src/editors/mongoDBDocumentService.ts @@ -1,6 +1,6 @@ +import * as util from 'util'; import * as vscode from 'vscode'; -import { EJSON } from 'bson'; -import util from 'util'; +import { EJSON, Document } from 'bson'; import ConnectionController from '../connectionController'; import { createLogger } from '../logging'; @@ -10,14 +10,14 @@ import type { EditDocumentInfo } from '../types/editDocumentInfoType'; import { StatusView } from '../views'; import TelemetryService from '../telemetry/telemetryService'; +const log = createLogger('document controller'); + export const DOCUMENT_ID_URI_IDENTIFIER = 'documentId'; export const DOCUMENT_SOURCE_URI_IDENTIFIER = 'source'; export const VIEW_DOCUMENT_SCHEME = 'VIEW_DOCUMENT_SCHEME'; -const log = createLogger('document controller'); - export default class MongoDBDocumentService { _context: vscode.ExtensionContext; _documentIdStore: DocumentIdStore; @@ -84,22 +84,17 @@ export default class MongoDBDocumentService { ); } - const findOneAndReplace = util.promisify( - dataservice.findOneAndReplace.bind(dataservice) - ); - this._statusView.showMessage('Saving document...'); try { + const findOneAndReplace = util.promisify( + dataservice.findOneAndReplace.bind(dataservice) + ); await findOneAndReplace( namespace, - { - _id: documentId - }, - newDocument, - { - returnDocument: 'after' - } + { _id: documentId }, + newDocument as Document, + { returnDocument: 'after' } ); this._statusView.hideMessage(); @@ -138,19 +133,14 @@ export default class MongoDBDocumentService { ); } - const find = util.promisify(dataservice.find.bind(dataservice)); - this._statusView.showMessage('Fetching document...'); try { + const find = util.promisify(dataservice.find.bind(dataservice)); const documents = await find( namespace, - { - _id: documentId - }, - { - limit: 1 - } + { _id: documentId }, + { limit: 1 } ); this._statusView.hideMessage(); diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 0f78b7687..829b75043 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -1,13 +1,15 @@ import * as vscode from 'vscode'; +import semver from 'semver'; import ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider'; +import CodeActionProvider from './codeActionProvider'; import ConnectionController, { DataServiceEventTypes } from '../connectionController'; import { createLogger } from '../logging'; import { ExplorerController, ConnectionTreeItem, DatabaseTreeItem } from '../explorer'; -import { LanguageServerController } from '../language'; import ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; +import { LanguageServerController } from '../language'; import { OutputChannel, ProgressLocation, TextEditor } from 'vscode'; import playgroundCreateIndexTemplate from '../templates/playgroundCreateIndexTemplate'; import playgroundCreateCollectionTemplate from '../templates/playgroundCreateCollectionTemplate'; @@ -26,29 +28,18 @@ import playgroundSearchTemplate from '../templates/playgroundSearchTemplate'; import playgroundTemplate from '../templates/playgroundTemplate'; import { StatusView } from '../views'; import TelemetryService from '../telemetry/telemetryService'; -import { ConnectionOptions } from '../types/connectionOptionsType'; -import CodeActionProvider from './codeActionProvider'; -const transpiler = require('bson-transpilers'); const log = createLogger('playground controller'); +const transpiler = require('bson-transpilers'); -const getSSLFilePathsFromConnectionModel = ( - connectionModelDriverOptions: ConnectionOptions -): { - sslCA?: string | string[]; - sslCert?: string | string[]; - sslKey?: string | string[]; -} => { - const sslFilePaths = {}; - ['sslCA', 'sslCert', 'sslKey'].forEach((key) => { - if (connectionModelDriverOptions[key]) { - sslFilePaths[key] = connectionModelDriverOptions[key] as ( - string | string[] - ); - } - }); +const MIN_TIME_SERIES_SERVER_VERSION = '5.0.0-alpha0'; - return sslFilePaths; +const hasTimeSeriesSupport = (serverVersion) => { + try { + return semver.gte(serverVersion, MIN_TIME_SERIES_SERVER_VERSION); + } catch (e) { + return true; + } }; /** @@ -58,25 +49,25 @@ export default class PlaygroundController { _connectionController: ConnectionController; _activeTextEditor?: TextEditor; _playgroundResult?: PlaygroundResult; - _context: vscode.ExtensionContext; _languageServerController: LanguageServerController; - _telemetryService: TelemetryService; - _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; - _outputChannel: OutputChannel; - _connectionString?: string; _selectedText?: string; - _codeToEvaluate = ''; - _isPartialRun: boolean; - _playgroundResultViewColumn?: vscode.ViewColumn; - _playgroundResultTextDocument?: vscode.TextDocument; - _statusView: StatusView; - _playgroundResultViewProvider: PlaygroundResultProvider; - _explorerController: ExplorerController; _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; _codeActionProvider: CodeActionProvider; + _isPartialRun = false; + + private _telemetryService: TelemetryService; + private _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; + private _outputChannel: OutputChannel; + private _playgroundResultViewColumn?: vscode.ViewColumn; + private _playgroundResultTextDocument?: vscode.TextDocument; + private _statusView: StatusView; + private _playgroundResultViewProvider: PlaygroundResultProvider; + private _explorerController: ExplorerController; + + private _codeToEvaluate = ''; + constructor( - context: vscode.ExtensionContext, connectionController: ConnectionController, languageServerController: LanguageServerController, telemetryService: TelemetryService, @@ -87,8 +78,6 @@ export default class PlaygroundController { codeActionProvider: CodeActionProvider, explorerController: ExplorerController ) { - this._context = context; - this._isPartialRun = false; this._connectionController = connectionController; this._languageServerController = languageServerController; this._telemetryService = telemetryService; @@ -156,34 +145,25 @@ export default class PlaygroundController { const dataService = this._connectionController.getActiveDataService(); const connectionId = this._connectionController.getActiveConnectionId(); - const connectionModel = this._connectionController - .getActiveConnectionModel(); - if (!dataService || !connectionId || !connectionModel) { + if (!dataService || !connectionId) { this._activeConnectionCodeLensProvider.refresh(); return; } - const connectionDetails = dataService.getConnectionOptions(); - const connectionString = connectionDetails.url; - // We pass file paths to the language server since it doesn't - // handle being passsed buffers well. - // With driver version 4.0 we should be able to remove any use - // of buffers and just pass file paths. - const sslOptionsFilePaths = getSSLFilePathsFromConnectionModel( - connectionModel.getAttributes({ derived: true }).driverOptions - ); + const mongoClientOption = dataService.getMongoClientConnectionOptions(); - const connectionOptions: ConnectionOptions = { - ...connectionDetails.options, - ...sslOptionsFilePaths - }; + if (!mongoClientOption) { + this._activeConnectionCodeLensProvider.refresh(); + + return; + } await this._languageServerController.connectToServiceProvider({ connectionId, - connectionString, - connectionOptions + connectionString: mongoClientOption.url, + connectionOptions: mongoClientOption.options }); this._activeConnectionCodeLensProvider.refresh(); @@ -230,15 +210,10 @@ export default class PlaygroundController { let content = playgroundCreateCollectionTemplate; if (dataService) { - const adminDb = dataService.client.client.db('admin'); - const buildInfo = await adminDb.command({ buildInfo: 1 }); + const instance = await dataService.instance(); - if (buildInfo) { - const serverVersion = buildInfo.versionArray[0]; - - if (serverVersion >= 5) { - content = playgroundCreateCollectionWithTSTemplate; - } + if (hasTimeSeriesSupport(instance.build.version)) { + content = playgroundCreateCollectionWithTSTemplate; } } @@ -599,14 +574,13 @@ export default class PlaygroundController { selection }); - const dataService = this._connectionController.getActiveDataService(); - const connectionDetails = dataService?.getConnectionOptions(); + const connectionModel = this._connectionController.getActiveDerivedConnectionModel(); const toCompile = { aggregation: selectedText, options: { collection: namespace.collectionName, database: namespace.databaseName, - uri: connectionDetails?.url + uri: connectionModel?.driverUrlWithSsh } }; diff --git a/src/explorer/collectionTreeItem.ts b/src/explorer/collectionTreeItem.ts index 37c3d7b0c..0d8f35aa5 100644 --- a/src/explorer/collectionTreeItem.ts +++ b/src/explorer/collectionTreeItem.ts @@ -1,17 +1,15 @@ +import * as util from 'util'; import * as vscode from 'vscode'; import path from 'path'; -import { createLogger } from '../logging'; import DocumentListTreeItem, { CollectionTypes, MAX_DOCUMENTS_VISIBLE } from './documentListTreeItem'; +import { getImagesPath } from '../extensionConstants'; import IndexListTreeItem from './indexListTreeItem'; import TreeItemParent from './treeItemParentInterface'; import SchemaTreeItem from './schemaTreeItem'; -import { getImagesPath } from '../extensionConstants'; - -const log = createLogger('tree view collection folder'); function getIconPath( type: CollectionTypes, @@ -90,7 +88,7 @@ export default class CollectionTreeItem extends vscode.TreeItem cachedDocumentCount: number | null, existingDocumentListChild?: DocumentListTreeItem, existingSchemaChild?: SchemaTreeItem, - existingIndexListChild?: IndexListTreeItem + existingIndexListChild?: IndexListTreeItem // Existing cache. ) { super( collection.name, @@ -103,16 +101,11 @@ export default class CollectionTreeItem extends vscode.TreeItem this.collectionName = collection.name; this.databaseName = databaseName; this.namespace = `${this.databaseName}.${this.collectionName}`; - this._type = collection.type; // Type can be `collection` or `view`. this._dataService = dataService; - this.isExpanded = isExpanded; - this.documentCount = cachedDocumentCount; - this.cacheIsUpToDate = cacheIsUpToDate; - this._documentListChild = existingDocumentListChild ? existingDocumentListChild : new DocumentListTreeItem( @@ -310,29 +303,6 @@ export default class CollectionTreeItem extends vscode.TreeItem return this._documentListChild.getMaxDocumentsToShow(); } - getCount(): Promise { - log.info(`fetching document count from namespace ${this.namespace}`); - - return new Promise((resolve, reject) => { - this._dataService.estimatedCount( - this.namespace, - {}, // No options. - (error: Error | undefined, count: number) => { - if (error) { - const printableError = error as { message: string }; - return reject( - new Error( - `Unable to get collection document count: ${printableError.message}` - ) - ); - } - - return resolve(count); - } - ); - }); - } - refreshDocumentCount = async (): Promise => { // Skip the count on views and time-series collections since it will error. if ( @@ -346,12 +316,20 @@ export default class CollectionTreeItem extends vscode.TreeItem try { // We fetch the document when we expand in order to show // the document count in the document list tree item `description`. - this.documentCount = await this.getCount(); + const estimatedCount = util.promisify( + this._dataService.estimatedCount.bind(this._dataService) + ); + + this.documentCount = await estimatedCount( + this.namespace, + {} // No options. + ); + + return this.documentCount as number; } catch (err) { + this.documentCount = null; return 0; } - - return this.documentCount; }; async onDropCollectionClicked(): Promise { @@ -384,24 +362,24 @@ export default class CollectionTreeItem extends vscode.TreeItem return Promise.resolve(false); } - return new Promise((resolve) => { - this._dataService.dropCollection( - `${this.databaseName}.${collectionName}`, - (error: Error | null, successfullyDroppedCollection = false) => { - if (error) { - const printableError = error as { message: string }; - void vscode.window.showErrorMessage( - `Drop collection failed: ${printableError.message}` - ); - - return resolve(false); - } + try { + const dropCollection = util.promisify( + this._dataService.dropCollection.bind(this._dataService) + ); + const successfullyDroppedCollection = await dropCollection( + `${this.databaseName}.${collectionName}` + ); - this.isDropped = successfullyDroppedCollection; + this.isDropped = successfullyDroppedCollection; - return resolve(successfullyDroppedCollection); - } + return successfullyDroppedCollection; + } catch (error) { + const printableError = error as { message: string }; + void vscode.window.showErrorMessage( + `Drop collection failed: ${printableError.message}` ); - }); + + return false; + } } } diff --git a/src/explorer/connectionTreeItem.ts b/src/explorer/connectionTreeItem.ts index 406899018..86d5199d3 100644 --- a/src/explorer/connectionTreeItem.ts +++ b/src/explorer/connectionTreeItem.ts @@ -1,42 +1,16 @@ import * as vscode from 'vscode'; import path from 'path'; -import { promisify } from 'util'; -import { isNotAuthorized } from 'mongodb-js-errors'; -import { MongoClient } from 'mongodb'; import DatabaseTreeItem from './databaseTreeItem'; import ConnectionController from '../connectionController'; -import TreeItemParent from './treeItemParentInterface'; import { getImagesPath } from '../extensionConstants'; +import TreeItemParent from './treeItemParentInterface'; export enum ConnectionItemContextValues { disconnected = 'disconnectedConnectionTreeItem', connected = 'connectedConnectionTreeItem', } -export function getDatabaseNamesFromPrivileges( - privileges: { - resource?: { - db?: string; - } - }[] -): string[] { - return privileges - .filter((priv) => { - // Find all named databases in priv list. - return ((priv.resource || {}).db || '').length > 0; - }) - .map((priv): string => { - // Return just the names. - return priv.resource!.db!; - }) - .filter((db, idx, arr) => { - // Make sure the list is unique. - return arr.indexOf(db) === idx; - }) - .sort(); -} - function getIconPath(isActiveConnection: boolean): { light: string; dark: string } { const LIGHT = path.join(getImagesPath(), 'light'); const DARK = path.join(getImagesPath(), 'dark'); @@ -68,7 +42,7 @@ export default class ConnectionTreeItem extends vscode.TreeItem isExpanded: boolean, connectionController: ConnectionController, cacheIsUpToDate: boolean, - existingChildrenCache: { [key: string]: DatabaseTreeItem } + childrenCache: { [key: string]: DatabaseTreeItem } // Existing cache. ) { super( connectionController.getSavedConnectionName(connectionId), @@ -86,7 +60,7 @@ export default class ConnectionTreeItem extends vscode.TreeItem this.connectionId = connectionId; this._connectionController = connectionController; this.isExpanded = isExpanded; - this._childrenCache = existingChildrenCache; + this._childrenCache = childrenCache; this.cacheIsUpToDate = cacheIsUpToDate; // Create a unique id to ensure the tree updates the expanded property. @@ -106,44 +80,18 @@ export default class ConnectionTreeItem extends vscode.TreeItem return element; } - async listDatabasesUserHasAccessTo( - dataService: MongoClient - ): Promise { - const db = dataService.db(); - - const adminDb = db.databaseName === 'admin' ? db : db.admin(); - const res = await adminDb.command({ - connectionStatus: 1, - showPrivileges: 1 - }, { - // `db.command` does not use the read preference set on the - // connection, so here we explicitly to specify it in the options. - readPreference: db.readPreference - }); - - const privileges = res.authInfo?.authenticatedUserPrivileges; - if (!privileges) { - return []; - } - - return getDatabaseNamesFromPrivileges(privileges); - } - async listDatabases(): Promise { const dataService = this._connectionController.getActiveDataService(); + if (dataService === null) { throw new Error('Not currently connected.'); } try { - const runListDatabases = promisify(dataService.listDatabases.bind(dataService)); - const dbs = await runListDatabases(); + const dbs = await dataService.listDatabases(); + return dbs.map(dbItem => dbItem.name); } catch (error) { - if (isNotAuthorized(error)) { - // Check for which databases privilages this user has, and list those. - return this.listDatabasesUserHasAccessTo(dataService.client.client); - } const printableError = error as { message: string }; throw new Error(`Unable to list databases: ${printableError.message}`); } diff --git a/src/explorer/databaseTreeItem.ts b/src/explorer/databaseTreeItem.ts index 2e199b711..d39dccff0 100644 --- a/src/explorer/databaseTreeItem.ts +++ b/src/explorer/databaseTreeItem.ts @@ -1,10 +1,9 @@ import * as vscode from 'vscode'; - -const path = require('path'); +import path from 'path'; import CollectionTreeItem from './collectionTreeItem'; -import TreeItemParent from './treeItemParentInterface'; import { getImagesPath } from '../extensionConstants'; +import TreeItemParent from './treeItemParentInterface'; function getIconPath(): { light: string; dark: string } { const LIGHT = path.join(getImagesPath(), 'light'); @@ -16,6 +15,8 @@ function getIconPath(): { light: string; dark: string } { }; } +import * as util from 'util'; + export default class DatabaseTreeItem extends vscode.TreeItem implements TreeItemParent, vscode.TreeDataProvider { contextValue = 'databaseTreeItem'; @@ -35,7 +36,7 @@ export default class DatabaseTreeItem extends vscode.TreeItem dataService: any, isExpanded: boolean, cacheIsUpToDate: boolean, - existingChildrenCache: { [key: string]: CollectionTreeItem } + childrenCache: { [key: string]: CollectionTreeItem } // Existing cache. ) { super( databaseName, @@ -49,7 +50,7 @@ export default class DatabaseTreeItem extends vscode.TreeItem this.isExpanded = isExpanded; this.cacheIsUpToDate = cacheIsUpToDate; - this._childrenCache = existingChildrenCache; + this._childrenCache = childrenCache; this.iconPath = getIconPath(); this.tooltip = this.databaseName; @@ -59,25 +60,6 @@ export default class DatabaseTreeItem extends vscode.TreeItem return element; } - listCollections(): Promise { - return new Promise((resolve, reject) => { - this._dataService.listCollections( - this.databaseName, - {}, // No filter. - (error: Error | undefined, collections: string[]) => { - if (error) { - const printableError = error as { message: string }; - return reject( - new Error(`Unable to list collections: ${printableError.message}`) - ); - } - - return resolve(collections); - } - ); - }); - } - async getChildren(): Promise { if (!this.isExpanded) { return []; @@ -112,7 +94,7 @@ export default class DatabaseTreeItem extends vscode.TreeItem } // List collections and build tree items. - const collections = await this.listCollections(); + const collections = await this._dataService.listCollections(this.databaseName); this.cacheIsUpToDate = true; @@ -223,23 +205,23 @@ export default class DatabaseTreeItem extends vscode.TreeItem return Promise.resolve(false); } - return new Promise((resolve) => { - this._dataService.dropDatabase( - databaseName, - (error: Error | null, successfullyDroppedDatabase = false) => { - if (error) { - const printableError = error as { message: string }; - void vscode.window.showErrorMessage( - `Drop database failed: ${printableError.message}` - ); - return resolve(false); - } + try { + const dropDatabase = util.promisify( + this._dataService.dropDatabase.bind(this._dataService) + ); + const successfullyDroppedDatabase = await dropDatabase( + databaseName + ); - this.isDropped = successfullyDroppedDatabase; + this.isDropped = successfullyDroppedDatabase; - return resolve(successfullyDroppedDatabase); - } + return successfullyDroppedDatabase; + } catch (error) { + const printableError = error as { message: string }; + void vscode.window.showErrorMessage( + `Drop database failed: ${printableError.message}` ); - }); + return false; + } } } diff --git a/src/explorer/documentListTreeItem.ts b/src/explorer/documentListTreeItem.ts index 5a629215c..a100725e9 100644 --- a/src/explorer/documentListTreeItem.ts +++ b/src/explorer/documentListTreeItem.ts @@ -1,9 +1,11 @@ +import * as util from 'util'; import * as vscode from 'vscode'; import numeral from 'numeral'; + import { createLogger } from '../logging'; import DocumentTreeItem from './documentTreeItem'; -import TreeItemParent from './treeItemParentInterface'; import { getImagesPath } from '../extensionConstants'; +import TreeItemParent from './treeItemParentInterface'; const path = require('path'); const log = createLogger('tree view document list'); @@ -111,7 +113,7 @@ export default class DocumentListTreeItem extends vscode.TreeItem cachedDocumentCount: number | null, refreshDocumentCount: () => Promise, cacheIsUpToDate: boolean, - existingCache: Array + childrenCache: Array // Existing cache. ) { super(ITEM_LABEL, getCollapsableStateForDocumentList(isExpanded, type)); @@ -129,7 +131,7 @@ export default class DocumentListTreeItem extends vscode.TreeItem this.refreshDocumentCount = refreshDocumentCount; - this._childrenCache = existingCache; + this._childrenCache = childrenCache; this.cacheIsUpToDate = cacheIsUpToDate; if (this._documentCount !== null) { @@ -144,32 +146,6 @@ export default class DocumentListTreeItem extends vscode.TreeItem return element; } - async getDocuments(): Promise<[]> { - log.info( - `fetching ${this._maxDocumentsToShow} documents from namespace ${this.namespace}` - ); - - return new Promise((resolve, reject) => { - this._dataService.find( - this.namespace, - { - /* No filter */ - }, - { - limit: this._maxDocumentsToShow - }, - (err: Error, documents: []) => { - if (err) { - void vscode.window.showErrorMessage(`Unable to list documents: ${err}`); - return reject(err); - } - - resolve(documents); - } - ); - }); - } - hasMoreDocumentsToShow(): boolean { if (this._documentCount === null) { return false; @@ -215,7 +191,12 @@ export default class DocumentListTreeItem extends vscode.TreeItem let documents; try { - documents = await this.getDocuments(); + const find = util.promisify(this._dataService.find.bind(this._dataService)); + documents = await find( + this.namespace, + {}, // No filter. + { limit: this._maxDocumentsToShow } + ); } catch (err) { return Promise.reject(err); } diff --git a/src/explorer/explorerTreeController.ts b/src/explorer/explorerTreeController.ts index a23877378..9ae9ef2d1 100644 --- a/src/explorer/explorerTreeController.ts +++ b/src/explorer/explorerTreeController.ts @@ -1,22 +1,22 @@ import * as vscode from 'vscode'; + import ConnectionController, { DataServiceEventTypes } from '../connectionController'; -import { DOCUMENT_ITEM } from './documentTreeItem'; +import ConnectionTreeItem from './connectionTreeItem'; import { createLogger } from '../logging'; +import { DOCUMENT_ITEM } from './documentTreeItem'; import { DOCUMENT_LIST_ITEM, CollectionTypes } from './documentListTreeItem'; -import ConnectionTreeItem from './connectionTreeItem'; -import { SavedConnection } from '../storage/storageController'; -import { sortTreeItemsByLabel } from './treeItemUtils'; import EXTENSION_COMMANDS from '../commands'; +import { SavedConnectionInfo } from '../connectionController'; +import { sortTreeItemsByLabel } from './treeItemUtils'; const log = createLogger('explorer controller'); export default class ExplorerTreeController implements vscode.TreeDataProvider { - _connectionController: ConnectionController; - _connectionTreeItems: { [key: string]: ConnectionTreeItem }; - contextValue = 'explorerTreeController'; + private _connectionController: ConnectionController; + private _connectionTreeItems: { [key: string]: ConnectionTreeItem }; constructor(connectionController: ConnectionController) { this._onDidChangeTreeData = new vscode.EventEmitter(); @@ -59,7 +59,7 @@ implements vscode.TreeDataProvider { return; } - this.onTreeItemUpdate(); + this._onTreeItemUpdate(); }); treeView.onDidExpandElement( @@ -79,7 +79,7 @@ implements vscode.TreeDataProvider { return resolve(); } - this.onTreeItemUpdate(); + this._onTreeItemUpdate(); resolve(); }, @@ -98,10 +98,10 @@ implements vscode.TreeDataProvider { if (selectedItem.isShowMoreItem) { selectedItem.onShowMoreClicked(); - this.onTreeItemUpdate(); + this._onTreeItemUpdate(); } - if (selectedItem.contextValue === DOCUMENT_ITEM) { + if (selectedItem._contextValue === DOCUMENT_ITEM) { await vscode.commands.executeCommand( EXTENSION_COMMANDS.MDB_OPEN_MONGODB_DOCUMENT_FROM_TREE, event.selection[0] @@ -109,7 +109,7 @@ implements vscode.TreeDataProvider { } if ( - selectedItem.contextValue === DOCUMENT_LIST_ITEM && + selectedItem._contextValue === DOCUMENT_LIST_ITEM && selectedItem.type === CollectionTypes.view ) { await vscode.commands.executeCommand( @@ -130,7 +130,7 @@ implements vscode.TreeDataProvider { return true; }; - onTreeItemUpdate(): void { + private _onTreeItemUpdate(): void { this._onDidChangeTreeData.fire(null); } @@ -146,7 +146,7 @@ implements vscode.TreeDataProvider { this._connectionTreeItems = {}; // Create new connection tree items, using cached children wherever possible. - connections.forEach((connection: SavedConnection) => { + connections.forEach((connection: SavedConnectionInfo) => { const isActiveConnection = connection.id === this._connectionController.getActiveConnectionId(); const isBeingConnectedTo = diff --git a/src/explorer/indexListTreeItem.ts b/src/explorer/indexListTreeItem.ts index fc944cc84..26b4d7fad 100644 --- a/src/explorer/indexListTreeItem.ts +++ b/src/explorer/indexListTreeItem.ts @@ -1,13 +1,12 @@ +import * as util from 'util'; import * as vscode from 'vscode'; -const path = require('path'); +import { DataService } from 'mongodb-data-service'; +import path from 'path'; -import { createLogger } from '../logging'; +import { getImagesPath } from '../extensionConstants'; import IndexTreeItem, { IndexModel } from './indexTreeItem'; -import TreeItemParent from './treeItemParentInterface'; import { sortTreeItemsByLabel } from './treeItemUtils'; -import { getImagesPath } from '../extensionConstants'; - -const log = createLogger('tree view indexes list'); +import TreeItemParent from './treeItemParentInterface'; const ITEM_LABEL = 'Indexes'; @@ -23,26 +22,25 @@ function getIconPath(): { light: string; dark: string } { export default class IndexListTreeItem extends vscode.TreeItem implements TreeItemParent, vscode.TreeDataProvider { - cacheIsUpToDate = false; - private _childrenCache: IndexTreeItem[] = []; - - contextValue = 'indexListTreeItem'; - collectionName: string; databaseName: string; - namespace: string; + isExpanded: boolean; + + cacheIsUpToDate = false; + contextValue = 'indexListTreeItem'; - private _dataService: any; + private _namespace: string; + private _dataService: DataService; - isExpanded: boolean; + private _childrenCache: IndexTreeItem[] = []; constructor( collectionName: string, databaseName: string, - dataService: any, + dataService: DataService, isExpanded: boolean, cacheIsUpToDate: boolean, - existingCache: IndexTreeItem[] + childrenCache: IndexTreeItem[] // Existing cache. ) { super( ITEM_LABEL, @@ -53,14 +51,11 @@ export default class IndexListTreeItem extends vscode.TreeItem this.collectionName = collectionName; this.databaseName = databaseName; - this.namespace = `${databaseName}.${collectionName}`; - + this._namespace = `${databaseName}.${collectionName}`; this._dataService = dataService; - this.isExpanded = isExpanded; - - this._childrenCache = existingCache; this.cacheIsUpToDate = cacheIsUpToDate; + this._childrenCache = childrenCache; this.iconPath = getIconPath(); this.tooltip = 'Collection Indexes'; @@ -70,28 +65,6 @@ export default class IndexListTreeItem extends vscode.TreeItem return element; } - getIndexes(): Promise { - const namespace = this.namespace; - - log.info(`fetching indexes from namespace ${namespace}`); - - return new Promise((resolve, reject) => { - this._dataService.indexes( - namespace, - { - /* No options */ - }, - (err: Error, indexes: IndexModel[]) => { - if (err) { - return reject(err); - } - - return resolve(indexes); - } - ); - }); - } - async getChildren(): Promise { if (!this.isExpanded) { return []; @@ -115,16 +88,20 @@ export default class IndexListTreeItem extends vscode.TreeItem return this._childrenCache; } - const indexes = await this.getIndexes(); + const fetchIndexes = util.promisify( + this._dataService.indexes.bind(this._dataService) + ); + const indexes = await fetchIndexes( + this._namespace, + {} // No options. + ); this.cacheIsUpToDate = true; if (indexes) { - const namespace = this.namespace; - this._childrenCache = sortTreeItemsByLabel( indexes.map((index: IndexModel) => { - return new IndexTreeItem(index, namespace, false /* Not expanded. */); + return new IndexTreeItem(index, this._namespace, false /* Not expanded. */); }) ) as IndexTreeItem[]; } else { diff --git a/src/explorer/schemaTreeItem.ts b/src/explorer/schemaTreeItem.ts index bc91e5d06..6e83aae18 100644 --- a/src/explorer/schemaTreeItem.ts +++ b/src/explorer/schemaTreeItem.ts @@ -1,12 +1,13 @@ +import * as util from 'util'; import * as vscode from 'vscode'; import parseSchema = require('mongodb-schema'); -const path = require('path'); +import path from 'path'; import { createLogger } from '../logging'; -import TreeItemParent from './treeItemParentInterface'; -import { MAX_DOCUMENTS_VISIBLE } from './documentListTreeItem'; import FieldTreeItem from './fieldTreeItem'; import { getImagesPath } from '../extensionConstants'; +import TreeItemParent from './treeItemParentInterface'; +import { MAX_DOCUMENTS_VISIBLE } from './documentListTreeItem'; const log = createLogger('tree view document list'); @@ -92,33 +93,16 @@ export default class SchemaTreeItem extends vscode.TreeItem return element; } - async fetchDocumentsForSchema(): Promise { - return new Promise((resolve, reject) => { - const namespace = `${this.databaseName}.${this.collectionName}`; - - this._dataService.find( - namespace, - { - /* No filter */ - }, - { - limit: MAX_DOCUMENTS_VISIBLE - }, - (findError: Error | undefined, documents: any[]) => { - if (findError) { - return reject(new Error(`Unable to list documents: ${findError}`)); - } - - return resolve(documents); - } - ); - }); - } - async getSchema(): Promise { + const namespace = `${this.databaseName}.${this.collectionName}`; let documents; try { - documents = await this.fetchDocumentsForSchema(); + const find = util.promisify(this._dataService.find.bind(this._dataService)); + documents = await find( + namespace, + {}, // No filter. + { limit: MAX_DOCUMENTS_VISIBLE } + ); } catch (err) { return Promise.reject(err); } diff --git a/src/language/README.md b/src/language/README.md index 932d244fc..f9262fda4 100644 --- a/src/language/README.md +++ b/src/language/README.md @@ -42,23 +42,6 @@ You can also configure logging from VSCode setting interface. The logs will be printed in the `MongoDB Language Server` Output Channel. -These logs are useful for developing and testing the Language Server, but they can be lengthy and hard to read. To visualize Cleint/Server logs use the [Language Server Protocol Inspector](https://github.com/Microsoft/language-server-protocol-inspector). - -#### Strem Logs To LSP Inspector - -- Install the [Language Server Protocol Inspector](https://marketplace.visualstudio.com/items?itemName=octref.lsp-inspector-webview) webview extension. -- Run the MongoDB extension. -- Activate the extension using any activation command. -- Open a playground. -- Check `mongodbLanguageServer.trace.server` settings. -- Run command "LSP Inspector: Start LSP Inspector" to open the LSP inspector webview. -- Run command "LSP Inspector: Start Stream LSP Logs" to start streaming logs to the LSP inspector port specified by the `languageServerExample.port` setting (The default value is 7000). - -![MongoDB Language Server log settings](./lsp-stream-logs.png) - -https://github.com/microsoft/vscode-extension-samples/tree/master/lsp-log-streaming-sample -https://github.com/microsoft/vscode-extension-samples/tree/master/lsp-sample - #### LSP Notifications From the server: diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index cf7f96152..afb683271 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import { MongoClientOptions } from 'mongodb'; import { LanguageClient, LanguageClientOptions, @@ -7,8 +8,7 @@ import { TransportKind, CancellationTokenSource } from 'vscode-languageclient/node'; -import WebSocket from 'ws'; -import { workspace, ExtensionContext, OutputChannel } from 'vscode'; +import { workspace, ExtensionContext } from 'vscode'; import { createLogger } from '../logging'; import { @@ -19,10 +19,8 @@ import { PlaygroundTextAndSelection } from '../types/playgroundType'; import { ServerCommands } from './serverCommands'; -import { ConnectionOptions } from '../types/connectionOptionsType'; const log = createLogger('LanguageServerController'); -let socket: WebSocket | null; /** * This controller manages the language server and client. @@ -59,31 +57,6 @@ export default class LanguageServerController { } }; - // Hijacks all LSP logs and redirect them to a specific port through WebSocket connection - const channel = vscode.window.createOutputChannel( - 'MongoDB Language Server' - ); - let logInspector = ''; - - const websocketOutputChannel = { - name: 'websocket', - // Only append the logs but send them later - append(value: string) { - logInspector += value; - }, - appendLine(value: string) { - logInspector += value; - channel.appendLine(logInspector); - - // Don't send logs until WebSocket initialization - if (socket && socket.readyState === WebSocket.OPEN) { - socket.send(logInspector); - } - - logInspector = ''; - } - } as OutputChannel; - // Options to control the language client const clientOptions: LanguageClientOptions = { // Register the server for mongodb documents @@ -95,8 +68,9 @@ export default class LanguageServerController { // Notify the server about file changes in the workspace fileEvents: workspace.createFileSystemWatcher('**/*') }, - // Attach WebSocket OutputChannel - outputChannel: websocketOutputChannel + outputChannel: vscode.window.createOutputChannel( + 'MongoDB Language Server' + ) }; log.info('Creating MongoDB Language Server', { @@ -193,7 +167,7 @@ export default class LanguageServerController { async connectToServiceProvider(params: { connectionId: string, connectionString: string; - connectionOptions: ConnectionOptions + connectionOptions: MongoClientOptions }): Promise { await this._client.onReady(); await this._client.sendRequest( @@ -209,16 +183,6 @@ export default class LanguageServerController { ); } - startStreamLanguageServerLogs(): Promise { - const socketPort = workspace - .getConfiguration('languageServerExample') - .get('port', 7000); - - socket = new WebSocket(`ws://localhost:${socketPort}`); - - return Promise.resolve(true); - } - cancelAll(): void { // Send a request for cancellation. As a result // the associated CancellationToken will be notified of the cancellation, diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 621628fea..2010b0097 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -8,13 +8,13 @@ import { MarkupContent, MarkupKind } from 'vscode-languageserver/node'; +import { MongoClientOptions } from 'mongodb'; import path from 'path'; import { signatures } from '@mongosh/shell-api'; import translator from '@mongosh/i18n'; import { Worker as WorkerThreads } from 'worker_threads'; import { CollectionItem } from '../types/collectionItemType'; -import { ConnectionOptions } from '../types/connectionOptionsType'; import { ServerCommands } from './serverCommands'; import { ShellExecuteAllResult, @@ -35,7 +35,7 @@ export default class MongoDBService { _connection: Connection; _connectionId?: string; _connectionString?: string; - _connectionOptions?: ConnectionOptions; + _connectionOptions?: MongoClientOptions; _cachedDatabases: CompletionItem[] | [] = []; _cachedFields: { [namespace: string]: CompletionItem[] } | {} = {}; _cachedCollections: { [database: string]: CollectionItem[] } | {} = {}; @@ -54,7 +54,7 @@ export default class MongoDBService { return this._connectionString; } - get connectionOptions(): ConnectionOptions | undefined { + get connectionOptions(): MongoClientOptions | undefined { return this._connectionOptions; } @@ -69,7 +69,7 @@ export default class MongoDBService { async connectToServiceProvider(params: { connectionId: string; connectionString: string; - connectionOptions: ConnectionOptions; + connectionOptions: MongoClientOptions; }): Promise { this._clearCurrentSessionConnection(); this._clearCurrentSessionFields(); diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index c5dd66e45..8dd19531d 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import ActiveConnectionCodeLensProvider from './editors/activeConnectionCodeLensProvider'; +import CodeActionProvider from './editors/codeActionProvider'; import ConnectionController from './connectionController'; import ConnectionTreeItem from './explorer/connectionTreeItem'; import { createLogger } from './logging'; @@ -22,11 +23,11 @@ import { HelpExplorer, CollectionTreeItem } from './explorer'; -import CodeActionProvider from './editors/codeActionProvider'; +import ExportToLanguageCodeLensProvider from './editors/exportToLanguageCodeLensProvider'; +import { ExportToLanguages } from './types/playgroundType'; import EXTENSION_COMMANDS from './commands'; import FieldTreeItem from './explorer/fieldTreeItem'; import IndexListTreeItem from './explorer/indexListTreeItem'; -import ExportToLanguageCodeLensProvider from './editors/exportToLanguageCodeLensProvider'; import { LanguageServerController } from './language'; import launchMongoShell from './commands/launchMongoShell'; import SchemaTreeItem from './explorer/schemaTreeItem'; @@ -36,7 +37,6 @@ import TelemetryService from './telemetry/telemetryService'; import PlaygroundsTreeItem from './explorer/playgroundsTreeItem'; import PlaygroundResultProvider from './editors/playgroundResultProvider'; import WebviewController from './views/webviewController'; -import { ExportToLanguages } from './types/playgroundType'; const log = createLogger('commands'); @@ -97,7 +97,6 @@ export default class MDBExtensionController implements vscode.Disposable { this._exportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); this._codeActionProvider = new CodeActionProvider(); this._playgroundController = new PlaygroundController( - context, this._connectionController, this._languageServerController, this._telemetryService, @@ -147,15 +146,16 @@ export default class MDBExtensionController implements vscode.Disposable { // Register our extension's commands. These are the event handlers and // control the functionality of our extension. + // ------ CONNECTION ------ // + this.registerCommand(EXTENSION_COMMANDS.MDB_OPEN_OVERVIEW_PAGE, () => + this._webviewController.openWebview(this._context) + ); this.registerCommand(EXTENSION_COMMANDS.MDB_CONNECT, () => this._webviewController.openWebview(this._context) ); this.registerCommand(EXTENSION_COMMANDS.MDB_CONNECT_WITH_URI, () => this._connectionController.connectWithURI() ); - this.registerCommand(EXTENSION_COMMANDS.MDB_OPEN_OVERVIEW_PAGE, () => - this._webviewController.openWebview(this._context) - ); this.registerCommand(EXTENSION_COMMANDS.MDB_DISCONNECT, () => this._connectionController.disconnect() ); @@ -166,6 +166,7 @@ export default class MDBExtensionController implements vscode.Disposable { this._connectionController.changeActiveConnection() ); + // ------ SHELL ------ // this.registerCommand(EXTENSION_COMMANDS.MDB_OPEN_MDB_SHELL, () => launchMongoShell(this._connectionController) ); @@ -174,6 +175,7 @@ export default class MDBExtensionController implements vscode.Disposable { () => launchMongoShell(this._connectionController) ); + // ------ PLAYGROUND ------ // this.registerCommand(EXTENSION_COMMANDS.MDB_CREATE_PLAYGROUND, () => this._playgroundController.createPlayground() ); @@ -195,6 +197,8 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand(EXTENSION_COMMANDS.MDB_REFRESH_PLAYGROUNDS, () => this._playgroundsExplorer.refresh() ); + + // ------ EXPORT TO LANGUAGE ------ // this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_PYTHON, () => this._playgroundController.exportToLanguage(ExportToLanguages.PYTHON) ); @@ -210,6 +214,8 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand(EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, (exportToLanguageAddons) => this._playgroundController.changeExportToLanguageAddons(exportToLanguageAddons) ); + + // ------ DOCUMENTS ------ // this.registerCommand( EXTENSION_COMMANDS.MDB_OPEN_MONGODB_DOCUMENT_FROM_CODE_LENS, (data: EditDocumentInfo) => { @@ -222,11 +228,6 @@ export default class MDBExtensionController implements vscode.Disposable { this._editorsController.saveMongoDBDocument() ); - this.registerCommand( - EXTENSION_COMMANDS.MDB_START_LANGUAGE_STREAM_LOGS, - () => this._languageServerController.startStreamLanguageServerLogs() - ); - this.registerEditorCommands(); this.registerTreeViewCommands(); @@ -295,7 +296,7 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand( EXTENSION_COMMANDS.MDB_COPY_CONNECTION_STRING, async (element: ConnectionTreeItem): Promise => { - const connectionString = this._connectionController.getConnectionStringFromConnectionId( + const connectionString = await this._connectionController.getConnectionStringByConnectionId( element.connectionId ); @@ -315,7 +316,6 @@ export default class MDBExtensionController implements vscode.Disposable { (element: ConnectionTreeItem) => this._connectionController.renameConnection(element.connectionId) ); - this.registerCommand( EXTENSION_COMMANDS.MDB_ADD_DATABASE, async (element: ConnectionTreeItem): Promise => { diff --git a/src/storage/storageController.ts b/src/storage/storageController.ts index 04423c418..7ea2c5ed9 100644 --- a/src/storage/storageController.ts +++ b/src/storage/storageController.ts @@ -1,6 +1,9 @@ import * as vscode from 'vscode'; import { v4 as uuidv4 } from 'uuid'; +import { ConnectionInfo, getConnectionTitle } from 'mongodb-data-service'; +import { SavedConnectionInfo } from '../connectionController'; + export enum StorageVariables { // Only exists on globalState. GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW = 'GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW', @@ -24,52 +27,50 @@ export enum DefaultSavingLocations { 'Session Only' = 'Session Only' } -// A saved connection does not contain the connection information, -// just metadata about the connection and an id for referencing secure storage. -export type SavedConnection = { - id: string; // uuidv4 - name: string; // Possibly user given name, not unique. - storageLocation: StorageScope; +export type ConnectionsFromStorage = { + [key: string]: SavedConnectionInfo }; -type StoredConnectionsType = { [key: string]: SavedConnection } | undefined; +type StoredVariableName = StorageVariables.GLOBAL_USER_ID | + StorageVariables.GLOBAL_SAVED_CONNECTIONS | + StorageVariables.WORKSPACE_SAVED_CONNECTIONS | + StorageVariables.GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW; + +type StoredItem = + T extends StorageVariables.GLOBAL_USER_ID ? string : + T extends StorageVariables.GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW ? boolean : + T extends StorageVariables.GLOBAL_SAVED_CONNECTIONS ? ConnectionsFromStorage : + T extends StorageVariables.WORKSPACE_SAVED_CONNECTIONS ? ConnectionsFromStorage : + never; export default class StorageController { - _globalState: vscode.Memento; - _workspaceState: vscode.Memento; + _storage: { [StorageScope.GLOBAL]: vscode.Memento, [StorageScope.WORKSPACE]: vscode.Memento }; constructor(context: vscode.ExtensionContext) { - this._globalState = context.globalState; - this._workspaceState = context.workspaceState; + this._storage = { + [StorageScope.GLOBAL]: context.globalState, + [StorageScope.WORKSPACE]: context.workspaceState + }; } - get(variableName: StorageVariables, storageScope?: StorageScope): any { - if (storageScope === StorageScope.WORKSPACE) { - return this._workspaceState.get(variableName); - } - - return this._globalState.get(variableName); + get(variableName: T, storageScope: StorageScope = StorageScope.GLOBAL): StoredItem { + return this._storage[storageScope].get(variableName); } // Update something in the storage. Defaults to global storage (not workspace). update( variableName: StorageVariables, - value: any, - storageScope?: StorageScope + value: boolean | string | ConnectionsFromStorage, + storageScope: StorageScope = StorageScope.GLOBAL ): Thenable { - if (storageScope === StorageScope.WORKSPACE) { - void this._workspaceState.update(variableName, value); - return Promise.resolve(); - } - - void this._globalState.update(variableName, value); + this._storage[storageScope].update(variableName, value); return Promise.resolve(); } getUserID(): string { let globalUserId = this.get(StorageVariables.GLOBAL_USER_ID); - if (globalUserId) { + if (globalUserId && typeof globalUserId === 'string') { return globalUserId; } @@ -79,22 +80,14 @@ export default class StorageController { return globalUserId; } - saveConnectionToGlobalStore( - connection: SavedConnection - ): Thenable { + async saveConnectionToGlobalStore( + savedConnectionInfo: SavedConnectionInfo + ): Promise { // Get the current save connections. - let globalConnections: - | { [key: string]: SavedConnection } - | undefined = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS); - - if (!globalConnections) { - globalConnections = {}; - } - - connection.storageLocation = StorageScope.GLOBAL; + const globalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS) || {}; // Add the new connection. - globalConnections[connection.id] = connection; + globalConnections[savedConnectionInfo.id] = savedConnectionInfo; // Update the store. return this.update( @@ -103,22 +96,17 @@ export default class StorageController { ); } - saveConnectionToWorkspaceStore( - connection: SavedConnection - ): Thenable { + async saveConnectionToWorkspaceStore( + savedConnectionInfo: SavedConnectionInfo + ): Promise { // Get the current save connections. - let workspaceConnections: StoredConnectionsType = this.get( + const workspaceConnections = this.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE - ); - if (!workspaceConnections) { - workspaceConnections = {}; - } - - connection.storageLocation = StorageScope.WORKSPACE; + ) || {}; // Add the new connection. - workspaceConnections[connection.id] = connection; + workspaceConnections[savedConnectionInfo.id] = savedConnectionInfo; // Update the store. return this.update( @@ -128,7 +116,60 @@ export default class StorageController { ); } - storeNewConnection(newConnection: SavedConnection): Thenable { + getPreferedStorageLocationFromConfiguration(): StorageScope { + const defaultConnectionSavingLocation = vscode.workspace + .getConfiguration('mdb.connectionSaving') + .get('defaultConnectionSavingLocation'); + + if (defaultConnectionSavingLocation === DefaultSavingLocations.Workspace) { + return StorageScope.WORKSPACE; + } + + if (defaultConnectionSavingLocation === DefaultSavingLocations.Global) { + return StorageScope.GLOBAL; + } + + return StorageScope.NONE; + } + + async getStorageLocationFromPrompt() { + const storeOnWorkspace = 'Save the connection on this workspace'; + const storeGlobally = 'Save the connection globally on vscode'; + // Prompt the user where they want to save the new connection. + const chosenConnectionSavingLocation = await vscode.window.showQuickPick( + [ + storeOnWorkspace, + storeGlobally, + "Don't save this connection (it will be lost when the session is closed)" + ], + { + placeHolder: + 'Where would you like to save this new connection? (This message can be disabled in the extension settings.)' + } + ); + + if (chosenConnectionSavingLocation === DefaultSavingLocations.Workspace) { + return StorageScope.WORKSPACE; + } + + if (chosenConnectionSavingLocation === DefaultSavingLocations.Global) { + return StorageScope.GLOBAL; + } + + return StorageScope.NONE; + } + + async storeNewConnection(safeConnectionInfo: ConnectionInfo): Promise { + const name = getConnectionTitle(safeConnectionInfo); + const savedConnectionInfo = { + id: safeConnectionInfo.id || uuidv4(), + name, + // To begin we just store it on the session, the storage controller + // handles changing this based on user preference. + storageLocation: StorageScope.NONE, + connectionOptions: safeConnectionInfo.connectionOptions + }; + const dontShowSaveLocationPrompt = vscode.workspace .getConfiguration('mdb.connectionSaving') .get('hideOptionToChooseWhereToSaveNewConnections'); @@ -136,57 +177,24 @@ export default class StorageController { if (dontShowSaveLocationPrompt === true) { // The user has chosen not to show the message on where to save the connection. // Save the connection in their default preference. - const preferedStorageScope = vscode.workspace - .getConfiguration('mdb.connectionSaving') - .get('defaultConnectionSavingLocation'); - - if (preferedStorageScope === DefaultSavingLocations.Workspace) { - return this.saveConnectionToWorkspaceStore(newConnection); - } else if (preferedStorageScope === DefaultSavingLocations.Global) { - return this.saveConnectionToGlobalStore(newConnection); - } + savedConnectionInfo.storageLocation = this.getPreferedStorageLocationFromConfiguration(); + } else { + savedConnectionInfo.storageLocation = await this.getStorageLocationFromPrompt(); + } - // The user prefers for the connections not to be saved. - return Promise.resolve(); + if (savedConnectionInfo.storageLocation === StorageScope.WORKSPACE) { + await this.saveConnectionToWorkspaceStore(savedConnectionInfo); + } else if (savedConnectionInfo.storageLocation === StorageScope.GLOBAL) { + await this.saveConnectionToGlobalStore(savedConnectionInfo); } - return new Promise((resolve) => { - const storeOnWorkspace = 'Save the connection on this workspace'; - const storeGlobally = 'Save the connection globally on vscode'; - // Prompt the user where they want to save the new connection. - void vscode.window - .showQuickPick( - [ - storeOnWorkspace, - storeGlobally, - "Don't save this connection (it will be lost when the session is closed)" - ], - { - placeHolder: - 'Where would you like to save this new connection? (This message can be disabled in the extension settings.)' - } - ) - .then((saveConnectionScope) => { - if (saveConnectionScope === storeOnWorkspace) { - return this.saveConnectionToWorkspaceStore(newConnection).then( - resolve - ); - } else if (saveConnectionScope === storeGlobally) { - return this.saveConnectionToGlobalStore(newConnection).then( - resolve - ); - } - - // Store it on the session (don't save anywhere). - return resolve(); - }); - }); + return savedConnectionInfo; } removeConnection(connectionId: string): void { // See if the connection exists in the saved global or workspace connections // and remove it if it is. - const globalStoredConnections: StoredConnectionsType = this.get( + const globalStoredConnections = this.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS ); if (globalStoredConnections && globalStoredConnections[connectionId]) { @@ -197,7 +205,7 @@ export default class StorageController { ); } - const workspaceStoredConnections: StoredConnectionsType = this.get( + const workspaceStoredConnections = this.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE ); @@ -219,11 +227,8 @@ export default class StorageController { const savedGlobalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS, StorageScope.GLOBAL); return ( - savedWorkspaceConnections - && Object.keys(savedWorkspaceConnections).length > 0 - ) || ( - savedGlobalConnections - && Object.keys(savedGlobalConnections).length > 0 + (savedWorkspaceConnections && Object.keys(savedWorkspaceConnections).length > 0) || + (savedGlobalConnections && Object.keys(savedGlobalConnections).length > 0) ); } } diff --git a/src/telemetry/connectionTelemetry.ts b/src/telemetry/connectionTelemetry.ts index 6d08abc81..1c87070a2 100644 --- a/src/telemetry/connectionTelemetry.ts +++ b/src/telemetry/connectionTelemetry.ts @@ -1,11 +1,11 @@ -import type { Document, MongoClient } from 'mongodb'; +import { DataService } from 'mongodb-data-service'; import { getCloudInfo } from 'mongodb-cloud-info'; import mongoDBBuildInfo from 'mongodb-build-info'; import { ConnectionTypes } from '../connectionController'; import { createLogger } from '../logging'; -import { ConnectionModel } from '../types/connectionModelType'; +const log = createLogger('connection telemetry helper'); const { version } = require('../../package.json'); export type NewConnectionTelemetryEventProperties = { @@ -16,13 +16,13 @@ export type NewConnectionTelemetryEventProperties = { is_data_lake: boolean; is_enterprise: boolean; is_public_cloud?: boolean; - dl_version?: string; + dl_version?: string | null; public_cloud_name?: string | null; is_genuine: boolean; non_genuine_server_name: string | null; server_version: string; - server_arch: string; - server_os: string; + server_arch?: string; + server_os?: string; is_used_connect_screen: boolean; is_used_command_palette: boolean; is_used_saved_connection: boolean; @@ -35,8 +35,6 @@ type CloudInfo = { publicCloudName?: string | null; }; -const log = createLogger('connection telemetry helper'); - async function getCloudInfoFromDataService( firstServerHostname: string ): Promise { @@ -78,65 +76,31 @@ async function getCloudInfoFromDataService( } export async function getConnectionTelemetryProperties( - dataService: MongoClient, - model: ConnectionModel, + dataService: DataService, connectionType: ConnectionTypes ): Promise { - const adminDb = dataService.db('admin'); - - // buildInfo doesn't require any privileges to run, so if it fails, - // something went wrong and we should throw the error. - const buildInfo = await adminDb.command({ - buildInfo: 1 - }); - - const cloudInfo = await getCloudInfoFromDataService( - model.hosts[0].host - ); - - let cmdLineOpts: null | Document = null; - try { - cmdLineOpts = await adminDb.command({ - getCmdLineOpts: 1 - }); - } catch (e) { /* Silently continue when can't retrieve command line opts. */ } - - const { - isGenuine, - serverName: nonGenuineServerName - } = mongoDBBuildInfo.getGenuineMongoDB(buildInfo, cmdLineOpts); - const { - isDataLake, - dlVersion - } = mongoDBBuildInfo.getDataLake(buildInfo); - - const { - serverOs, - serverArch - } = mongoDBBuildInfo.getBuildEnv(buildInfo); - - const { - driverUrl, - driverAuthMechanism - } = model.getAttributes({ - derived: true - }); - - /* eslint-disable camelcase */ + const connectionString = dataService.getConnectionString(); + const firstHost = connectionString.hosts[0] || ''; + const [hostname] = firstHost.split(':'); + const authMechanism = connectionString.searchParams.get('authMechanism'); + const username = connectionString.username ? 'DEFAULT' : 'NONE'; + const authStrategy = authMechanism ? authMechanism : username; + const instance = await dataService.instance(); + const cloudInfo = await getCloudInfoFromDataService(hostname); const preparedProperties: NewConnectionTelemetryEventProperties = { - auth_strategy: driverAuthMechanism, - is_atlas: mongoDBBuildInfo.isAtlas(driverUrl), - is_localhost: mongoDBBuildInfo.isLocalhost(driverUrl), - is_data_lake: isDataLake, - is_enterprise: mongoDBBuildInfo.isEnterprise(buildInfo), + auth_strategy: authStrategy, + is_atlas: mongoDBBuildInfo.isAtlas(connectionString.toString()), + is_localhost: mongoDBBuildInfo.isLocalhost(connectionString.toString()), + is_data_lake: instance.dataLake.isDataLake, + is_enterprise: instance.build.isEnterprise, is_public_cloud: cloudInfo.isPublicCloud, - dl_version: dlVersion, + dl_version: instance.dataLake.version, public_cloud_name: cloudInfo.publicCloudName, - is_genuine: isGenuine, - non_genuine_server_name: nonGenuineServerName, - server_version: buildInfo.version, - server_arch: serverArch, - server_os: serverOs, + is_genuine: instance.genuineMongoDB.isGenuine, + non_genuine_server_name: instance.genuineMongoDB.dbType, + server_version: instance.build.version, + server_arch: instance.host.arch, + server_os: instance.host.os, is_used_connect_screen: connectionType === ConnectionTypes.CONNECTION_FORM, is_used_command_palette: @@ -145,7 +109,6 @@ export async function getConnectionTelemetryProperties( connectionType === ConnectionTypes.CONNECTION_ID, vscode_mdb_extension_version: version }; - /* eslint-enable camelcase */ return preparedProperties; } diff --git a/src/telemetry/telemetryService.ts b/src/telemetry/telemetryService.ts index 2d354ebce..86f9c09da 100644 --- a/src/telemetry/telemetryService.ts +++ b/src/telemetry/telemetryService.ts @@ -1,24 +1,22 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { config } from 'dotenv'; +import { DataService } from 'mongodb-data-service'; import fs from 'fs'; import SegmentAnalytics from 'analytics-node'; -import type { MongoClient } from 'mongodb'; -import { ConnectionModel } from '../types/connectionModelType'; import { ConnectionTypes } from '../connectionController'; import { createLogger } from '../logging'; import { DocumentSource } from '../documentSource'; -import type { ShellExecuteAllResult } from '../types/playgroundType'; -import { StorageController } from '../storage'; import { - NewConnectionTelemetryEventProperties, - getConnectionTelemetryProperties + getConnectionTelemetryProperties, + NewConnectionTelemetryEventProperties } from './connectionTelemetry'; - -const { version } = require('../../package.json'); +import type { ShellExecuteAllResult } from '../types/playgroundType'; +import { StorageController } from '../storage'; const log = createLogger('telemetry'); +const { version } = require('../../package.json'); type PlaygroundTelemetryEventProperties = { type: string | null; @@ -29,7 +27,7 @@ type PlaygroundTelemetryEventProperties = { export type SegmentProperties = { event: string; userId: string; - properties: any; + properties: unknown; }; type LinkClickedTelemetryEventProperties = { @@ -73,12 +71,13 @@ export enum TelemetryEventTypes { * This controller manages telemetry. */ export default class TelemetryService { - _context: vscode.ExtensionContext; - _shouldTrackTelemetry: boolean; // When tests run the extension, we don't want to track telemetry. _segmentAnalytics?: SegmentAnalytics; _segmentUserID: string; // The user uuid from the global storage. _segmentKey?: string; // The segment API write key. + private _context: vscode.ExtensionContext; + private _shouldTrackTelemetry: boolean; // When tests run the extension, we don't want to track telemetry. + constructor( storageController: StorageController, context: vscode.ExtensionContext, @@ -106,7 +105,7 @@ export default class TelemetryService { }); } - _readSegmentKey(): string | undefined { + private _readSegmentKey(): string | undefined { config({ path: path.join(this._context.extensionPath, '.env') }); try { @@ -198,8 +197,7 @@ export default class TelemetryService { } async trackNewConnection( - dataService: MongoClient, - model: ConnectionModel, + dataService: DataService, connectionType: ConnectionTypes ): Promise { try { @@ -209,7 +207,6 @@ export default class TelemetryService { const connectionTelemetryProperties = await getConnectionTelemetryProperties( dataService, - model, connectionType ); diff --git a/src/test/fixture/.vscode/settings.json b/src/test/fixture/.vscode/settings.json index be8348c0e..8cb4abde8 100644 --- a/src/test/fixture/.vscode/settings.json +++ b/src/test/fixture/.vscode/settings.json @@ -7,12 +7,6 @@ "files.insertFinalNewline": true, "prettier.singleQuote": true, "prettier.arrowParens": "always", - // Language server logging for - // https://github.com/Microsoft/language-server-protocol-inspector - "mongodbLanguageServer.trace.server": { - "format": "json", - "verbosity": "verbose" - }, "editor.suggest.snippetsPreventQuickSuggestions": false, "editor.quickSuggestions": { "other": true, diff --git a/src/test/suite/commands/launchMongoShell.test.ts b/src/test/suite/commands/launchMongoShell.test.ts index 0be43c1e8..0dbb58e7f 100644 --- a/src/test/suite/commands/launchMongoShell.test.ts +++ b/src/test/suite/commands/launchMongoShell.test.ts @@ -1,8 +1,9 @@ -import assert from 'assert'; import * as vscode from 'vscode'; -import { beforeEach, afterEach } from 'mocha'; import * as sinon from 'sinon'; -import Connection = require('mongodb-connection-model/lib/model'); +import assert from 'assert'; +import { beforeEach, afterEach } from 'mocha'; +import ConnectionModel from 'mongodb-connection-model'; + import launchMongoShell from '../../../commands/launchMongoShell'; import { mdbTestExtension } from '../stubbableMdbExtension'; @@ -14,7 +15,7 @@ suite('Commands Test Suite', () => { const sandbox = sinon.createSandbox(); let fakeShowErrorMessage: any; - let fakeGetActiveConnectionModel: any; + let fakeGetActiveDerivedConnectionModel: any; let fakeIsCurrentlyConnected: any; let createTerminalStub: any; let fakeSendTerminalText: any; @@ -23,9 +24,9 @@ suite('Commands Test Suite', () => { sandbox.stub(vscode.window, 'showInformationMessage'); fakeShowErrorMessage = sandbox.stub(vscode.window, 'showErrorMessage'); - fakeGetActiveConnectionModel = sandbox.stub( + fakeGetActiveDerivedConnectionModel = sandbox.stub( mockConnectionController, - 'getActiveConnectionModel' + 'getActiveDerivedConnectionModel' ); fakeIsCurrentlyConnected = sandbox.stub( @@ -72,13 +73,12 @@ suite('Commands Test Suite', () => { test('openMongoDBShell should open a terminal with the active connection driver url', async () => { const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + const connectionModel = new ConnectionModel({ + hostname: 'localhost', + port: 27018 + }); - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: 'localhost', - port: 27018 - }) - ); + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -99,15 +99,15 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: '127.0.0.1', - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'my.ssh-server.com', - sshTunnelUsername: 'my-user', - sshTunnelPassword: 'password' - }) - ); + const connectionModel = new ConnectionModel({ + hostname: '127.0.0.1', + sshTunnel: 'USER_PASSWORD', + sshTunnelHostname: 'my.ssh-server.com', + sshTunnelUsername: 'my-user', + sshTunnelPassword: 'password' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -129,15 +129,14 @@ suite('Commands Test Suite', () => { test('openMongoDBShell should open a terminal with ssl config injected', async () => { const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: '127.0.0.1', - ssl: true, - sslMethod: 'SERVER', - sslCA: './path_to_some_file' - }) - ); + const connectionModel = new ConnectionModel({ + hostname: '127.0.0.1', + ssl: true, + sslMethod: 'SERVER', + sslCA: './path_to_some_file' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -160,17 +159,16 @@ suite('Commands Test Suite', () => { test('openMongoDBShell should open a terminal with x509 config injected', async () => { const driverUri = 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external'; - - fakeGetActiveConnectionModel.returns( - new Connection({ - sslMethod: 'ALL', - sslCA: './path_to_ca', - sslCert: './path_to_cert', - sslKey: './path_to_key', - authStrategy: 'X509', - x509Username: 'testing' - }) - ); + const connectionModel = new ConnectionModel({ + sslMethod: 'ALL', + sslKey: ['path/to/key'], + sslCert: ['path/to/cert'], + sslCA: ['path/to/ca'], + authStrategy: 'X509', + x509Username: 'testing' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -184,7 +182,7 @@ suite('Commands Test Suite', () => { const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - assert.strictEqual(shellCommandText, 'mongosh --tls --tlsAllowInvalidHostnames --tlsCAFile="./path_to_ca" --tlsCertificateKeyFile="./path_to_cert" $MDB_CONNECTION_STRING;'); + assert.strictEqual(shellCommandText, 'mongosh --tls --tlsAllowInvalidHostnames --tlsCAFile="path/to/ca" --tlsCertificateKeyFile="path/to/cert" $MDB_CONNECTION_STRING;'); }); }); @@ -196,13 +194,12 @@ suite('Commands Test Suite', () => { test('powershell openMongoDBShell should open a terminal with the active connection driver url', async () => { const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + const connectionModel = new ConnectionModel({ + hostname: 'localhost', + port: 27018 + }); - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: 'localhost', - port: 27018 - }) - ); + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -225,15 +222,15 @@ suite('Commands Test Suite', () => { }); test('powershell openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: '127.0.0.1', - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'my.ssh-server.com', - sshTunnelUsername: 'my-user', - sshTunnelPassword: 'password' - }) - ); + const connectionModel = new ConnectionModel({ + hostname: '127.0.0.1', + sshTunnel: 'USER_PASSWORD', + sshTunnelHostname: 'my.ssh-server.com', + sshTunnelUsername: 'my-user', + sshTunnelPassword: 'password' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -257,15 +254,14 @@ suite('Commands Test Suite', () => { test('powershell openMongoDBShell should open a terminal with ssl config injected', async () => { const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: '127.0.0.1', - ssl: true, - sslMethod: 'SERVER', - sslCA: './path_to_some_file' - }) - ); + const connectionModel = new ConnectionModel({ + hostname: '127.0.0.1', + ssl: true, + sslMethod: 'SERVER', + sslCA: './path_to_some_file' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -304,13 +300,12 @@ suite('Commands Test Suite', () => { test('windows cmd openMongoDBShell should open a terminal with the active connection driver url', async () => { const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + const connectionModel = new ConnectionModel({ + hostname: 'localhost', + port: 27018 + }); - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: 'localhost', - port: 27018 - }) - ); + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -327,15 +322,15 @@ suite('Commands Test Suite', () => { }); test('windows cmd openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: '127.0.0.1', - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'my.ssh-server.com', - sshTunnelUsername: 'my-user', - sshTunnelPassword: 'password' - }) - ); + const connectionModel = new ConnectionModel({ + hostname: '127.0.0.1', + sshTunnel: 'USER_PASSWORD', + sshTunnelHostname: 'my.ssh-server.com', + sshTunnelUsername: 'my-user', + sshTunnelPassword: 'password' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -359,15 +354,14 @@ suite('Commands Test Suite', () => { test('windows cmd openMongoDBShell should open a terminal with ssl config injected', async () => { const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - - fakeGetActiveConnectionModel.returns( - new Connection({ - hostname: '127.0.0.1', - ssl: true, - sslMethod: 'SERVER', - sslCA: './path_to_some_file' - }) - ); + const connectionModel = new ConnectionModel({ + hostname: '127.0.0.1', + ssl: true, + sslMethod: 'SERVER', + sslCA: './path_to_some_file' + }); + + fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index 90a12705b..c4b1580f6 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -1,13 +1,10 @@ -import assert from 'assert'; +import * as sinon from 'sinon'; import * as vscode from 'vscode'; import { afterEach, beforeEach } from 'mocha'; -import * as sinon from 'sinon'; +import assert from 'assert'; +import { connect } from 'mongodb-data-service'; import { promisify } from 'util'; -import Connection from 'mongodb-connection-model/lib/model'; -import DataService from 'mongodb-data-service'; - -import TelemetryService from '../../telemetry/telemetryService'; import ConnectionController, { DataServiceEventTypes } from '../../connectionController'; @@ -17,9 +14,9 @@ import { DefaultSavingLocations } from '../../storage/storageController'; import { StatusView } from '../../views'; +import TelemetryService from '../../telemetry/telemetryService'; import { TestExtensionContext } from './stubs'; import { TEST_DATABASE_URI } from './dbTestHelper'; -import { ConnectionModel } from '../../types/connectionModelType'; const testDatabaseConnectionName = 'localhost:27018'; const testDatabaseURI2WithTimeout = @@ -29,11 +26,6 @@ const sleep = (ms: number): Promise => { return promisify(setTimeout)(ms); }; -const getConnection = (dbUri: string): Promise => { - const connectionFrom = promisify(Connection.from.bind(Connection)); - return connectionFrom(dbUri); -}; - suite('Connection Controller Test Suite', function () { this.timeout(5000); @@ -77,7 +69,7 @@ suite('Connection Controller Test Suite', function () { const connnectionId = testConnectionController.getActiveConnectionId() || ''; const name = testConnectionController._connections[connnectionId].name; - const connectionModel = testConnectionController.getActiveConnectionModel(); + const connectionModel = testConnectionController.getActiveDerivedConnectionModel(); const dataService = testConnectionController.getActiveDataService(); assert( @@ -88,22 +80,13 @@ suite('Connection Controller Test Suite', function () { testConnectionController.getSavedConnections().length === 1, 'Expected there to be 1 connection in the connection list.' ); + assert( name === 'localhost:27018', `Expected active connection to be 'localhost:27018' found ${name}` ); assert(connectionModel !== null); - assert( - testConnectionController.getConnectionNameFromConnectionModel( - connectionModel - ) === 'localhost:27018' - ); assert(dataService !== null); - assert( - testConnectionController._activeConnectionModel?.appname.startsWith( - 'mongodb-vscode' - ) - ); assert(testConnectionController.isCurrentlyConnected()); }); @@ -124,7 +107,7 @@ suite('Connection Controller Test Suite', function () { const connectionsCount = testConnectionController.getSavedConnections() .length; const connnectionId = testConnectionController.getActiveConnectionId(); - const connectionModel = testConnectionController.getActiveConnectionModel(); + const connectionModel = testConnectionController.getActiveDerivedConnectionModel(); const dataService = testConnectionController.getActiveDataService(); assert(testConnectionController.getConnectionStatus() === 'DISCONNECTED'); @@ -293,7 +276,7 @@ suite('Connection Controller Test Suite', function () { test('the connection model loads both global and workspace stored connection models', async () => { const expectedDriverUri = - 'mongodb://localhost:27018/?readPreference=primary&directConnection=true&ssl=false'; + 'mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false'; await vscode.workspace .getConfiguration('mdb.connectionSaving') @@ -333,10 +316,10 @@ suite('Connection Controller Test Suite', function () { "Expected loaded connection to include name 'localhost:27018'" ); assert( - connections[Object.keys(connections)[2]].connectionModel.driverUrl === + connections[Object.keys(connections)[2]].connectionOptions?.connectionString === expectedDriverUri, `Expected loaded connection to include driver url '${expectedDriverUri}' found '${ - connections[Object.keys(connections)[2]].connectionModel.driverUrl + connections[Object.keys(connections)[2]].connectionOptions?.connectionString }'` ); }); @@ -352,7 +335,7 @@ suite('Connection Controller Test Suite', function () { const globalStoreConnections = mockStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS - ); + ) || {}; assert( Object.keys(globalStoreConnections).length === 1, @@ -393,7 +376,7 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE - ); + ) || {}; assert( Object.keys(workspaceStoreConnections).length === 1, @@ -420,13 +403,11 @@ suite('Connection Controller Test Suite', function () { }); test('a connection can be connected to by id', async () => { - const connectionModel = await getConnection(TEST_DATABASE_URI); - testConnectionController._connections = { '25': { id: '25', name: 'tester', - connectionModel, + connectionOptions: { connectionString: TEST_DATABASE_URI }, storageLocation: StorageScope.NONE } }; @@ -454,7 +435,7 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE - ); + ) || {}; assert( Object.keys(workspaceStoreConnections).length === 1, @@ -496,18 +477,9 @@ suite('Connection Controller Test Suite', function () { name === 'localhost:27018', `Expected the active connection name to be 'localhost:27018', found ${name}.` ); - - const port = - testConnectionController._connections[activeId || ''].connectionModel - .port; - - assert( - port === 27018, - `Expected the active connection port to be '27018', found ${port}.` - ); }); - test('"getConnectionStringFromConnectionId" returns the driver uri of a connection', async () => { + test('"getConnectionStringByConnectionId" returns the driver uri of a connection', async () => { const expectedDriverUri = 'mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode%200.0.0-dev.0&directConnection=true&ssl=false'; @@ -523,7 +495,7 @@ suite('Connection Controller Test Suite', function () { 'Expected active connection to not be null' ); - const testDriverUri = testConnectionController.getConnectionStringFromConnectionId( + const testDriverUri = await testConnectionController.getConnectionStringByConnectionId( activeConnectionId || '' ); @@ -533,80 +505,6 @@ suite('Connection Controller Test Suite', function () { ); }); - test('"getConnectionNameFromConnectionModel" returns a connection\'s name', () => { - const testConnections: { - model: ConnectionModel; - name: string; - }[] = [ - { - model: new Connection({ - hosts: [ - { - host: 'pineapple', - port: 27020 - } - ] - }), - name: 'pineapple:27020' - }, - { - model: new Connection({ - hostname: 'alaska', - port: 27020, - hosts: [ - { - host: 'wyoming', - port: 28001 - } - ], - isSrvRecord: true - }), - name: 'alaska' - }, - { - model: new Connection({ - hostname: 'pineapple', - port: 27020, - hosts: [ - { - host: 'kentucky', - port: 28001 - }, - { - host: 'nebraska', - port: 28002 - } - ] - }), - name: 'kentucky:28001,nebraska:28002' - }, - { - model: new Connection({ - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'california', - sshTunnelPort: 29222, - hosts: [ - { - host: 'alabama', - port: 28123 - } - ] - }), - name: 'SSH to alabama:28123' - } - ]; - - testConnections.forEach((connection) => { - const name = testConnectionController.getConnectionNameFromConnectionModel( - connection.model - ); - assert( - name === connection.name, - `Expected to be returned the name "${connection.name}" found ${name}` - ); - }); - }); - test('when a connection is added and the user has set it to not save on default it is not saved', async () => { await testConnectionController.loadSavedConnections(); @@ -659,7 +557,7 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE - ); + ) || {}; assert( Object.keys(workspaceStoreConnections).length === 1, @@ -677,7 +575,7 @@ suite('Connection Controller Test Suite', function () { const postWorkspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE - ); + ) || {}; assert( Object.keys(postWorkspaceStoreConnections).length === 0, @@ -698,7 +596,7 @@ suite('Connection Controller Test Suite', function () { const globalStoreConnections = mockStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS - ); + ) || {}; assert( Object.keys(globalStoreConnections).length === 1, @@ -713,7 +611,7 @@ suite('Connection Controller Test Suite', function () { const postGlobalStoreConnections = mockStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS - ); + ) || {}; assert( Object.keys(postGlobalStoreConnections).length === 0, @@ -738,7 +636,7 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE - ); + ) || {}; assert( Object.keys(workspaceStoreConnections).length === 1, @@ -855,15 +753,15 @@ suite('Connection Controller Test Suite', function () { ); assert( connectionQuickPicks[0].label === 'Add new connection', - `Expected the first quick pick label to be 'Add new connection', found '${connectionQuickPicks[0].name}'.` + `Expected the first quick pick label to be 'Add new connection', found '${connectionQuickPicks[0].label}'.` ); assert( connectionQuickPicks[1].label === 'localhost:27018', - `Expected the second quick pick label to be 'localhost:27018', found '${connectionQuickPicks[1].name}'.` + `Expected the second quick pick label to be 'localhost:27018', found '${connectionQuickPicks[1].label}'.` ); assert( connectionQuickPicks[2].label === 'Lynx', - `Expected the third quick pick labele to be 'Lynx', found '${connectionQuickPicks[2].name}'.` + `Expected the third quick pick labele to be 'Lynx', found '${connectionQuickPicks[2].label}'.` ); }); @@ -914,14 +812,12 @@ suite('Connection Controller Test Suite', function () { }); test('it only connects to the most recent connection attempt', async () => { - const connectionModel = await getConnection(TEST_DATABASE_URI); - for (let i = 0; i < 5; i++) { const id = `${i}`; testConnectionController._connections[id] = { id, name: `test${i}`, - connectionModel, + connectionOptions: { connectionString: TEST_DATABASE_URI }, storageLocation: StorageScope.NONE }; } @@ -963,13 +859,11 @@ suite('Connection Controller Test Suite', function () { }); test('a connection which fails can be removed while it is being connected to', async () => { - const connectionModel = await getConnection(testDatabaseURI2WithTimeout); - const connectionId = 'skateboard'; testConnectionController._connections[connectionId] = { id: connectionId, name: 'asdfasdg', - connectionModel, + connectionOptions: { connectionString: testDatabaseURI2WithTimeout }, storageLocation: StorageScope.NONE }; @@ -988,23 +882,24 @@ suite('Connection Controller Test Suite', function () { }); test('a successfully connecting connection can be removed while it is being connected to', async () => { - const connectionModel = await getConnection(TEST_DATABASE_URI); - const connectionId = 'skateboard2'; testConnectionController._connections[connectionId] = { id: connectionId, name: 'asdfasdg', - connectionModel, + connectionOptions: { connectionString: TEST_DATABASE_URI }, storageLocation: StorageScope.NONE }; sinon.replace( - DataService.prototype, - 'connect', - sinon.fake(async (callback) => { + testConnectionController, + 'getDataServiceAndConnect', + sinon.fake(async (connectionOptions) => { await sleep(50); - return callback(null, DataService); + const dataService = await connect(connectionOptions); + await dataService.connect(); + + return dataService; }) ); diff --git a/src/test/suite/dbTestHelper.ts b/src/test/suite/dbTestHelper.ts index 972d5591c..ce6075363 100644 --- a/src/test/suite/dbTestHelper.ts +++ b/src/test/suite/dbTestHelper.ts @@ -1,8 +1,7 @@ +import { connect, DataService } from 'mongodb-data-service'; +import { EJSON } from 'bson'; import { promisify } from 'util'; -import Connection = require('mongodb-connection-model/lib/model'); -import DataService = require('mongodb-data-service'); - export const TEST_USER_USERNAME = 'testUser'; export const TEST_USER_PASSWORD = 'password'; @@ -11,40 +10,33 @@ export const TEST_DATABASE_URI_USER = `mongodb://${TEST_USER_USERNAME}:${TEST_US export const TEST_DB_NAME = 'vscodeTestDatabaseAA'; -let testDatabaseConnectionModel; +let testDataService; // Note: Be sure to disconnect from the dataservice to free up connections. export const seedDataAndCreateDataService = async ( collectionName: string, - documentsArray: any[] -): Promise => { - if (!testDatabaseConnectionModel) { - const connectionFrom = promisify(Connection.from.bind(Connection)); - - try { - testDatabaseConnectionModel = await connectionFrom(TEST_DATABASE_URI); - } catch (error) { - throw new Error(`Error connecting to ${TEST_DATABASE_URI}: ${error}`); - } + documentsArray: EJSON.SerializableTypes[] +): Promise => { + if (!testDataService) { + testDataService = await connect({ connectionString: TEST_DATABASE_URI }); } - const newConnection = new DataService(testDatabaseConnectionModel); - const connect = promisify(newConnection.connect.bind(newConnection)); - const insertMany = promisify(newConnection.insertMany.bind(newConnection)); + const insertMany = promisify(testDataService.insertMany.bind(testDataService)); - await connect(); + await testDataService.connect(); await insertMany(`${TEST_DB_NAME}.${collectionName}`, documentsArray, {}); - return newConnection; + return testDataService; }; export const cleanupTestDB = async (): Promise => { - const newConnection = new DataService(testDatabaseConnectionModel); - const connect = promisify(newConnection.connect.bind(newConnection)); - const dropDatabase = promisify(newConnection.dropDatabase.bind(newConnection)); - const disconnect = promisify(newConnection.disconnect.bind(newConnection)); + if (!testDataService) { + testDataService = await connect({ connectionString: TEST_DATABASE_URI }); + } + + const dropDatabase = promisify(testDataService.dropDatabase.bind(testDataService)); - await connect(); + await testDataService.connect(); await dropDatabase(TEST_DB_NAME); - await disconnect(); + await testDataService.disconnect(); }; diff --git a/src/test/suite/editors/activeDBCodeLensProvider.test.ts b/src/test/suite/editors/activeDBCodeLensProvider.test.ts index d82e3719a..d4801fc93 100644 --- a/src/test/suite/editors/activeDBCodeLensProvider.test.ts +++ b/src/test/suite/editors/activeDBCodeLensProvider.test.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { beforeEach, afterEach } from 'mocha'; +import { DataService } from 'mongodb-data-service'; import chai from 'chai'; import sinon from 'sinon'; @@ -64,12 +65,17 @@ suite('Active DB CodeLens Provider Test Suite', () => { ); const mockActiveConnection = { find: (namespace, filter, options, callback): void => { - return callback(null, ['Text message']); + return callback(null, [{ field: 'Text message' }]); }, - client: {} - }; + instance: () => Promise.resolve({ + dataLake: {}, + build: {}, + genuineMongoDB: {}, + host: {} + }), + } as DataService; - testConnectionController.setActiveConnection(mockActiveConnection); + testConnectionController.setActiveDataService(mockActiveConnection); beforeEach(() => { sinon.replace( diff --git a/src/test/suite/editors/codeActionProvider.test.ts b/src/test/suite/editors/codeActionProvider.test.ts index ff12fe306..132a54ca5 100644 --- a/src/test/suite/editors/codeActionProvider.test.ts +++ b/src/test/suite/editors/codeActionProvider.test.ts @@ -8,16 +8,14 @@ import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageC import CodeActionProvider from '../../../editors/codeActionProvider'; import { ExplorerController } from '../../../explorer'; import { LanguageServerController } from '../../../language'; +import { mdbTestExtension } from '../stubbableMdbExtension'; import { PlaygroundController } from '../../../editors'; import { PlaygroundResult, ExportToLanguageMode } from '../../../types/playgroundType'; - -import { mdbTestExtension } from '../stubbableMdbExtension'; +import { TEST_DATABASE_URI } from '../dbTestHelper'; import { TestExtensionContext } from '../stubs'; const expect = chai.expect; -import { TEST_DATABASE_URI } from '../dbTestHelper'; - suite('Code Action Provider Test Suite', function () { this.timeout(5000); @@ -52,7 +50,6 @@ suite('Code Action Provider Test Suite', function () { ); mdbTestExtension.testExtensionController._playgroundController = new PlaygroundController( - testExtensionContext, mdbTestExtension.testExtensionController._connectionController, mdbTestExtension.testExtensionController._languageServerController, mdbTestExtension.testExtensionController._telemetryService, @@ -311,7 +308,7 @@ suite('Code Action Provider Test Suite', function () { expectedResult = { namespace: 'db.coll', type: null, - content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", + content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode%200.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", language: 'python' }; diff --git a/src/test/suite/editors/collectionDocumentsProvider.test.ts b/src/test/suite/editors/collectionDocumentsProvider.test.ts index c4589397e..d7244d539 100644 --- a/src/test/suite/editors/collectionDocumentsProvider.test.ts +++ b/src/test/suite/editors/collectionDocumentsProvider.test.ts @@ -1,15 +1,13 @@ import * as vscode from 'vscode'; import { afterEach } from 'mocha'; import assert from 'assert'; +import { DataService } from 'mongodb-data-service'; import sinon from 'sinon'; -import { promisify } from 'util'; import { DocumentSource } from '../../../documentSource'; import CollectionDocumentsOperationsStore from '../../../editors/collectionDocumentsOperationsStore'; import CollectionDocumentsProvider, { VIEW_COLLECTION_SCHEME } from '../../../editors/collectionDocumentsProvider'; -import Connection from 'mongodb-connection-model/lib/model'; import ConnectionController from '../../../connectionController'; -import { ConnectionModel } from '../../../types/connectionModelType'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; import { StatusView } from '../../../views'; import { StorageController } from '../../../storage'; @@ -18,11 +16,6 @@ import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { TestExtensionContext, mockTextEditor } from '../stubs'; -const getConnection = (dbUri): Promise => { - const connectionFrom = promisify(Connection.from.bind(Connection)); - return connectionFrom(dbUri); -}; - const mockDocumentsAsJsonString = `[ { "_id": "first_id", @@ -61,16 +54,16 @@ suite('Collection Documents Provider Test Suite', () => { `Expected find limit to be 10, found ${options.limit}` ); - return callback(null, ['Declaration of Independence']); + return callback(null, [{ field: 'Declaration of Independence' }]); } - }; + } as DataService; const mockConnectionController = new ConnectionController( new StatusView(mockExtensionContext), mockStorageController, testTelemetryService ); - mockConnectionController.setActiveConnection(mockActiveConnection); + mockConnectionController.setActiveDataService(mockActiveConnection); const testQueryStore = new CollectionDocumentsOperationsStore(); const testCodeLensProvider = new EditDocumentCodeLensProvider( @@ -118,13 +111,13 @@ suite('Collection Documents Provider Test Suite', () => { find: (namespace, filter, options, callback): void => { return callback(null, mockDocuments); } - }; + } as DataService; const mockConnectionController = new ConnectionController( new StatusView(mockExtensionContext), mockStorageController, testTelemetryService ); - mockConnectionController.setActiveConnection(mockActiveConnection); + mockConnectionController.setActiveDataService(mockActiveConnection); const testQueryStore = new CollectionDocumentsOperationsStore(); const testCodeLensProvider = new EditDocumentCodeLensProvider( @@ -159,15 +152,15 @@ suite('Collection Documents Provider Test Suite', () => { test('provideTextDocumentContent sets hasMoreDocumentsToShow to false when there arent more documents', (done) => { const mockActiveConnection = { find: (namespace, filter, options, callback): void => { - return callback(null, ['Apollo', 'Gemini ']); + return callback(null, [{ field: 'Apollo' }, { field: 'Gemini ' }]); } - }; + } as DataService; const mockConnectionController = new ConnectionController( new StatusView(mockExtensionContext), mockStorageController, testTelemetryService ); - mockConnectionController.setActiveConnection(mockActiveConnection); + mockConnectionController.setActiveDataService(mockActiveConnection); const testQueryStore = new CollectionDocumentsOperationsStore(); const testCodeLensProvider = new EditDocumentCodeLensProvider( @@ -212,13 +205,13 @@ suite('Collection Documents Provider Test Suite', () => { }); test('provideTextDocumentContent shows a status bar item while it is running then hide it', (done) => { - const mockActiveConnection = { find: {} }; + const mockActiveConnection = { find: {} } as DataService; const mockConnectionController = new ConnectionController( new StatusView(mockExtensionContext), mockStorageController, testTelemetryService ); - mockConnectionController.setActiveConnection(mockActiveConnection); + mockConnectionController.setActiveDataService(mockActiveConnection); const testStatusView = new StatusView(mockExtensionContext); @@ -256,7 +249,7 @@ suite('Collection Documents Provider Test Suite', () => { assert(!mockHideMessage.called); assert(mockShowMessage.firstCall.args[0] === 'Fetching documents...'); - return callback(null, ['aaaaaaaaaaaaaaaaa']); + return callback(null, [{ field: 'aaaaaaaaaaaaaaaaa' }]); }; testCollectionViewProvider @@ -465,7 +458,6 @@ suite('Collection Documents Provider Test Suite', () => { const mockHideMessage = sinon.fake(); sinon.replace(testCollectionViewProvider._statusView, 'hideMessage', mockHideMessage); - const connectionModel = await getConnection(TEST_DATABASE_URI); const firstConnectionId = '1c8c2b06-fbfb-40b7-bd8a-bd1f8333a487'; const secondConnectionId = '333c2b06-hhhh-40b7-bd8a-bd1f8333a896'; @@ -473,13 +465,13 @@ suite('Collection Documents Provider Test Suite', () => { [firstConnectionId]: { id: firstConnectionId, name: 'localhost', - connectionModel, + connectionOptions: { connectionString: TEST_DATABASE_URI }, storageLocation: StorageScope.NONE }, [secondConnectionId]: { id: secondConnectionId, name: 'compass', - connectionModel, + connectionOptions: { connectionString: TEST_DATABASE_URI }, storageLocation: StorageScope.NONE } }; diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index bcf033b1b..0e91e4020 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -4,11 +4,12 @@ import chai from 'chai'; import sinon from 'sinon'; import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; -import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; +import CodeActionProvider from '../../../editors/codeActionProvider'; import ConnectionController from '../../../connectionController'; -import { ConnectionModel } from '../../../types/connectionModelType'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; import { ExplorerController } from '../../../explorer'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; +import { ExportToLanguageMode } from '../../../types/playgroundType'; import { LanguageServerController } from '../../../language'; import { PlaygroundController } from '../../../editors'; import PlaygroundResultProvider from '../../../editors/playgroundResultProvider'; @@ -17,8 +18,6 @@ import { StorageController } from '../../../storage'; import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { TestExtensionContext, MockLanguageServerController } from '../stubs'; -import CodeActionProvider from '../../../editors/codeActionProvider'; -import { ExportToLanguageMode } from '../../../types/playgroundType'; const expect = chai.expect; @@ -67,7 +66,6 @@ suite('Playground Controller Test Suite', function () { testConnectionController ); const testPlaygroundController = new PlaygroundController( - mockExtensionContext, testConnectionController, mockLanguageServerController as LanguageServerController, testTelemetryService, @@ -99,14 +97,14 @@ suite('Playground Controller Test Suite', function () { beforeEach(async () => { const mockGetActiveConnectionName = sinon.fake.returns('fakeName'); const mockGetActiveDataService = sinon.fake.returns({ - getConnectionOptions: () => ({ + getMongoClientConnectionOptions: () => ({ url: TEST_DATABASE_URI, options: { appname: 'VSCode Playground Tests', port: 27018, - sslKey: 'some buffer', - sslCert: 'not the file path', - sslCA: 'aaaa', + sslKey: ['path/to/key'], + sslCert: ['path/to/cert'], + sslCA: ['path/to/ca'], } }) }); @@ -140,16 +138,14 @@ suite('Playground Controller Test Suite', function () { ); sinon.replace( testPlaygroundController._connectionController, - 'getActiveConnectionModel', - () => (({ - getAttributes: () => ({ - driverOptions: { - sslKey: 'sslKeyFile.pem', - sslCert: 'sslCertFile.pem', - sslCA: 'sslCAFile.pem' - } - }) - } as any)as ConnectionModel) + 'getActiveDerivedConnectionModel', + () => ({ + driverOptions: { + sslKey: ['path/to/key'], + sslCert: ['path/to/cert'], + sslCA: ['path/to/ca'] + } + }) ); await testPlaygroundController._connectToServiceProvider(); @@ -167,24 +163,24 @@ suite('Playground Controller Test Suite', function () { expect( (mockConnectToServiceProvider.firstCall.firstArg as { connectionOptions: { - sslKey: string; + sslKey: string[]; } }).connectionOptions.sslKey - ).to.equal('sslKeyFile.pem'); + ).to.deep.equal(['path/to/key']); expect( (mockConnectToServiceProvider.firstCall.firstArg as { connectionOptions: { sslCert: string; } }).connectionOptions.sslCert - ).to.equal('sslCertFile.pem'); + ).to.deep.equal(['path/to/cert']); expect( (mockConnectToServiceProvider.firstCall.firstArg as { connectionOptions: { sslCA: string; } }).connectionOptions.sslCA - ).to.equal('sslCAFile.pem'); + ).to.deep.equal(['path/to/ca']); }); }); @@ -265,7 +261,7 @@ suite('Playground Controller Test Suite', function () { suite('user is not connected', () => { before(() => { const mockGetActiveConnectionName = sinon.fake.returns(''); - const mockGetActiveConnectionModel = sinon.fake.returns(null); + const mockGetActiveDerivedConnectionModel = sinon.fake.returns(null); sinon.replace( testPlaygroundController._connectionController, @@ -274,8 +270,8 @@ suite('Playground Controller Test Suite', function () { ); sinon.replace( testPlaygroundController._connectionController, - 'getActiveConnectionModel', - mockGetActiveConnectionModel + 'getActiveDerivedConnectionModel', + mockGetActiveDerivedConnectionModel ); }); @@ -332,7 +328,7 @@ suite('Playground Controller Test Suite', function () { beforeEach(async () => { const mockGetActiveConnectionName = sinon.fake.returns('fakeName'); const mockGetActiveDataService = sinon.fake.returns({ - getConnectionOptions: () => ({ + getMongoClientConnectionOptions: () => ({ url: TEST_DATABASE_URI, options: { appname: 'VSCode Playground Tests', @@ -475,7 +471,6 @@ suite('Playground Controller Test Suite', function () { testConnectionController ); const playgroundControllerTest = new PlaygroundController( - mockExtensionContext, testConnectionController, mockLanguageServerController as LanguageServerController, testTelemetryService, @@ -497,7 +492,6 @@ suite('Playground Controller Test Suite', function () { testConnectionController ); const playgroundControllerTest = new PlaygroundController( - mockExtensionContext, testConnectionController, mockLanguageServerController as LanguageServerController, testTelemetryService, diff --git a/src/test/suite/explorer/collectionTreeItem.test.ts b/src/test/suite/explorer/collectionTreeItem.test.ts index 15526ce36..3bff52c6b 100644 --- a/src/test/suite/explorer/collectionTreeItem.test.ts +++ b/src/test/suite/explorer/collectionTreeItem.test.ts @@ -1,13 +1,12 @@ import assert from 'assert'; -const { contributes } = require('../../../../package.json'); - import CollectionTreeItem from '../../../explorer/collectionTreeItem'; import { CollectionTypes } from '../../../explorer/documentListTreeItem'; import { ext } from '../../../extensionConstants'; - import { TestExtensionContext, DataServiceStub } from '../stubs'; +const { contributes } = require('../../../../package.json'); + suite('CollectionTreeItem Test Suite', () => { ext.context = new TestExtensionContext(); @@ -79,9 +78,7 @@ suite('CollectionTreeItem Test Suite', () => { type: CollectionTypes.collection }, 'mock_db_name', - { - estimatedCount: (ns, options, cb): void => cb(null, 5000) - }, + { estimatedCount: (ns, options, cb): void => cb(null, 5000) }, false, false, null diff --git a/src/test/suite/explorer/connectionTreeItem.test.ts b/src/test/suite/explorer/connectionTreeItem.test.ts index 37ec14100..2834feb9e 100644 --- a/src/test/suite/explorer/connectionTreeItem.test.ts +++ b/src/test/suite/explorer/connectionTreeItem.test.ts @@ -1,41 +1,16 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ -import assert from 'assert'; import * as vscode from 'vscode'; +import assert from 'assert'; import { beforeEach, afterEach } from 'mocha'; import sinon from 'sinon'; -import { MongoClient } from 'mongodb'; - -const { contributes } = require('../../../../package.json'); import ConnectionTreeItem, { - ConnectionItemContextValues, - getDatabaseNamesFromPrivileges + ConnectionItemContextValues } from '../../../explorer/connectionTreeItem'; -import { mdbTestExtension } from '../stubbableMdbExtension'; import { DataServiceStub } from '../stubs'; -import { TEST_DATABASE_URI, TEST_DATABASE_URI_USER, TEST_USER_PASSWORD, TEST_USER_USERNAME } from '../dbTestHelper'; +import { mdbTestExtension } from '../stubbableMdbExtension'; -const mockPrivileges = [{}, { - resource: { - db: 'db2', - roles: ['readWrite', 'listCollections'] - } -}, { - resource: { - db: 'db3', - roles: ['readWrite', 'listCollections'] - } -}, { - resource: { - db: 'pineapple', - roles: ['read', 'listCollections'] - }, -}, { - resource: { - db: 'db1', - roles: ['readWrite', 'listCollections'] - } -}]; +const { contributes } = require('../../../../package.json'); suite('ConnectionTreeItem Test Suite', () => { test('its context value should be in the package json', function () { @@ -98,7 +73,7 @@ suite('ConnectionTreeItem Test Suite', () => { mdbTestExtension.testExtensionController._connectionController, 'getActiveDataService', () => ({ - listDatabases: (cb) => { cb(new Error('peaches')); } + listDatabases: () => new Promise(() => { throw Error('peaches'); }) }) as any ); @@ -142,162 +117,5 @@ suite('ConnectionTreeItem Test Suite', () => { assert.strictEqual(dbNames.length, 3); assert(dbNames.includes('mockDatabase2')); }); - - test('when list databases errors with not authorization error it does not call listDatabasesUserHasAccessTo', async () => { - sinon.replace( - mdbTestExtension.testExtensionController._connectionController, - 'getActiveDataService', - () => ({ - listDatabases: (cb) => { cb(new Error('the dog is barking at a squirrel')); } - }) as any - ); - - const fake = sinon.fake.resolves([]); - sinon.replace( - testConnectionTreeItem, - 'listDatabasesUserHasAccessTo', - fake - ); - - try { - await testConnectionTreeItem.listDatabases(); - assert(false, 'Expected to error and did not'); - } catch (_) { - assert(!fake.called); - } - }); - - test('when list databases errors with authorization error it calls listDatabasesUserHasAccessTo', async () => { - sinon.replace( - mdbTestExtension.testExtensionController._connectionController, - 'getActiveDataService', - () => ({ - listDatabases: (cb) => { - cb(new Error('not allowed to listDatabases')); - }, - client: {} - }) as any - ); - - const fake = sinon.fake.resolves([]); - sinon.replace( - testConnectionTreeItem, - 'listDatabasesUserHasAccessTo', - fake - ); - - await testConnectionTreeItem.listDatabases(); - assert(fake.called); - }); - - test('when list databases errors with authorization error it reads privileges to get possible dbs', async () => { - const mockDbCommandResult = { - authInfo: { - authenticatedUserPrivileges: mockPrivileges - } - }; - - const mockDataserviceClient = { - client: { - db: () => ({ - databaseName: 'admin', - command: () => { - return new Promise((resolve) => { - resolve(mockDbCommandResult); - }); - } - }) - } - }; - - sinon.replace( - mdbTestExtension.testExtensionController._connectionController, - 'getActiveDataService', - () => ({ - listDatabases: (cb) => { - cb(new Error('not allowed to listDatabases')); - }, - client: mockDataserviceClient - }) as any - ); - - const dbs = await testConnectionTreeItem.listDatabases(); - - assert.strictEqual(dbs.length, 4); - assert(dbs.includes('pineapple')); - }); - }); - - suite('#listDatabasesUserHasAccessTo', () => { - let testConnectionTreeItem: ConnectionTreeItem; - let adminClient = new MongoClient(TEST_DATABASE_URI); - let userClient = new MongoClient(TEST_DATABASE_URI); - - beforeEach(async () => { - testConnectionTreeItem = new ConnectionTreeItem( - '', - vscode.TreeItemCollapsibleState.Expanded, - true, - mdbTestExtension.testExtensionController._connectionController, - false, - {} - ); - adminClient = new MongoClient(TEST_DATABASE_URI); - await adminClient.connect(); - - const adminDb = adminClient.db().admin(); - // Create a new user with access to a db. - await adminDb.addUser( - TEST_USER_USERNAME, - TEST_USER_PASSWORD, - { - roles: [{ - role: 'readWrite', - db: 'coffee' - }] - } - ); - - userClient = new MongoClient(TEST_DATABASE_URI_USER); - await userClient.connect(); - }); - - afterEach(async () => { - // Remove the user we created. - const adminDb = adminClient.db().admin(); - await adminDb.removeUser(TEST_USER_USERNAME); - - await adminClient.close(); - await userClient.close(); - sinon.restore(); - }); - - test('returns a list of database names the authenticated user might be able to use', async () => { - const dbNames = await testConnectionTreeItem.listDatabasesUserHasAccessTo( - userClient - ); - - assert.strictEqual(dbNames.length, 1); - assert.strictEqual(dbNames[0], 'coffee'); - }); - }); - - suite('#getDatabaseNamesFromPrivileges', () => { - test('it returns a sorted list of privileges', () => { - assert.deepStrictEqual( - getDatabaseNamesFromPrivileges(mockPrivileges), - ['db1', 'db2', 'db3', 'pineapple'] - ); - }); - - test('it returns an empty array when there are no privileges', () => { - const privs = []; - assert.deepStrictEqual(getDatabaseNamesFromPrivileges(privs), []); - }); - - test('it returns an empty array when there are no resources', () => { - const privs = [{}]; - assert.deepStrictEqual(getDatabaseNamesFromPrivileges(privs), []); - }); }); }); diff --git a/src/test/suite/explorer/databaseTreeItem.test.ts b/src/test/suite/explorer/databaseTreeItem.test.ts index 4d8a0936c..0acea3657 100644 --- a/src/test/suite/explorer/databaseTreeItem.test.ts +++ b/src/test/suite/explorer/databaseTreeItem.test.ts @@ -1,18 +1,18 @@ import * as vscode from 'vscode'; -import assert from 'assert'; import { afterEach } from 'mocha'; +import assert from 'assert'; -const { contributes } = require('../../../../package.json'); - +import { CollectionTreeItem } from '../../../explorer'; import DatabaseTreeItem from '../../../explorer/databaseTreeItem'; import { DataServiceStub, mockDatabaseNames, mockDatabases } from '../stubs'; -import { CollectionTreeItem } from '../../../explorer'; import { seedDataAndCreateDataService, cleanupTestDB, TEST_DB_NAME } from '../dbTestHelper'; +const { contributes } = require('../../../../package.json'); + suite('DatabaseTreeItem Test Suite', () => { test('its context value should be in the package json', () => { let databaseRegisteredCommandInPackageJson = false; @@ -192,6 +192,7 @@ suite('DatabaseTreeItem Test Suite', () => { aField: 1234567 } }; + for (let i = 0; i < 28; i++) { mockDocWithThirtyFields[`field${i}`] = 'some value'; } @@ -206,8 +207,8 @@ suite('DatabaseTreeItem Test Suite', () => { false, {} ); - const collectionTreeItems: CollectionTreeItem[] = await testDatabaseTreeItem.getChildren(); + assert( collectionTreeItems[0].isExpanded === false, 'Expected collection tree item not to be expanded on default.' @@ -217,7 +218,6 @@ suite('DatabaseTreeItem Test Suite', () => { const schemaTreeItem = collectionTreeItems[0].getSchemaChild(); if (!schemaTreeItem) { assert(false, 'No schema tree item found on collection.'); - return; } void schemaTreeItem.onDidExpand(); schemaTreeItem.onShowMoreClicked(); @@ -234,6 +234,7 @@ suite('DatabaseTreeItem Test Suite', () => { !!schemaTreeItem.childrenCache.testerObject, 'Expected the subdocument field to be in the schema cache.' ); + // Expand the subdocument. void schemaTreeItem.childrenCache.testerObject.onDidExpand(); @@ -248,7 +249,7 @@ suite('DatabaseTreeItem Test Suite', () => { void testDatabaseTreeItem.onDidExpand(); const newCollectionTreeItems = await testDatabaseTreeItem .getChildren(); - dataService.disconnect(() => {}); + void dataService.disconnect(); const postCollapseSchemaTreeItem = newCollectionTreeItems[0].getSchemaChild(); assert( diff --git a/src/test/suite/explorer/explorerController.test.ts b/src/test/suite/explorer/explorerController.test.ts index 68f64b801..34ec4ee4c 100644 --- a/src/test/suite/explorer/explorerController.test.ts +++ b/src/test/suite/explorer/explorerController.test.ts @@ -3,7 +3,6 @@ import assert from 'assert'; import { beforeEach, afterEach } from 'mocha'; import sinon from 'sinon'; -import Connection = require('mongodb-connection-model/lib/model'); import { DefaultSavingLocations, StorageScope @@ -53,17 +52,15 @@ suite('Explorer Controller Test Suite', function () { const testExplorerController = mdbTestExtension.testExtensionController._explorerController; const treeController = testExplorerController.getTreeController(); - const mockConnectionId = 'testConnectionId'; testConnectionController._connections = { testConnectionId: { id: 'testConnectionId', - connectionModel: new Connection(), + connectionOptions: { connectionString: 'mongodb://localhost' }, name: 'testConnectionName', storageLocation: StorageScope.NONE } }; - testConnectionController.setConnnectingConnectionId(mockConnectionId); testConnectionController.setConnnecting(true); const connectionsItems = await treeController.getChildren(); @@ -194,16 +191,14 @@ suite('Explorer Controller Test Suite', function () { const connectionId = testConnectionController.getActiveConnectionId() || ''; testConnectionController._connections.aaa = { - connectionModel: - testConnectionController._connections[connectionId].connectionModel, + connectionOptions: testConnectionController._connections[connectionId].connectionOptions, name: 'aaa', id: 'aaa', storageLocation: StorageScope.WORKSPACE }; testConnectionController._connections.zzz = { - connectionModel: - testConnectionController._connections[connectionId].connectionModel, + connectionOptions: testConnectionController._connections[connectionId].connectionOptions, name: 'zzz', id: 'zzz', storageLocation: StorageScope.WORKSPACE diff --git a/src/test/suite/explorer/fieldTreeItem.test.ts b/src/test/suite/explorer/fieldTreeItem.test.ts index 9f4c4e97a..10ef3d908 100644 --- a/src/test/suite/explorer/fieldTreeItem.test.ts +++ b/src/test/suite/explorer/fieldTreeItem.test.ts @@ -1,22 +1,22 @@ -import assert from 'assert'; import { afterEach } from 'mocha'; +import assert from 'assert'; -const { contributes } = require('../../../../package.json'); +import { ext } from '../../../extensionConstants'; import FieldTreeItem, { FIELD_TREE_ITEM_CONTEXT_VALUE, fieldIsExpandable, getIconFileNameForField } from '../../../explorer/fieldTreeItem'; -import SchemaTreeItem from '../../../explorer/schemaTreeItem'; -import { ext } from '../../../extensionConstants'; - import { seedDataAndCreateDataService, cleanupTestDB, TEST_DB_NAME } from '../dbTestHelper'; +import SchemaTreeItem from '../../../explorer/schemaTreeItem'; import { TestExtensionContext } from '../stubs'; +const { contributes } = require('../../../../package.json'); + suite('FieldTreeItem Test Suite', function () { this.timeout(10000); test('its context value should be in the package json', function () { @@ -158,7 +158,7 @@ suite('FieldTreeItem Test Suite', function () { testSchemaTreeItem .getChildren() .then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); assert( schemaFields[0].label === '_id', @@ -212,7 +212,7 @@ suite('FieldTreeItem Test Suite', function () { void testSchemaTreeItem.onDidExpand(); void testSchemaTreeItem.getChildren().then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); assert( schemaFields.length === 2, `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` @@ -276,7 +276,7 @@ suite('FieldTreeItem Test Suite', function () { void testSchemaTreeItem.onDidExpand(); testSchemaTreeItem.getChildren().then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); assert( schemaFields.length === 2, `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` @@ -336,7 +336,7 @@ suite('FieldTreeItem Test Suite', function () { void testSchemaTreeItem.onDidExpand(); void testSchemaTreeItem.getChildren().then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); schemaFields[1].getChildren().then((nestedSubDocuments) => { assert( diff --git a/src/test/suite/explorer/indexListTreeItem.test.ts b/src/test/suite/explorer/indexListTreeItem.test.ts index a4c1a9961..06fcd0014 100644 --- a/src/test/suite/explorer/indexListTreeItem.test.ts +++ b/src/test/suite/explorer/indexListTreeItem.test.ts @@ -1,10 +1,11 @@ -import assert from 'assert'; import * as vscode from 'vscode'; - -const { contributes } = require('../../../../package.json'); +import assert from 'assert'; +import { DataService } from 'mongodb-data-service'; import IndexListTreeItem from '../../../explorer/indexListTreeItem'; +const { contributes } = require('../../../../package.json'); + suite('IndexListTreeItem Test Suite', () => { test('its context value should be in the package json', () => { let indexListRegisteredCommandInPackageJson = false; @@ -12,8 +13,10 @@ suite('IndexListTreeItem Test Suite', () => { 'pineapple', 'tasty_fruits', { - indexes: (): void => { } - }, + indexes: (ns, opts, cb): void => { + cb(null, []); + } + } as DataService, false, false, [] @@ -62,7 +65,7 @@ suite('IndexListTreeItem Test Suite', () => { cb(null, fakeFetchIndexes); } - }, + } as DataService, false, false, [] @@ -99,13 +102,17 @@ suite('IndexListTreeItem Test Suite', () => { const testIndexListTreeItem = new IndexListTreeItem( 'pineapple', 'tasty_fruits', - 'fake data service', + { + indexes: (ns, opts, cb): void => { + cb(null, []); + } + } as DataService, false, false, [] ); - const indexesIconPath: any = testIndexListTreeItem.iconPath; + const indexesIconPath = testIndexListTreeItem.iconPath as { light: string; dark: string }; assert( indexesIconPath.dark.includes('indexes.svg'), 'Expected icon path to point to an svg by the name "indexes" with a dark mode' @@ -119,9 +126,9 @@ suite('IndexListTreeItem Test Suite', () => { 'tasty_fruits', { indexes: (ns, opts, cb): void => { - cb(new Error(expectedErrorMessage)); + cb(new Error(expectedErrorMessage), []); } - }, + } as DataService, false, false, [] @@ -168,7 +175,7 @@ suite('IndexListTreeItem Test Suite', () => { indexes: (ns, opts, cb): void => { cb(null, fakeFetchIndexes); } - }, + } as DataService, false, false, [] @@ -194,7 +201,7 @@ suite('IndexListTreeItem Test Suite', () => { indexes: (ns, opts, cb): void => { cb(null, []); } - }, + } as DataService, testIndexListTreeItem.isExpanded, testIndexListTreeItem.cacheIsUpToDate, testIndexListTreeItem.getChildrenCache() diff --git a/src/test/suite/explorer/schemaTreeItem.test.ts b/src/test/suite/explorer/schemaTreeItem.test.ts index a3a0a89a3..04b4f3561 100644 --- a/src/test/suite/explorer/schemaTreeItem.test.ts +++ b/src/test/suite/explorer/schemaTreeItem.test.ts @@ -1,24 +1,23 @@ -import assert from 'assert'; import * as vscode from 'vscode'; -import { afterEach } from 'mocha'; import * as sinon from 'sinon'; +import { afterEach } from 'mocha'; +import assert from 'assert'; import { inspect } from 'util'; -const { contributes } = require('../../../../package.json'); - -import SchemaTreeItem, { - FIELDS_TO_SHOW -} from '../../../explorer/schemaTreeItem'; -import { fieldIsExpandable } from '../../../explorer/fieldTreeItem'; import { ext } from '../../../extensionConstants'; - +import { fieldIsExpandable } from '../../../explorer/fieldTreeItem'; import { seedDataAndCreateDataService, cleanupTestDB, TEST_DB_NAME } from '../dbTestHelper'; +import SchemaTreeItem, { + FIELDS_TO_SHOW +} from '../../../explorer/schemaTreeItem'; import { TestExtensionContext } from '../stubs'; +const { contributes } = require('../../../../package.json'); + suite('SchemaTreeItem Test Suite', function () { this.timeout(10000); afterEach(() => { @@ -253,7 +252,7 @@ suite('SchemaTreeItem Test Suite', function () { testSchemaTreeItem .getChildren() .then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); assert( schemaFields.length === 0, @@ -287,7 +286,7 @@ suite('SchemaTreeItem Test Suite', function () { testSchemaTreeItem .getChildren() .then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); assert( schemaFields.length === 2, `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` @@ -340,7 +339,7 @@ suite('SchemaTreeItem Test Suite', function () { testSchemaTreeItem .getChildren() .then((schemaFields) => { - dataService.disconnect(() => {}); + void dataService.disconnect(); assert( schemaFields.length === 3, `Expected 3 schema tree items to be returned, recieved ${schemaFields.length}: ${inspect(schemaFields)}` diff --git a/src/test/suite/language/languageServerController.test.ts b/src/test/suite/language/languageServerController.test.ts index 8fe3ff418..bbe98ba01 100644 --- a/src/test/suite/language/languageServerController.test.ts +++ b/src/test/suite/language/languageServerController.test.ts @@ -5,11 +5,11 @@ import path from 'path'; import sinon from 'sinon'; import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; -import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; +import CodeActionProvider from '../../../editors/codeActionProvider'; import ConnectionController from '../../../connectionController'; -import { DataServiceType } from '../../../types/dataServiceType'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; import { ExplorerController } from '../../../explorer'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; import { LanguageServerController } from '../../../language'; import { mdbTestExtension } from '../stubbableMdbExtension'; import { PlaygroundController } from '../../../editors'; @@ -20,7 +20,6 @@ import { StorageController } from '../../../storage'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import TelemetryService from '../../../telemetry/telemetryService'; import { TestExtensionContext } from '../stubs'; -import CodeActionProvider from '../../../editors/codeActionProvider'; const expect = chai.expect; @@ -61,7 +60,6 @@ suite('Language Server Controller Test Suite', () => { const testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); const testCodeActionProvider = new CodeActionProvider(); const testPlaygroundController = new PlaygroundController( - mockExtensionContext, testConnectionController, testLanguageServerController, testTelemetryService, @@ -84,8 +82,8 @@ suite('Language Server Controller Test Suite', () => { sinon.replace( testConnectionController, 'getActiveDataService', - () => (({ - getConnectionOptions: () => ({ + () => ({ + getMongoClientConnectionOptions: () => ({ url: TEST_DATABASE_URI, options: { appname: 'VSCode Playground Tests', @@ -93,7 +91,7 @@ suite('Language Server Controller Test Suite', () => { readPreference: READ_PREFERENCES.PRIMARY } }) - } as any) as DataServiceType) + } as any) ); sinon.replace( testConnectionController, diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 34792d197..8604ee2d5 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import { afterEach, beforeEach } from 'mocha'; import assert from 'assert'; +import { DataService } from 'mongodb-data-service'; import sinon, { SinonSpy } from 'sinon'; import { @@ -13,7 +14,6 @@ import { DocumentTreeItem, SchemaTreeItem } from '../../explorer'; -import Connection = require('mongodb-connection-model/lib/model'); import EXTENSION_COMMANDS from '../../commands'; import FieldTreeItem from '../../explorer/fieldTreeItem'; import IndexListTreeItem from '../../explorer/indexListTreeItem'; @@ -255,7 +255,7 @@ suite('MDBExtensionController Test Suite', function () { const mockStubUri: any = sinon.fake.returns('weStubThisUri'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, - 'getConnectionStringFromConnectionId', + 'getConnectionStringByConnectionId', mockStubUri ); @@ -445,9 +445,7 @@ suite('MDBExtensionController Test Suite', function () { type: CollectionTypes.collection }, 'airZebra', - { - estimatedCount: (ns, opts, cb): void => cb(null, count) - }, + { estimatedCount: (ns, opts, cb): void => cb(null, count) }, false, false, null @@ -527,7 +525,7 @@ suite('MDBExtensionController Test Suite', function () { const mockTreeItem = new IndexListTreeItem( 'zebraWearwolf', 'giraffeVampire', - {}, + {} as DataService, false, false, [] @@ -595,13 +593,12 @@ suite('MDBExtensionController Test Suite', function () { ); const mockGetActiveDataService: any = sinon.fake.returns({ - client: { - client: { - db: () => ({ - command: () => Promise.resolve({ versionArray: [4, 4, 4, 0] }) - }) - } - }, + instance: () => Promise.resolve({ + dataLake: {}, + build: { version: '4.9.7' }, + genuineMongoDB: {}, + host: {} + }), createCollection: (namespace, options, callback) => { callback(null); } @@ -648,13 +645,12 @@ suite('MDBExtensionController Test Suite', function () { ); const mockGetActiveDataService: any = sinon.fake.returns({ - client: { - client: { - db: () => ({ - command: () => Promise.resolve({ versionArray: [5, 0, 0, 0] }) - }) - } - }, + instance: () => Promise.resolve({ + dataLake: {}, + build: { version: '5.0.1' }, + genuineMongoDB: {}, + host: {} + }), createCollection: (namespace, options, callback) => { callback(null); } @@ -787,30 +783,25 @@ suite('MDBExtensionController Test Suite', function () { }); test('mdb.addCollection should create a MongoDB playground with create collection template without time-series', async () => { + const mockGetActiveDataService: any = sinon.fake.returns({ + instance: () => Promise.resolve({ + dataLake: {}, + build: { version: '4.9.7' }, + genuineMongoDB: {}, + host: {} + }), + createCollection: (namespace, options, callback) => { + callback(null); + } + }); const mockTreeItem = new DatabaseTreeItem( 'iceCreamDB', - { - createCollection: (namespace, options, callback): void => { - callback(null); - } - }, + mockGetActiveDataService, false, false, {} ); - const mockGetActiveDataService: any = sinon.fake.returns({ - client: { - client: { - db: () => ({ - command: () => Promise.resolve({ versionArray: [4, 4, 4, 0] }) - }) - } - }, - createCollection: (namespace, options, callback) => { - callback(null); - } - }); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveDataService', @@ -856,13 +847,12 @@ suite('MDBExtensionController Test Suite', function () { ); const mockGetActiveDataService: any = sinon.fake.returns({ - client: { - client: { - db: () => ({ - command: () => Promise.resolve({ versionArray: [5, 0, 0, 0] }) - }) - } - }, + instance: () => Promise.resolve({ + dataLake: {}, + build: { version: '5.0.1' }, + genuineMongoDB: {}, + host: {} + }), createCollection: (namespace, options, callback) => { callback(null); } @@ -1187,7 +1177,7 @@ suite('MDBExtensionController Test Suite', function () { test('mdb.renameConnection fails when the name input is empty', (done) => { mdbTestExtension.testExtensionController._connectionController._connections.blueBerryPancakesAndTheSmellOfBacon = { id: 'blueBerryPancakesAndTheSmellOfBacon', - connectionModel: new Connection(), + connectionOptions: { connectionString: 'mongodb://localhost' }, name: 'NAAAME', storageLocation: StorageScope.NONE }; @@ -1226,7 +1216,7 @@ suite('MDBExtensionController Test Suite', function () { mdbTestExtension.testExtensionController._connectionController._connections.blueBerryPancakesAndTheSmellOfBacon = { id: 'blueBerryPancakesAndTheSmellOfBacon', name: 'NAAAME', - connectionModel: new Connection(), + connectionOptions: { connectionString: 'mongodb://localhost' }, storageLocation: StorageScope.NONE }; diff --git a/src/test/suite/storage/storageController.test.ts b/src/test/suite/storage/storageController.test.ts index 5e16cd29c..1cd06255a 100644 --- a/src/test/suite/storage/storageController.test.ts +++ b/src/test/suite/storage/storageController.test.ts @@ -1,24 +1,27 @@ +import * as vscode from 'vscode'; import assert from 'assert'; import StorageController, { StorageVariables, - StorageScope + StorageScope, + DefaultSavingLocations } from '../../../storage/storageController'; - import { TestExtensionContext } from '../stubs'; suite('Storage Controller Test Suite', () => { test('getting a variable gets it from the global context store', () => { const testExtensionContext = new TestExtensionContext(); testExtensionContext._globalState = { - [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: 'this_gonna_get_saved' + [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: { + 'collOne': { name: 'this_gonna_get_saved' } + } }; const testStorageController = new StorageController(testExtensionContext); const testVal = testStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS ); assert( - testVal === 'this_gonna_get_saved', + testVal.collOne.name === 'this_gonna_get_saved', `Expected ${testVal} from global state to equal 'this_gonna_get_saved'.` ); }); @@ -26,8 +29,9 @@ suite('Storage Controller Test Suite', () => { test('getting a variable from the workspace state gets it from the workspace context store', () => { const testExtensionContext = new TestExtensionContext(); testExtensionContext._workspaceState = { - [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: - 'i_cant_believe_its_gonna_save_this' + [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: { + 'collTwo': { name: 'i_cant_believe_its_gonna_save_this' } + } }; const testStorageController = new StorageController(testExtensionContext); const testVal = testStorageController.get( @@ -35,27 +39,116 @@ suite('Storage Controller Test Suite', () => { StorageScope.WORKSPACE ); assert( - testVal === 'i_cant_believe_its_gonna_save_this', + testVal.collTwo.name === 'i_cant_believe_its_gonna_save_this', `Expected ${testVal} from workspace state to equal 'i_cant_believe_its_gonna_save_this'.` ); }); + test('storeNewConnection adds the connection to preexisting connections on the global store', async () => { + await vscode.workspace + .getConfiguration('mdb.connectionSaving') + .update('defaultConnectionSavingLocation', DefaultSavingLocations.Global); + + const testExtensionContext = new TestExtensionContext(); + testExtensionContext._globalState = { + [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: { + conn1: { + id: 'conn1', + name: 'saved1', + storageLocation: StorageScope.GLOBAL, + connectionOptions: { connectionString: 'mongodb://localhost' } + } + } + }; + const testStorageController = new StorageController(testExtensionContext); + void testStorageController.storeNewConnection( + { + id: 'conn2', + connectionOptions: { connectionString: 'mongodb://localhost' } + } + ); + + const updatedGlobalModels = testStorageController.get( + StorageVariables.GLOBAL_SAVED_CONNECTIONS + ) || {}; + assert( + Object.keys(updatedGlobalModels).length === 2, + `Expected 2 connections, found ${Object.keys(updatedGlobalModels).length + }.` + ); + assert( + updatedGlobalModels.conn1.name === 'saved1', + 'Expected connection data to persist.' + ); + assert( + updatedGlobalModels.conn2.storageLocation === StorageScope.GLOBAL, + 'Expected storage scope to be set.' + ); + assert(testStorageController.hasSavedConnections()); + }); + + test('storeNewConnection adds the connection to preexisting connections on the workspace store', async () => { + await vscode.workspace + .getConfiguration('mdb.connectionSaving') + .update('defaultConnectionSavingLocation', DefaultSavingLocations.Workspace); + + const testExtensionContext = new TestExtensionContext(); + testExtensionContext._workspaceState = { + [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: { + conn1: { + id: 'conn1', + name: 'saved1', + storageLocation: StorageScope.WORKSPACE, + connectionOptions: { connectionString: 'mongodb://localhost' } + } + } + }; + const testStorageController = new StorageController(testExtensionContext); + void testStorageController.storeNewConnection( + { + id: 'conn2', + connectionOptions: { connectionString: 'mongodb://localhost' } + } + ); + + const updatedWorkspaceModels = testStorageController.get( + StorageVariables.WORKSPACE_SAVED_CONNECTIONS, + StorageScope.WORKSPACE + ); + assert( + Object.keys(updatedWorkspaceModels).length === 2, + `Expected 2 connections, found ${Object.keys(updatedWorkspaceModels).length + }.` + ); + assert( + updatedWorkspaceModels.conn1.name === 'saved1', + 'Expected connection data to persist.' + ); + assert( + updatedWorkspaceModels.conn2.storageLocation === StorageScope.WORKSPACE, + 'Expected storage scope to be set.' + ); + assert(testStorageController.hasSavedConnections()); + }); + test('addNewConnectionToGlobalStore adds the connection to preexisting connections on the global store', () => { const testExtensionContext = new TestExtensionContext(); testExtensionContext._globalState = { [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: { conn1: { - driverUrl: 'so_saved', id: 'conn1', - name: 'so_saved' + name: 'saved1', + storageLocation: StorageScope.GLOBAL, + connectionOptions: { connectionString: 'mongodb://localhost' } } } }; const testStorageController = new StorageController(testExtensionContext); void testStorageController.saveConnectionToGlobalStore({ - id: 'new_conn', + id: 'conn2', name: 'saved2', - storageLocation: StorageScope.NONE + storageLocation: StorageScope.GLOBAL, + connectionOptions: { connectionString: 'mongodb://localhost' } }); const updatedGlobalModels = testStorageController.get( @@ -67,15 +160,11 @@ suite('Storage Controller Test Suite', () => { }.` ); assert( - updatedGlobalModels.conn1.name === 'so_saved', + updatedGlobalModels.conn1.name === 'saved1', 'Expected connection data to persist.' ); assert( - !updatedGlobalModels.new_conn.driverUrl, - 'Expected new connection data to not exist on the saved connection.' - ); - assert( - updatedGlobalModels.new_conn.storageLocation === StorageScope.GLOBAL, + updatedGlobalModels.conn2.storageLocation === StorageScope.GLOBAL, 'Expected storage scope to be set.' ); assert(testStorageController.hasSavedConnections()); @@ -87,15 +176,18 @@ suite('Storage Controller Test Suite', () => { [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: { conn1: { id: 'conn1', - name: 'saved1' + name: 'saved1', + storageLocation: StorageScope.WORKSPACE, + connectionOptions: { connectionString: 'mongodb://localhost' } } } }; const testStorageController = new StorageController(testExtensionContext); void testStorageController.saveConnectionToWorkspaceStore({ - id: 'new_conn', + id: 'conn2', name: 'saved2', - storageLocation: StorageScope.NONE + storageLocation: StorageScope.WORKSPACE, + connectionOptions: { connectionString: 'mongodb://localhost:27018' } }); const updatedWorkspaceModels = testStorageController.get( @@ -112,19 +204,11 @@ suite('Storage Controller Test Suite', () => { 'Expected connection id data to persist.' ); assert( - !updatedWorkspaceModels.conn1.driverUrl, - 'Expected connection string data to not persist.' - ); - assert( - !updatedWorkspaceModels.new_conn.driverUrl, - 'Expected new connection data to not exist.' - ); - assert( - updatedWorkspaceModels.new_conn.name === 'saved2', + updatedWorkspaceModels.conn2.name === 'saved2', 'Expected new connection data to exist.' ); assert( - updatedWorkspaceModels.new_conn.storageLocation === + updatedWorkspaceModels.conn2.storageLocation === StorageScope.WORKSPACE, 'Expected storage scope to be set.' ); diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index 1a34f62c7..330f5cb9b 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -135,12 +135,16 @@ for (let i = 0; i < numberOfDocumentsToMock; i++) { } class DataServiceStub { - listDatabases(callback: any): void { - callback(null, mockDatabaseNames.map(dbName => ({ name: dbName }))); + listDatabases(): Promise { + return new Promise((resolve) => { + resolve(mockDatabaseNames.map(dbName => ({ name: dbName }))); + }); } - listCollections(databaseName: string, filter: object, callback: any): void { - callback(null, mockDatabases[databaseName].collections); + listCollections(databaseName: string): Promise { + return new Promise((resolve) => { + resolve(mockDatabases[databaseName].collections); + }); } find(namespace: string, filter: any, options: any, callback: any): void { @@ -258,7 +262,7 @@ class MockLanguageServerController { connectToServiceProvider(/* params: { connectionString?: string; - connectionOptions?: any; + connectionOptions?: MongoClientOptions; extensionPath: string; }*/): Promise { return Promise.resolve(); @@ -268,10 +272,6 @@ class MockLanguageServerController { return Promise.resolve(); } - startStreamLanguageServerLogs(): Promise { - return Promise.resolve(true); - } - cancelAll(): void { return; } diff --git a/src/test/suite/telemetry/connectionTelemetry.test.ts b/src/test/suite/telemetry/connectionTelemetry.test.ts index 81463f800..80977ccce 100644 --- a/src/test/suite/telemetry/connectionTelemetry.test.ts +++ b/src/test/suite/telemetry/connectionTelemetry.test.ts @@ -1,33 +1,36 @@ -import sinon from 'sinon'; -import DataService = require('mongodb-data-service'); -import Connection = require('mongodb-connection-model/lib/model'); -import { expect } from 'chai'; -import { promisify } from 'util'; import { beforeEach, afterEach } from 'mocha'; +import { connect } from 'mongodb-data-service'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { ConnectionTypes } from '../../../connectionController'; import { getConnectionTelemetryProperties } from '../../../telemetry/connectionTelemetry'; -import { ConnectionTypes } from '../../../connectionController'; + +const TEST_DATABASE_URI = 'mongodb://localhost:27018'; suite('ConnectionTelemetry Controller Test Suite', function () { this.timeout(5000); - suite('with mock client', () => { - const mockClient: any = { - db: () => ({ - command: () => ({}) + suite('with mock data service', () => { + const mockDataService: any = { + getConnectionString: () => ({ + hosts: ['localhost:27018'], + searchParams: { get: () => null }, + username: 'authMechanism' + }), + instance: () => Promise.resolve({ + dataLake: {}, + build: {}, + genuineMongoDB: {}, + host: {} }) }; - const testConnectionModel = new Connection({ - hostname: 'scubatank', - port: 22345 - }); test('it returns is_used_connect_screen true when the connection type is form', async () => { const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_FORM ); @@ -35,10 +38,10 @@ suite('ConnectionTelemetry Controller Test Suite', function () { expect(instanceTelemetry.is_used_command_palette).to.equal(false); expect(instanceTelemetry.is_used_saved_connection).to.equal(false); }); + test('it returns is_used_command_palette true when the connection type is string', async () => { const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_STRING ); @@ -46,10 +49,10 @@ suite('ConnectionTelemetry Controller Test Suite', function () { expect(instanceTelemetry.is_used_command_palette).to.equal(true); expect(instanceTelemetry.is_used_saved_connection).to.equal(false); }); + test('it returns is_used_saved_connection true when the connection type is id', async () => { const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_ID ); @@ -57,91 +60,51 @@ suite('ConnectionTelemetry Controller Test Suite', function () { expect(instanceTelemetry.is_used_command_palette).to.equal(false); expect(instanceTelemetry.is_used_saved_connection).to.equal(true); }); + test('it has is_localhost false for a remote connection', async () => { const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_STRING ); expect(instanceTelemetry.is_localhost).to.equal(false); - expect(instanceTelemetry.is_atlas).to.equal(false); - expect(instanceTelemetry.is_genuine).to.equal(true); }); - test('it has is_localhost false for a remote connection', async () => { + + test('it has a default is atlas false', async () => { const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_STRING ); - expect(instanceTelemetry.is_localhost).to.equal(false); expect(instanceTelemetry.is_atlas).to.equal(false); - expect(instanceTelemetry.is_genuine).to.equal(true); }); - test('it has is_atlas true for an atlas url connection', async () => { - const connectionModel = new Connection({ - hostname: 'test.mongodb.net', - port: 22345 - }); - const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - connectionModel, - ConnectionTypes.CONNECTION_STRING - ); - expect(instanceTelemetry.is_localhost).to.equal(false); - expect(instanceTelemetry.is_atlas).to.equal(true); - expect(instanceTelemetry.is_genuine).to.equal(true); - }); test('it has a default driver auth mechanism undefined', async () => { const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_STRING ); - expect(instanceTelemetry.auth_strategy).to.equal(undefined); - }); - test('it has the driver auth mechanism for x509', async () => { - const connectionModel = new Connection({ - hostname: 'localhost', - authStrategy: 'X509', - port: 27040 - }); - const instanceTelemetry = await getConnectionTelemetryProperties( - mockClient, - connectionModel, - ConnectionTypes.CONNECTION_STRING - ); - - expect(instanceTelemetry.auth_strategy).to.equal('MONGODB-X509'); + expect(instanceTelemetry.auth_strategy).to.equal('DEFAULT'); }); }); suite('with live connection', () => { let dataServ; - const connectionModel = new Connection({ - hostname: 'localhost', - port: 27018 - }); beforeEach(async () => { - dataServ = new DataService(connectionModel); - const runConnect = promisify(dataServ.connect.bind(dataServ)); - await runConnect(); + dataServ = await connect({ connectionString: TEST_DATABASE_URI }); + await dataServ.connect(); }); afterEach(async () => { sinon.restore(); - const runDisconnect = promisify(dataServ.disconnect.bind(dataServ)); - await runDisconnect(); + await dataServ.disconnect(); }); test('track new connection event fetches the connection instance information', async() => { const instanceTelemetry = await getConnectionTelemetryProperties( - dataServ.client.client, - connectionModel, + dataServ, ConnectionTypes.CONNECTION_STRING ); diff --git a/src/test/suite/telemetry/telemetryService.test.ts b/src/test/suite/telemetry/telemetryService.test.ts index b6fc721c6..eb6116cc3 100644 --- a/src/test/suite/telemetry/telemetryService.test.ts +++ b/src/test/suite/telemetry/telemetryService.test.ts @@ -1,41 +1,45 @@ import * as vscode from 'vscode'; +import * as path from 'path'; import { afterEach, beforeEach } from 'mocha'; import chai from 'chai'; +import { connect } from 'mongodb-data-service'; import { config } from 'dotenv'; -import * as path from 'path'; import { resolve } from 'path'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; -import { promisify } from 'util'; import Sinon = require('sinon'); -import DataService = require('mongodb-data-service'); -import Connection = require('mongodb-connection-model/lib/model'); import { ConnectionTypes } from '../../../connectionController'; +import { DocumentSource } from '../../../documentSource'; +import { mdbTestExtension } from '../stubbableMdbExtension'; +import { + NewConnectionTelemetryEventProperties +} from '../../../telemetry/connectionTelemetry'; import { SegmentProperties, TelemetryEventTypes } from '../../../telemetry/telemetryService'; -import { - NewConnectionTelemetryEventProperties -} from '../../../telemetry/connectionTelemetry'; -import { DocumentSource } from '../../../documentSource'; -import { mdbTestExtension } from '../stubbableMdbExtension'; -const { version } = require('../../../../package.json'); const expect = chai.expect; +const { version } = require('../../../../package.json'); + +const TEST_DATABASE_URI = 'mongodb://localhost:27018'; chai.use(sinonChai); config({ path: resolve(__dirname, '../../../../.env') }); suite('Telemetry Controller Test Suite', () => { - const testConnectionModel = new Connection({ - hostname: 'localhost', - port: 27018 - }); const testTelemetryService = mdbTestExtension.testExtensionController._telemetryService; + const mockDataService: any = sinon.fake.returns({ + instance: () => Promise.resolve({ + dataLake: {}, + build: {}, + genuineMongoDB: {}, + host: {} + }) + }); let mockTrackNewConnection: Sinon.SinonSpy; let mockTrackCommandRun: Sinon.SinonSpy; @@ -110,15 +114,13 @@ suite('Telemetry Controller Test Suite', () => { ); mockConnectionController.sendTelemetry( - { client: {} } as any, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_STRING ); sinon.assert.calledWith( mockTrackNewConnection, sinon.match.any, - testConnectionModel, sinon.match(ConnectionTypes.CONNECTION_STRING) ); }); @@ -134,15 +136,13 @@ suite('Telemetry Controller Test Suite', () => { ); mockConnectionController.sendTelemetry( - { client: {} } as any, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_FORM ); sinon.assert.calledWith( mockTrackNewConnection, sinon.match.any, - testConnectionModel, sinon.match(ConnectionTypes.CONNECTION_FORM) ); }); @@ -158,15 +158,13 @@ suite('Telemetry Controller Test Suite', () => { ); mockConnectionController.sendTelemetry( - { client: {} } as any, - testConnectionModel, + mockDataService, ConnectionTypes.CONNECTION_ID ); sinon.assert.calledWith( mockTrackNewConnection, sinon.match.any, - testConnectionModel, sinon.match(ConnectionTypes.CONNECTION_ID) ); }); @@ -304,29 +302,21 @@ suite('Telemetry Controller Test Suite', () => { this.timeout(5000); let dataServ; - const connectionModel = new Connection({ - hostname: 'localhost', - port: 27018 - }); - beforeEach(async () => { - dataServ = new DataService(connectionModel); - const runConnect = promisify(dataServ.connect.bind(dataServ)); - await runConnect(); + dataServ = await connect({ connectionString: TEST_DATABASE_URI }); + await dataServ.connect(); }); afterEach(async () => { sinon.restore(); - const runDisconnect = promisify(dataServ.disconnect.bind(dataServ)); - await runDisconnect(); + await dataServ.disconnect(); }); test('track new connection event fetches the connection instance information', async() => { sinon.replace(testTelemetryService, 'track', mockTrack); sinon.replace(testTelemetryService, '_isTelemetryFeatureEnabled', () => true); await mdbTestExtension.testExtensionController._telemetryService.trackNewConnection( - dataServ.client.client, - connectionModel, + dataServ, ConnectionTypes.CONNECTION_STRING ); diff --git a/src/test/suite/utils/connection-secrets.test.ts b/src/test/suite/utils/connection-secrets.test.ts new file mode 100644 index 000000000..2e29036c2 --- /dev/null +++ b/src/test/suite/utils/connection-secrets.test.ts @@ -0,0 +1,167 @@ +import { ConnectionInfo } from 'mongodb-data-service'; +import { expect } from 'chai'; + +import { + mergeSecrets, + extractSecrets, + ConnectionSecrets, +} from '../../../utils/connectionSecrets'; + +suite('connection secrets', () => { + suite('mergeSecrets', () => { + test('does not modify the original object', () => { + const originalConnectionInfo: ConnectionInfo = { + connectionOptions: { + connectionString: 'mongodb://localhost:27017', + sshTunnel: { + host: 'localhost', + username: 'user', + port: 22, + }, + }, + favorite: { + name: 'connection 1', + }, + }; + + const originalConnectionInfoStr = JSON.stringify(originalConnectionInfo); + + const newConnectionInfo = mergeSecrets(originalConnectionInfo, { + password: 'xxx', + awsSessionToken: 'xxx', + sshTunnelPassphrase: 'xxx', + tlsCertificateKeyFilePassword: 'xxx', + }); + + expect(newConnectionInfo).to.not.equal(originalConnectionInfo); + + expect(newConnectionInfo.connectionOptions).to.not.equal( + originalConnectionInfo.connectionOptions + ); + + expect(newConnectionInfo.connectionOptions.sshTunnel).to.not.equal( + originalConnectionInfo.connectionOptions.sshTunnel + ); + + expect(newConnectionInfo.favorite).to.not.equal( + originalConnectionInfo.favorite + ); + + expect(originalConnectionInfoStr).to.equal( + JSON.stringify(originalConnectionInfo) + ); + }); + + test('merges secrets', () => { + const originalConnectionInfo: ConnectionInfo = { + connectionOptions: { + connectionString: 'mongodb://username@localhost:27017/', + sshTunnel: { + host: 'localhost', + username: 'user', + port: 22, + }, + }, + }; + + const newConnectionInfo = mergeSecrets(originalConnectionInfo, { + awsSessionToken: 'sessionToken', + password: 'userPassword', + sshTunnelPassphrase: 'passphrase', + tlsCertificateKeyFilePassword: 'tlsCertPassword', + }); + + expect(newConnectionInfo).to.be.deep.equal({ + connectionOptions: { + connectionString: + 'mongodb://username:userPassword@localhost:27017/?tlsCertificateKeyFilePassword=tlsCertPassword&authMechanismProperties=AWS_SESSION_TOKEN%3AsessionToken', + sshTunnel: { + host: 'localhost', + username: 'user', + port: 22, + identityKeyPassphrase: 'passphrase', + }, + }, + } as ConnectionInfo); + }); + }); + + suite('extractSecrets', () => { + test('does not modify the original object', () => { + const originalConnectionInfo: ConnectionInfo = { + connectionOptions: { + connectionString: 'mongodb://localhost:27017', + sshTunnel: { + host: 'localhost', + username: 'user', + port: 22, + }, + }, + favorite: { + name: 'connection 1', + }, + }; + + const originalConnectionInfoStr = JSON.stringify(originalConnectionInfo); + + const { connectionInfo: newConnectionInfo } = extractSecrets( + originalConnectionInfo + ); + + expect(newConnectionInfo).to.not.equal(originalConnectionInfo); + + expect(newConnectionInfo.connectionOptions).to.not.equal( + originalConnectionInfo.connectionOptions + ); + + expect(newConnectionInfo.connectionOptions.sshTunnel).to.not.equal( + originalConnectionInfo.connectionOptions.sshTunnel + ); + + expect(newConnectionInfo.favorite).to.not.equal( + originalConnectionInfo.favorite + ); + + expect(originalConnectionInfoStr).to.equal( + JSON.stringify(originalConnectionInfo) + ); + }); + + test('extracts secrets', () => { + const originalConnectionInfo: ConnectionInfo = { + connectionOptions: { + connectionString: + 'mongodb://username:userPassword@localhost:27017/?tlsCertificateKeyFilePassword=tlsCertPassword&authMechanismProperties=AWS_SESSION_TOKEN%3AsessionToken', + sshTunnel: { + host: 'localhost', + username: 'user', + port: 22, + identityKeyPassphrase: 'passphrase', + }, + }, + }; + + const { connectionInfo: newConnectionInfo, secrets } = extractSecrets( + originalConnectionInfo + ); + + expect(newConnectionInfo).to.be.deep.equal({ + connectionOptions: { + connectionString: 'mongodb://username@localhost:27017/', + sshTunnel: { + host: 'localhost', + username: 'user', + port: 22, + }, + }, + } as ConnectionInfo); + + expect(secrets).to.be.deep.equal({ + awsSessionToken: 'sessionToken', + password: 'userPassword', + sshTunnelPassphrase: 'passphrase', + tlsCertificateKeyFilePassword: 'tlsCertPassword', + } as ConnectionSecrets); + }); + }); +}); diff --git a/src/test/suite/views/webviewController.test.ts b/src/test/suite/views/webviewController.test.ts index ba0756649..c3fc5eb59 100644 --- a/src/test/suite/views/webviewController.test.ts +++ b/src/test/suite/views/webviewController.test.ts @@ -1,21 +1,21 @@ -import assert from 'assert'; +import * as linkHelper from '../../../utils/linkHelper'; +import * as sinon from 'sinon'; import * as vscode from 'vscode'; +import assert from 'assert'; import { beforeEach, afterEach } from 'mocha'; -import * as sinon from 'sinon'; -import Connection = require('mongodb-connection-model/lib/model'); +import ConnectionModel from 'mongodb-connection-model'; -import * as linkHelper from '../../../utils/linkHelper'; -import TelemetryService from '../../../telemetry/telemetryService'; import ConnectionController from '../../../connectionController'; +import { mdbTestExtension } from '../stubbableMdbExtension'; +import { MESSAGE_TYPES } from '../../../views/webview-app/extension-app-message-constants'; +import { StatusView } from '../../../views'; import { StorageController } from '../../../storage'; +import TelemetryService from '../../../telemetry/telemetryService'; +import { TestExtensionContext } from '../stubs'; +import { TEST_DATABASE_URI } from '../dbTestHelper'; import WebviewController, { getWebviewContent } from '../../../views/webviewController'; -import { StatusView } from '../../../views'; -import { MESSAGE_TYPES } from '../../../views/webview-app/extension-app-message-constants'; -import { mdbTestExtension } from '../stubbableMdbExtension'; -import { TestExtensionContext } from '../stubs'; -import { TEST_DATABASE_URI } from '../dbTestHelper'; const fs = require('fs'); const path = require('path'); @@ -124,9 +124,6 @@ suite('Webview Test Suite', () => { testConnectionController.getActiveConnectionName() === 'localhost:27018' ); - assert( - testConnectionController.getActiveConnectionModel()?.port === 27018 - ); await testConnectionController.disconnect(); done(); @@ -160,7 +157,7 @@ suite('Webview Test Suite', () => { 'Ensure it starts listening for messages from the webview.' ); - Connection.from(TEST_DATABASE_URI, (err, connectionModel) => { + ConnectionModel.from(TEST_DATABASE_URI, (err, connectionModel) => { if (err) { assert(false); } @@ -233,7 +230,7 @@ suite('Webview Test Suite', () => { 'Ensure it starts listening for messages from the webview.' ); - Connection.from(TEST_DATABASE_URI, (err, connectionModel) => { + ConnectionModel.from(TEST_DATABASE_URI, (err, connectionModel) => { if (err) { assert(false); } diff --git a/src/types/connectionModelType.ts b/src/types/connectionModelType.ts deleted file mode 100644 index 49612e0c7..000000000 --- a/src/types/connectionModelType.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Host } from '../views/webview-app/connection-model/connection-model'; -import SSH_TUNNEL_TYPES from '../views/webview-app/connection-model/constants/ssh-tunnel-types'; -import { ConnectionOptions } from './connectionOptionsType'; - -export type ConnectionModel = { - appname: string; - hosts: Host[]; - hostname: string; - isSrvRecord: boolean; - port: number; - driverUrl: string; - driverUrlWithSsh: string; - sshTunnel?: SSH_TUNNEL_TYPES; - getAttributes(options: { - derived?: boolean, - props?: boolean - }): { - driverUrl: string; - driverAuthMechanism: string; - driverUrlWithSsh: string; - driverOptions: ConnectionOptions; - sshTunnelOptions: { - host?: string; - port?: number; - }; - }; - disconnect(callback: (n: Error | undefined) => void): void; -}; diff --git a/src/types/connectionOptionsType.ts b/src/types/connectionOptionsType.ts deleted file mode 100644 index c5c951e8f..000000000 --- a/src/types/connectionOptionsType.ts +++ /dev/null @@ -1,22 +0,0 @@ -import READ_PREFERENCES from '../views/webview-app/connection-model/constants/read-preferences'; - -export type ConnectionOptions = { - appname?: string; - auth?: { - user: string; - password: string; - }; - useUnifiedTopology?: boolean; - useNewUrlParser?: boolean; - port?: number; - authSource?: string; - authMechanism?: string; - checkServerIdentity?: boolean; - promoteValues?: boolean; - sslValidate?: boolean; - sslCA?: string | string[]; - sslKey?: string | string[]; - sslCert?: string | string[]; - sslPass?: string; - readPreference?: READ_PREFERENCES; -}; diff --git a/src/types/dataServiceType.ts b/src/types/dataServiceType.ts deleted file mode 100644 index 9d9375855..000000000 --- a/src/types/dataServiceType.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { EJSON } from 'bson'; -import { MongoClient } from 'mongodb'; - -import { ConnectionOptions } from './connectionOptionsType'; - -export type DataServiceType = { - connect(callback: (error: Error | undefined) => void): void; - disconnect(callback: (error: Error | undefined) => void): void; - - getConnectionOptions(): { - options: ConnectionOptions; - url: string - } - - listDatabases( - callback: ( - error: Error | undefined, - databases: { name: string }[] - ) => void - ): void; - - createCollection( - namespace: string, - options: object, - callback: (error: Error | undefined) => void - ): void; - - find( - namespace: string, - filter: object, - options: object, - callback: (error: Error | undefined, documents: object[]) => void - ): void; - - findOneAndReplace( - namespace: string, - filter: object, - replacement: EJSON.SerializableTypes, - options: object, - callback: (error: Error | undefined, result?: object) => void - ): void; - - instance(opts: any, callback: any): any; - - client: { - client: MongoClient - }; -}; diff --git a/src/utils/connectionSecrets.ts b/src/utils/connectionSecrets.ts new file mode 100644 index 000000000..3874241b0 --- /dev/null +++ b/src/utils/connectionSecrets.ts @@ -0,0 +1,120 @@ +import { cloneDeep } from 'lodash'; +import ConnectionString, { + CommaAndColonSeparatedRecord, +} from 'mongodb-connection-string-url'; +import { ConnectionInfo } from 'mongodb-data-service'; + +export interface ConnectionSecrets { + password?: string; + sshTunnelPassphrase?: string; + awsSessionToken?: string; + tlsCertificateKeyFilePassword?: string; +} + +const AWS_SESSION_TOKEN_PROPERTY = 'AWS_SESSION_TOKEN'; +const AUTH_MECHANISM_PROPERTIES_PARAM = 'authMechanismProperties'; +const TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM = 'tlsCertificateKeyFilePassword'; + +export const mergeSecrets = ( + connectionInfo: Readonly, + secrets: ConnectionSecrets | undefined +): ConnectionInfo => { + const connectionInfoWithSecrets = cloneDeep(connectionInfo); + + if (!secrets) { + return connectionInfoWithSecrets; + } + + const connectionOptions = connectionInfoWithSecrets.connectionOptions; + + const uri = new ConnectionString(connectionOptions.connectionString); + + if (secrets.password) { + uri.password = secrets.password; + } + + if (secrets.sshTunnelPassphrase && connectionOptions.sshTunnel) { + connectionOptions.sshTunnel.identityKeyPassphrase = + secrets.sshTunnelPassphrase; + } + + if (secrets.tlsCertificateKeyFilePassword) { + uri.searchParams.set( + TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM, + secrets.tlsCertificateKeyFilePassword + ); + } + + if (secrets.awsSessionToken) { + const authMechanismProperties = new CommaAndColonSeparatedRecord( + uri.searchParams.get(AUTH_MECHANISM_PROPERTIES_PARAM) + ); + + authMechanismProperties.set( + AWS_SESSION_TOKEN_PROPERTY, + secrets.awsSessionToken + ); + + uri.searchParams.set( + AUTH_MECHANISM_PROPERTIES_PARAM, + authMechanismProperties.toString() + ); + } + + connectionInfoWithSecrets.connectionOptions.connectionString = uri.href; + + return connectionInfoWithSecrets; +}; + +export const extractSecrets = (connectionInfo: Readonly): { + connectionInfo: ConnectionInfo; + secrets: ConnectionSecrets; +} => { + const connectionInfoWithoutSecrets = cloneDeep(connectionInfo); + const secrets: ConnectionSecrets = {}; + + const connectionOptions = connectionInfoWithoutSecrets.connectionOptions; + const uri = new ConnectionString(connectionOptions.connectionString); + + if (uri.password) { + secrets.password = uri.password; + uri.password = ''; + } + + if (connectionOptions.sshTunnel?.identityKeyPassphrase) { + secrets.sshTunnelPassphrase = + connectionOptions.sshTunnel.identityKeyPassphrase; + delete connectionOptions.sshTunnel.identityKeyPassphrase; + } + + if (uri.searchParams.has(TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM)) { + secrets.tlsCertificateKeyFilePassword = + uri.searchParams.get(TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM) || + undefined; + uri.searchParams.delete(TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM); + } + + const authMechanismProperties = new CommaAndColonSeparatedRecord( + uri.searchParams.get(AUTH_MECHANISM_PROPERTIES_PARAM) + ); + + if (authMechanismProperties.has(AWS_SESSION_TOKEN_PROPERTY)) { + secrets.awsSessionToken = authMechanismProperties.get( + AWS_SESSION_TOKEN_PROPERTY + ); + authMechanismProperties.delete(AWS_SESSION_TOKEN_PROPERTY); + + if (authMechanismProperties.toString()) { + uri.searchParams.set( + AUTH_MECHANISM_PROPERTIES_PARAM, + authMechanismProperties.toString() + ); + } else { + uri.searchParams.delete(AUTH_MECHANISM_PROPERTIES_PARAM); + } + } + + connectionInfoWithoutSecrets.connectionOptions.connectionString = uri.href; + + return { connectionInfo: connectionInfoWithoutSecrets, secrets }; +}; diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index d16f48a78..88fc91fb2 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -4,18 +4,18 @@ import path from 'path'; import ConnectionController, { ConnectionTypes } from '../connectionController'; -import TelemetryService from '../telemetry/telemetryService'; +import ConnectionModel from './webview-app/connection-model/connection-model'; +import { createLogger } from '../logging'; +import EXTENSION_COMMANDS from '../commands'; import { MESSAGE_FROM_WEBVIEW_TO_EXTENSION, MESSAGE_TYPES, USER_ID_GLOBAL_VARIABLE_NAME, OpenFilePickerMessage } from './webview-app/extension-app-message-constants'; -import { createLogger } from '../logging'; -import EXTENSION_COMMANDS from '../commands'; -import ConnectionModel from './webview-app/connection-model/connection-model'; import { openLink } from '../utils/linkHelper'; import { StorageController } from '../storage'; +import TelemetryService from '../telemetry/telemetryService'; const log = createLogger('webviewController'); @@ -83,8 +83,7 @@ export default class WebviewController { connectionAttemptId: string ): Promise => { try { - const connectionModel = this._connectionController - .parseNewConnection(rawConnectionModel as any); + const connectionModel = this._connectionController.parseNewConnection(rawConnectionModel); const { successfullyConnected, @@ -165,9 +164,7 @@ export default class WebviewController { return; case MESSAGE_TYPES.OPEN_CONNECTION_STRING_INPUT: void vscode.commands.executeCommand(EXTENSION_COMMANDS.MDB_CONNECT_WITH_URI); - return; - case MESSAGE_TYPES.OPEN_TRUSTED_LINK: try { await openLink(message.linkTo); @@ -184,16 +181,13 @@ export default class WebviewController { message.screen, message.linkId ); - return; - case MESSAGE_TYPES.RENAME_ACTIVE_CONNECTION: if (this._connectionController.isCurrentlyConnected()) { void this._connectionController.renameConnection( this._connectionController.getActiveConnectionId() as string ); } - return; default: // no-op. From 066fae18c711414d0ace0f41cbc1cf8135f13c25 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Thu, 16 Dec 2021 14:09:07 +0100 Subject: [PATCH 02/15] feat: handle secrets and migrate old connections VSCODE-313, VSCODE-314 --- package-lock.json | 25 +-- package.json | 3 +- src/connectionController.ts | 210 +++++++++++------- src/explorer/explorerTreeController.ts | 4 +- src/storage/storageController.ts | 125 +++++------ src/test/suite/connectionController.test.ts | 38 ++-- .../collectionDocumentsProvider.test.ts | 6 +- .../suite/explorer/explorerController.test.ts | 8 +- src/test/suite/mdbExtensionController.test.ts | 6 +- .../suite/storage/storageController.test.ts | 107 +-------- src/utils/connectionSecrets.ts | 3 +- 11 files changed, 240 insertions(+), 295 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0de9a518d..7a0b0061a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.6.0", - "lodash": "^4.17.21", + "lodash.clonedeep": "^4.5.0", "micromatch": "^4.0.4", "mongodb": "^4.2.0", "mongodb-cloud-info": "^1.1.3", @@ -59,7 +59,6 @@ "@types/enzyme": "^3.10.10", "@types/glob": "^7.2.0", "@types/jest": "^26.0.24", - "@types/lodash": "^4.14.178", "@types/mocha": "^8.2.3", "@types/node": "^14.17.33", "@types/react": "^17.0.35", @@ -2512,12 +2511,6 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, - "node_modules/@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", - "dev": true - }, "node_modules/@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -14365,6 +14358,11 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "optional": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -26243,12 +26241,6 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, - "@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", - "dev": true - }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -36170,6 +36162,11 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "optional": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", diff --git a/package.json b/package.json index a53d87535..918fcb103 100644 --- a/package.json +++ b/package.json @@ -916,7 +916,7 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.6.0", - "lodash": "^4.17.21", + "lodash.clonedeep": "^4.5.0", "micromatch": "^4.0.4", "mongodb": "^4.2.0", "mongodb-cloud-info": "^1.1.3", @@ -947,7 +947,6 @@ "@types/enzyme": "^3.10.10", "@types/glob": "^7.2.0", "@types/jest": "^26.0.24", - "@types/lodash": "^4.14.178", "@types/mocha": "^8.2.3", "@types/node": "^14.17.33", "@types/react": "^17.0.35", diff --git a/src/connectionController.ts b/src/connectionController.ts index 0670bee49..4205f1216 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -5,22 +5,26 @@ import { convertConnectionInfoToModel, ConnectionInfo, ConnectionOptions, - DataService + DataService, + getConnectionTitle } from 'mongodb-data-service'; import ConnectionModel from 'mongodb-connection-model'; import { EventEmitter } from 'events'; import { promisify } from 'util'; +import { v4 as uuidv4 } from 'uuid'; import { CONNECTION_STATUS } from './views/webview-app/extension-app-message-constants'; import { createLogger } from './logging'; import { ext } from './extensionConstants'; -import { ConnectionSecrets, extractSecrets, mergeSecrets } from './utils/connectionSecrets'; import RawConnectionModel from './views/webview-app/connection-model/connection-model'; -import { StorageScope, ConnectionsFromStorage } from './storage/storageController'; +import { StorageLocation, ConnectionsFromStorage } from './storage/storageController'; import { StorageController, StorageVariables } from './storage'; import { StatusView } from './views'; import TelemetryService from './telemetry/telemetryService'; +// TODO: export these helpers from mongodb-data-service. +import { ConnectionSecrets, extractSecrets, mergeSecrets } from './utils/connectionSecrets'; + const log = createLogger('connection controller'); const { name, version } = require('../package.json'); @@ -37,11 +41,12 @@ export enum ConnectionTypes { CONNECTION_ID = 'CONNECTION_ID' } -export interface SavedConnectionInfo { +export interface StoreConnectionInfo { id: string; // Connection model id or a new uuid. name: string; // Possibly user given name, not unique. - storageLocation: StorageScope; + storageLocation: StorageLocation; connectionOptions: ConnectionOptions; + connectionModel?: ConnectionModel; } export enum NewConnectionType { @@ -59,11 +64,16 @@ interface СonnectionQuickPicks { data: { type: NewConnectionType, connectionId?: string } } +interface ConnectionSecretsInfo { + connectionId: string; + secrets: ConnectionSecrets +} + export default class ConnectionController { // This is a map of connection ids to their configurations. // These connections can be saved on the session (runtime), // on the workspace, or globally in vscode. - _connections: { [key: string]: SavedConnectionInfo } = {}; + _connections: { [key: string]: StoreConnectionInfo } = {}; _activeDataService: DataService| null = null; _activeConnectionModel: ConnectionModel | null = null; @@ -97,38 +107,78 @@ export default class ConnectionController { this._telemetryService = telemetryService; } - private _migratePreviouslySavedConnection () {} + private async _migratePreviouslySavedConnection( + savedConnectionInfo: StoreConnectionInfo + ): Promise { + // Transform a raw connection model from storage to an ampersand model. + const newConnectionModelWithSecrets = new ConnectionModel(savedConnectionInfo.connectionModel); + // Convert the connection model to a connection info object. + const newConnectionInfoWithSecrets = convertConnectionModelToInfo(newConnectionModelWithSecrets); + + // Use connectionOptions instead of connectionModel. + const newSavedConnectionInfoWithSecrets = { + id: savedConnectionInfo.id, + name: savedConnectionInfo.name, + storageLocation: savedConnectionInfo.storageLocation, + connectionOptions: newConnectionInfoWithSecrets.connectionOptions + }; + + await this._saveConnection(newSavedConnectionInfoWithSecrets); + + return newSavedConnectionInfoWithSecrets; + } - private async _getConnectionOptionsWithSecrets(connectionId: string): Promise { + private async _getConnectionInfoWithSecrets( + savedConnectionInfo: StoreConnectionInfo + ): Promise { + // Migrate previously saved connections to a new format. + // Save only secrets to keychain. + // Remove connectionModel and use connectionOptions instead. + if (savedConnectionInfo.connectionModel) { + try { + return await this._migratePreviouslySavedConnection( + savedConnectionInfo + ); + } catch (error) { + // Here we're leniant when loading connections in case their + // connections have become corrupted. + const printableError = error as { message: string }; + log.error(`Connection migration failed: ${printableError.message}`); + return; + } + } + + // If connection has a new format already and keytar module is undefined. + // Return saved connection as it is. if (!ext.keytarModule) { log.error('VSCode extension keytar module is undefined.'); - return; + return savedConnectionInfo; } try { const unparsedSecrets = await ext.keytarModule.getPassword( this._serviceName, - connectionId + savedConnectionInfo.id ); + // Ignore empty secrets. if (!unparsedSecrets) { - // Ignore empty secrets. - return; + return savedConnectionInfo; } const secrets = JSON.parse(unparsedSecrets); - - if (secrets.connectionModel) { - this._migratePreviouslySavedConnection(); - } - - const connectionOptions = this._connections[connectionId].connectionOptions; + const connectionOptions = savedConnectionInfo.connectionOptions; const connectionInfoWithSecrets = mergeSecrets({ connectionOptions }, secrets); - return connectionInfoWithSecrets.connectionOptions; + return { + ...savedConnectionInfo, + connectionOptions: connectionInfoWithSecrets.connectionOptions + }; } catch (error) { // Here we're leniant when loading connections in case their // connections have become corrupted. + const printableError = error as { message: string }; + log.error(`Mergin connection with secrets failed: ${printableError.message}`); return; } } @@ -136,20 +186,24 @@ export default class ConnectionController { private async _loadSavedConnectionsByStore( savedConnections: ConnectionsFromStorage ): Promise { - if (Object.keys(savedConnections).length < 1) { + if (!Object.keys(savedConnections).length) { return; } + /** Users connections are being saved both in: + * 1. Vscode global/workspace storage (without secrets) + keychain (secrets) + * 2. Memory of the extension (with secrets) + */ await Promise.all( Object.keys(savedConnections).map(async (connectionId) => { - this._connections[connectionId] = { ...savedConnections[connectionId] }; - - const connectionOptionsWithSecrets = await this._getConnectionOptionsWithSecrets( - connectionId + // Get connection info from vscode storage and merge with secrets. + const connectionInfoWithSecrets = await this._getConnectionInfoWithSecrets( + savedConnections[connectionId] ); - if (connectionOptionsWithSecrets) { - this._connections[connectionId].connectionOptions = connectionOptionsWithSecrets; + // Save connection info with secrets to extension memory. + if (connectionInfoWithSecrets) { + this._connections[connectionId] = connectionInfoWithSecrets; } this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); @@ -159,18 +213,14 @@ export default class ConnectionController { async loadSavedConnections(): Promise { // Try to pull in the connections previously saved in the global storage of vscode. - const existingGlobalConnections = - this._storageController.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS) || - {}; + const existingGlobalConnections = this._storageController.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS); await this._loadSavedConnectionsByStore(existingGlobalConnections); // Try to pull in the connections previously saved in the workspace storage of vscode. - const existingWorkspaceConnections = - this._storageController.get( - StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || - {}; + const existingWorkspaceConnections = this._storageController.get( + StorageVariables.WORKSPACE_SAVED_CONNECTIONS, + StorageLocation.WORKSPACE + ); await this._loadSavedConnectionsByStore(existingWorkspaceConnections); } @@ -252,43 +302,62 @@ export default class ConnectionController { } private async _saveSecretsToKeychain( - { connectionId, secrets }: { connectionId: string, secrets: ConnectionSecrets } + { connectionId, secrets }: ConnectionSecretsInfo ): Promise { - if (ext.keytarModule) { - const secretsAsString = JSON.stringify(secrets); - - await ext.keytarModule.setPassword( - this._serviceName, - connectionId, - secretsAsString - ); + if (!ext.keytarModule || !Object.keys(secrets).length) { + return; } + + const secretsAsString = JSON.stringify(secrets); + + await ext.keytarModule.setPassword( + this._serviceName, + connectionId, + secretsAsString + ); + } + + private async _saveConnection( + newStoreConnectionInfoWithSecrets: StoreConnectionInfo + ): Promise { + // We don't want to store secrets to disc. + const { connectionInfo: safeConnectionInfo, secrets } = extractSecrets( + newStoreConnectionInfoWithSecrets + ); + const savedConnectionInfo = await this._storageController.saveConnection({ + ...newStoreConnectionInfoWithSecrets, + connectionOptions: safeConnectionInfo.connectionOptions // The connection info without secrtes. + }); + + await this._saveSecretsToKeychain({ + connectionId: savedConnectionInfo.id, + secrets // Only secrets. + }); + + return savedConnectionInfo; } async saveNewConnectionAndConnect( originalConnectionInfo: ConnectionInfo, connectionType: ConnectionTypes ): Promise { - // We don't want to store secrets to disc. - const { connectionInfo: safeConnectionInfo, secrets } = extractSecrets( - originalConnectionInfo - ); - const savedConnectionInfo = await this._storageController.storeNewConnection( - safeConnectionInfo // The connection info without secrtes. - ); + const name = getConnectionTitle(originalConnectionInfo); + const newConnectionInfo = { + id: uuidv4(), + name, + // To begin we just store it on the session, the storage controller + // handles changing this based on user preference. + storageLocation: StorageLocation.NONE, + connectionOptions: originalConnectionInfo.connectionOptions + }; + + const savedConnectionInfo = await this._saveConnection(newConnectionInfo); - // We want to keep secrets in memory tho. this._connections[savedConnectionInfo.id] = { ...savedConnectionInfo, connectionOptions: originalConnectionInfo.connectionOptions // The connection options with secrtes. }; - // We also want to store secrets to keychain. - await this._saveSecretsToKeychain({ - connectionId: savedConnectionInfo.id, - secrets - }); - log.info(`Connect called to connect to instance: ${savedConnectionInfo.name}`); return this._connect(savedConnectionInfo.id, connectionType); } @@ -574,24 +643,7 @@ export default class ConnectionController { this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); this.eventEmitter.emit(DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED); - if ( - this._connections[connectionId].storageLocation === StorageScope.GLOBAL - ) { - await this._storageController.saveConnectionToGlobalStore( - this._connections[connectionId] - ); - return true; - } - - if ( - this._connections[connectionId].storageLocation === StorageScope.WORKSPACE - ) { - await this._storageController.saveConnectionToWorkspaceStore( - this._connections[connectionId] - ); - - return true; - } + await this._storageController.saveConnection(this._connections[connectionId]); // No storing needed. return true; @@ -623,7 +675,7 @@ export default class ConnectionController { return this._activeDataService !== null; } - getSavedConnections(): SavedConnectionInfo[] { + getSavedConnections(): StoreConnectionInfo[] { return Object.values(this._connections); } @@ -756,10 +808,10 @@ export default class ConnectionController { } }, ...Object.values(this._connections) - .sort((connectionA: SavedConnectionInfo, connectionB: SavedConnectionInfo) => + .sort((connectionA: StoreConnectionInfo, connectionB: StoreConnectionInfo) => (connectionA.name || '').localeCompare(connectionB.name || '') ) - .map((item: SavedConnectionInfo) => ({ + .map((item: StoreConnectionInfo) => ({ label: item.name, data: { type: NewConnectionType.SAVED_CONNECTION, diff --git a/src/explorer/explorerTreeController.ts b/src/explorer/explorerTreeController.ts index 9ae9ef2d1..f1fc5bfa0 100644 --- a/src/explorer/explorerTreeController.ts +++ b/src/explorer/explorerTreeController.ts @@ -8,7 +8,7 @@ import { createLogger } from '../logging'; import { DOCUMENT_ITEM } from './documentTreeItem'; import { DOCUMENT_LIST_ITEM, CollectionTypes } from './documentListTreeItem'; import EXTENSION_COMMANDS from '../commands'; -import { SavedConnectionInfo } from '../connectionController'; +import { StoreConnectionInfo } from '../connectionController'; import { sortTreeItemsByLabel } from './treeItemUtils'; const log = createLogger('explorer controller'); @@ -146,7 +146,7 @@ implements vscode.TreeDataProvider { this._connectionTreeItems = {}; // Create new connection tree items, using cached children wherever possible. - connections.forEach((connection: SavedConnectionInfo) => { + connections.forEach((connection: StoreConnectionInfo) => { const isActiveConnection = connection.id === this._connectionController.getActiveConnectionId(); const isBeingConnectedTo = diff --git a/src/storage/storageController.ts b/src/storage/storageController.ts index 7ea2c5ed9..a542e9f82 100644 --- a/src/storage/storageController.ts +++ b/src/storage/storageController.ts @@ -1,8 +1,7 @@ import * as vscode from 'vscode'; import { v4 as uuidv4 } from 'uuid'; -import { ConnectionInfo, getConnectionTitle } from 'mongodb-data-service'; -import { SavedConnectionInfo } from '../connectionController'; +import { StoreConnectionInfo } from '../connectionController'; export enum StorageVariables { // Only exists on globalState. @@ -14,7 +13,7 @@ export enum StorageVariables { } // Typically variables default to 'GLOBAL' scope. -export enum StorageScope { +export enum StorageLocation { GLOBAL = 'GLOBAL', WORKSPACE = 'WORKSPACE', NONE = 'NONE' @@ -28,7 +27,7 @@ export enum DefaultSavingLocations { } export type ConnectionsFromStorage = { - [key: string]: SavedConnectionInfo + [key: string]: StoreConnectionInfo }; type StoredVariableName = StorageVariables.GLOBAL_USER_ID | @@ -44,26 +43,26 @@ type StoredItem = never; export default class StorageController { - _storage: { [StorageScope.GLOBAL]: vscode.Memento, [StorageScope.WORKSPACE]: vscode.Memento }; + _storage: { [StorageLocation.GLOBAL]: vscode.Memento, [StorageLocation.WORKSPACE]: vscode.Memento }; constructor(context: vscode.ExtensionContext) { this._storage = { - [StorageScope.GLOBAL]: context.globalState, - [StorageScope.WORKSPACE]: context.workspaceState + [StorageLocation.GLOBAL]: context.globalState, + [StorageLocation.WORKSPACE]: context.workspaceState }; } - get(variableName: T, storageScope: StorageScope = StorageScope.GLOBAL): StoredItem { - return this._storage[storageScope].get(variableName); + get(variableName: T, storageLocation: StorageLocation = StorageLocation.GLOBAL): StoredItem { + return this._storage[storageLocation].get(variableName); } // Update something in the storage. Defaults to global storage (not workspace). update( variableName: StorageVariables, value: boolean | string | ConnectionsFromStorage, - storageScope: StorageScope = StorageScope.GLOBAL + storageLocation: StorageLocation = StorageLocation.GLOBAL ): Thenable { - this._storage[storageScope].update(variableName, value); + this._storage[storageLocation].update(variableName, value); return Promise.resolve(); } @@ -80,14 +79,12 @@ export default class StorageController { return globalUserId; } - async saveConnectionToGlobalStore( - savedConnectionInfo: SavedConnectionInfo - ): Promise { + async saveConnectionToGlobalStore(storeConnectionInfo: StoreConnectionInfo): Promise { // Get the current save connections. - const globalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS) || {}; + const globalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS); // Add the new connection. - globalConnections[savedConnectionInfo.id] = savedConnectionInfo; + globalConnections[storeConnectionInfo.id] = storeConnectionInfo; // Update the store. return this.update( @@ -96,40 +93,44 @@ export default class StorageController { ); } - async saveConnectionToWorkspaceStore( - savedConnectionInfo: SavedConnectionInfo - ): Promise { + async saveConnectionToWorkspaceStore(storeConnectionInfo: StoreConnectionInfo): Promise { // Get the current save connections. const workspaceConnections = this.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || {}; + StorageLocation.WORKSPACE + ); // Add the new connection. - workspaceConnections[savedConnectionInfo.id] = savedConnectionInfo; + workspaceConnections[storeConnectionInfo.id] = storeConnectionInfo; // Update the store. return this.update( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, workspaceConnections, - StorageScope.WORKSPACE + StorageLocation.WORKSPACE ); } - getPreferedStorageLocationFromConfiguration(): StorageScope { - const defaultConnectionSavingLocation = vscode.workspace + async saveConnection(storeConnectionInfo: StoreConnectionInfo): Promise { + const dontShowSaveLocationPrompt = vscode.workspace .getConfiguration('mdb.connectionSaving') - .get('defaultConnectionSavingLocation'); + .get('hideOptionToChooseWhereToSaveNewConnections'); - if (defaultConnectionSavingLocation === DefaultSavingLocations.Workspace) { - return StorageScope.WORKSPACE; + if (dontShowSaveLocationPrompt === true) { + // The user has chosen not to show the message on where to save the connection. + // Save the connection in their default preference. + storeConnectionInfo.storageLocation = this.getPreferedStorageLocationFromConfiguration(); + } else { + storeConnectionInfo.storageLocation = await this.getStorageLocationFromPrompt(); } - if (defaultConnectionSavingLocation === DefaultSavingLocations.Global) { - return StorageScope.GLOBAL; + if (storeConnectionInfo.storageLocation === StorageLocation.WORKSPACE) { + await this.saveConnectionToWorkspaceStore(storeConnectionInfo); + } else if (storeConnectionInfo.storageLocation === StorageLocation.GLOBAL) { + await this.saveConnectionToGlobalStore(storeConnectionInfo); } - return StorageScope.NONE; + return storeConnectionInfo; } async getStorageLocationFromPrompt() { @@ -149,46 +150,14 @@ export default class StorageController { ); if (chosenConnectionSavingLocation === DefaultSavingLocations.Workspace) { - return StorageScope.WORKSPACE; + return StorageLocation.WORKSPACE; } if (chosenConnectionSavingLocation === DefaultSavingLocations.Global) { - return StorageScope.GLOBAL; + return StorageLocation.GLOBAL; } - return StorageScope.NONE; - } - - async storeNewConnection(safeConnectionInfo: ConnectionInfo): Promise { - const name = getConnectionTitle(safeConnectionInfo); - const savedConnectionInfo = { - id: safeConnectionInfo.id || uuidv4(), - name, - // To begin we just store it on the session, the storage controller - // handles changing this based on user preference. - storageLocation: StorageScope.NONE, - connectionOptions: safeConnectionInfo.connectionOptions - }; - - const dontShowSaveLocationPrompt = vscode.workspace - .getConfiguration('mdb.connectionSaving') - .get('hideOptionToChooseWhereToSaveNewConnections'); - - if (dontShowSaveLocationPrompt === true) { - // The user has chosen not to show the message on where to save the connection. - // Save the connection in their default preference. - savedConnectionInfo.storageLocation = this.getPreferedStorageLocationFromConfiguration(); - } else { - savedConnectionInfo.storageLocation = await this.getStorageLocationFromPrompt(); - } - - if (savedConnectionInfo.storageLocation === StorageScope.WORKSPACE) { - await this.saveConnectionToWorkspaceStore(savedConnectionInfo); - } else if (savedConnectionInfo.storageLocation === StorageScope.GLOBAL) { - await this.saveConnectionToGlobalStore(savedConnectionInfo); - } - - return savedConnectionInfo; + return StorageLocation.NONE; } removeConnection(connectionId: string): void { @@ -207,7 +176,7 @@ export default class StorageController { const workspaceStoredConnections = this.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE + StorageLocation.WORKSPACE ); if ( workspaceStoredConnections && @@ -217,18 +186,34 @@ export default class StorageController { void this.update( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, workspaceStoredConnections, - StorageScope.WORKSPACE + StorageLocation.WORKSPACE ); } } hasSavedConnections(): boolean { - const savedWorkspaceConnections = this.get(StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageScope.WORKSPACE); - const savedGlobalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS, StorageScope.GLOBAL); + const savedWorkspaceConnections = this.get(StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageLocation.WORKSPACE); + const savedGlobalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS, StorageLocation.GLOBAL); return ( (savedWorkspaceConnections && Object.keys(savedWorkspaceConnections).length > 0) || (savedGlobalConnections && Object.keys(savedGlobalConnections).length > 0) ); } + + getPreferedStorageLocationFromConfiguration(): StorageLocation { + const defaultConnectionSavingLocation = vscode.workspace + .getConfiguration('mdb.connectionSaving') + .get('defaultConnectionSavingLocation'); + + if (defaultConnectionSavingLocation === DefaultSavingLocations.Workspace) { + return StorageLocation.WORKSPACE; + } + + if (defaultConnectionSavingLocation === DefaultSavingLocations.Global) { + return StorageLocation.GLOBAL; + } + + return StorageLocation.NONE; + } } diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index c4b1580f6..a542f3360 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -10,7 +10,7 @@ import ConnectionController, { } from '../../connectionController'; import { StorageController, StorageVariables } from '../../storage'; import { - StorageScope, + StorageLocation, DefaultSavingLocations } from '../../storage/storageController'; import { StatusView } from '../../views'; @@ -335,7 +335,7 @@ suite('Connection Controller Test Suite', function () { const globalStoreConnections = mockStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS - ) || {}; + ); assert( Object.keys(globalStoreConnections).length === 1, @@ -375,8 +375,8 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || {}; + StorageLocation.WORKSPACE + ); assert( Object.keys(workspaceStoreConnections).length === 1, @@ -408,7 +408,7 @@ suite('Connection Controller Test Suite', function () { id: '25', name: 'tester', connectionOptions: { connectionString: TEST_DATABASE_URI }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE } }; @@ -434,8 +434,8 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || {}; + StorageLocation.WORKSPACE + ); assert( Object.keys(workspaceStoreConnections).length === 1, @@ -531,7 +531,7 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE + StorageLocation.WORKSPACE ); assert( @@ -556,8 +556,8 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || {}; + StorageLocation.WORKSPACE + ); assert( Object.keys(workspaceStoreConnections).length === 1, @@ -574,8 +574,8 @@ suite('Connection Controller Test Suite', function () { const postWorkspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || {}; + StorageLocation.WORKSPACE + ); assert( Object.keys(postWorkspaceStoreConnections).length === 0, @@ -596,7 +596,7 @@ suite('Connection Controller Test Suite', function () { const globalStoreConnections = mockStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS - ) || {}; + ); assert( Object.keys(globalStoreConnections).length === 1, @@ -611,7 +611,7 @@ suite('Connection Controller Test Suite', function () { const postGlobalStoreConnections = mockStorageController.get( StorageVariables.GLOBAL_SAVED_CONNECTIONS - ) || {}; + ); assert( Object.keys(postGlobalStoreConnections).length === 0, @@ -635,8 +635,8 @@ suite('Connection Controller Test Suite', function () { const workspaceStoreConnections = mockStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ) || {}; + StorageLocation.WORKSPACE + ); assert( Object.keys(workspaceStoreConnections).length === 1, @@ -818,7 +818,7 @@ suite('Connection Controller Test Suite', function () { id, name: `test${i}`, connectionOptions: { connectionString: TEST_DATABASE_URI }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE }; } @@ -864,7 +864,7 @@ suite('Connection Controller Test Suite', function () { id: connectionId, name: 'asdfasdg', connectionOptions: { connectionString: testDatabaseURI2WithTimeout }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE }; void testConnectionController.connectWithConnectionId(connectionId); @@ -887,7 +887,7 @@ suite('Connection Controller Test Suite', function () { id: connectionId, name: 'asdfasdg', connectionOptions: { connectionString: TEST_DATABASE_URI }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE }; sinon.replace( diff --git a/src/test/suite/editors/collectionDocumentsProvider.test.ts b/src/test/suite/editors/collectionDocumentsProvider.test.ts index d7244d539..70bfcca01 100644 --- a/src/test/suite/editors/collectionDocumentsProvider.test.ts +++ b/src/test/suite/editors/collectionDocumentsProvider.test.ts @@ -11,7 +11,7 @@ import ConnectionController from '../../../connectionController'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; import { StatusView } from '../../../views'; import { StorageController } from '../../../storage'; -import { StorageScope } from '../../../storage/storageController'; +import { StorageLocation } from '../../../storage/storageController'; import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { TestExtensionContext, mockTextEditor } from '../stubs'; @@ -466,13 +466,13 @@ suite('Collection Documents Provider Test Suite', () => { id: firstConnectionId, name: 'localhost', connectionOptions: { connectionString: TEST_DATABASE_URI }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE }, [secondConnectionId]: { id: secondConnectionId, name: 'compass', connectionOptions: { connectionString: TEST_DATABASE_URI }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE } }; diff --git a/src/test/suite/explorer/explorerController.test.ts b/src/test/suite/explorer/explorerController.test.ts index 34ec4ee4c..8cc3167e0 100644 --- a/src/test/suite/explorer/explorerController.test.ts +++ b/src/test/suite/explorer/explorerController.test.ts @@ -5,7 +5,7 @@ import sinon from 'sinon'; import { DefaultSavingLocations, - StorageScope + StorageLocation } from '../../../storage/storageController'; import { mdbTestExtension } from '../stubbableMdbExtension'; import { TEST_DATABASE_URI } from '../dbTestHelper'; @@ -58,7 +58,7 @@ suite('Explorer Controller Test Suite', function () { id: 'testConnectionId', connectionOptions: { connectionString: 'mongodb://localhost' }, name: 'testConnectionName', - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE } }; testConnectionController.setConnnecting(true); @@ -194,14 +194,14 @@ suite('Explorer Controller Test Suite', function () { connectionOptions: testConnectionController._connections[connectionId].connectionOptions, name: 'aaa', id: 'aaa', - storageLocation: StorageScope.WORKSPACE + storageLocation: StorageLocation.WORKSPACE }; testConnectionController._connections.zzz = { connectionOptions: testConnectionController._connections[connectionId].connectionOptions, name: 'zzz', id: 'zzz', - storageLocation: StorageScope.WORKSPACE + storageLocation: StorageLocation.WORKSPACE }; const connectionsItems = await treeController.getChildren(); diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 8604ee2d5..29731e155 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -20,7 +20,7 @@ import IndexListTreeItem from '../../explorer/indexListTreeItem'; import { mdbTestExtension } from './stubbableMdbExtension'; import { mockTextEditor } from './stubs'; import { - StorageScope, + StorageLocation, StorageVariables } from '../../storage/storageController'; import { VIEW_COLLECTION_SCHEME } from '../../editors/collectionDocumentsProvider'; @@ -1179,7 +1179,7 @@ suite('MDBExtensionController Test Suite', function () { id: 'blueBerryPancakesAndTheSmellOfBacon', connectionOptions: { connectionString: 'mongodb://localhost' }, name: 'NAAAME', - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE }; const mockTreeItem = new ConnectionTreeItem( @@ -1217,7 +1217,7 @@ suite('MDBExtensionController Test Suite', function () { id: 'blueBerryPancakesAndTheSmellOfBacon', name: 'NAAAME', connectionOptions: { connectionString: 'mongodb://localhost' }, - storageLocation: StorageScope.NONE + storageLocation: StorageLocation.NONE }; const mockTreeItem = new ConnectionTreeItem( diff --git a/src/test/suite/storage/storageController.test.ts b/src/test/suite/storage/storageController.test.ts index 1cd06255a..a82f97d0b 100644 --- a/src/test/suite/storage/storageController.test.ts +++ b/src/test/suite/storage/storageController.test.ts @@ -1,10 +1,8 @@ -import * as vscode from 'vscode'; import assert from 'assert'; import StorageController, { StorageVariables, - StorageScope, - DefaultSavingLocations + StorageLocation } from '../../../storage/storageController'; import { TestExtensionContext } from '../stubs'; @@ -36,7 +34,7 @@ suite('Storage Controller Test Suite', () => { const testStorageController = new StorageController(testExtensionContext); const testVal = testStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE + StorageLocation.WORKSPACE ); assert( testVal.collTwo.name === 'i_cant_believe_its_gonna_save_this', @@ -44,93 +42,6 @@ suite('Storage Controller Test Suite', () => { ); }); - test('storeNewConnection adds the connection to preexisting connections on the global store', async () => { - await vscode.workspace - .getConfiguration('mdb.connectionSaving') - .update('defaultConnectionSavingLocation', DefaultSavingLocations.Global); - - const testExtensionContext = new TestExtensionContext(); - testExtensionContext._globalState = { - [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: { - conn1: { - id: 'conn1', - name: 'saved1', - storageLocation: StorageScope.GLOBAL, - connectionOptions: { connectionString: 'mongodb://localhost' } - } - } - }; - const testStorageController = new StorageController(testExtensionContext); - void testStorageController.storeNewConnection( - { - id: 'conn2', - connectionOptions: { connectionString: 'mongodb://localhost' } - } - ); - - const updatedGlobalModels = testStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS - ) || {}; - assert( - Object.keys(updatedGlobalModels).length === 2, - `Expected 2 connections, found ${Object.keys(updatedGlobalModels).length - }.` - ); - assert( - updatedGlobalModels.conn1.name === 'saved1', - 'Expected connection data to persist.' - ); - assert( - updatedGlobalModels.conn2.storageLocation === StorageScope.GLOBAL, - 'Expected storage scope to be set.' - ); - assert(testStorageController.hasSavedConnections()); - }); - - test('storeNewConnection adds the connection to preexisting connections on the workspace store', async () => { - await vscode.workspace - .getConfiguration('mdb.connectionSaving') - .update('defaultConnectionSavingLocation', DefaultSavingLocations.Workspace); - - const testExtensionContext = new TestExtensionContext(); - testExtensionContext._workspaceState = { - [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: { - conn1: { - id: 'conn1', - name: 'saved1', - storageLocation: StorageScope.WORKSPACE, - connectionOptions: { connectionString: 'mongodb://localhost' } - } - } - }; - const testStorageController = new StorageController(testExtensionContext); - void testStorageController.storeNewConnection( - { - id: 'conn2', - connectionOptions: { connectionString: 'mongodb://localhost' } - } - ); - - const updatedWorkspaceModels = testStorageController.get( - StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE - ); - assert( - Object.keys(updatedWorkspaceModels).length === 2, - `Expected 2 connections, found ${Object.keys(updatedWorkspaceModels).length - }.` - ); - assert( - updatedWorkspaceModels.conn1.name === 'saved1', - 'Expected connection data to persist.' - ); - assert( - updatedWorkspaceModels.conn2.storageLocation === StorageScope.WORKSPACE, - 'Expected storage scope to be set.' - ); - assert(testStorageController.hasSavedConnections()); - }); - test('addNewConnectionToGlobalStore adds the connection to preexisting connections on the global store', () => { const testExtensionContext = new TestExtensionContext(); testExtensionContext._globalState = { @@ -138,7 +49,7 @@ suite('Storage Controller Test Suite', () => { conn1: { id: 'conn1', name: 'saved1', - storageLocation: StorageScope.GLOBAL, + storageLocation: StorageLocation.GLOBAL, connectionOptions: { connectionString: 'mongodb://localhost' } } } @@ -147,7 +58,7 @@ suite('Storage Controller Test Suite', () => { void testStorageController.saveConnectionToGlobalStore({ id: 'conn2', name: 'saved2', - storageLocation: StorageScope.GLOBAL, + storageLocation: StorageLocation.GLOBAL, connectionOptions: { connectionString: 'mongodb://localhost' } }); @@ -164,7 +75,7 @@ suite('Storage Controller Test Suite', () => { 'Expected connection data to persist.' ); assert( - updatedGlobalModels.conn2.storageLocation === StorageScope.GLOBAL, + updatedGlobalModels.conn2.storageLocation === StorageLocation.GLOBAL, 'Expected storage scope to be set.' ); assert(testStorageController.hasSavedConnections()); @@ -177,7 +88,7 @@ suite('Storage Controller Test Suite', () => { conn1: { id: 'conn1', name: 'saved1', - storageLocation: StorageScope.WORKSPACE, + storageLocation: StorageLocation.WORKSPACE, connectionOptions: { connectionString: 'mongodb://localhost' } } } @@ -186,13 +97,13 @@ suite('Storage Controller Test Suite', () => { void testStorageController.saveConnectionToWorkspaceStore({ id: 'conn2', name: 'saved2', - storageLocation: StorageScope.WORKSPACE, + storageLocation: StorageLocation.WORKSPACE, connectionOptions: { connectionString: 'mongodb://localhost:27018' } }); const updatedWorkspaceModels = testStorageController.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageScope.WORKSPACE + StorageLocation.WORKSPACE ); assert( Object.keys(updatedWorkspaceModels).length === 2, @@ -209,7 +120,7 @@ suite('Storage Controller Test Suite', () => { ); assert( updatedWorkspaceModels.conn2.storageLocation === - StorageScope.WORKSPACE, + StorageLocation.WORKSPACE, 'Expected storage scope to be set.' ); }); diff --git a/src/utils/connectionSecrets.ts b/src/utils/connectionSecrets.ts index 3874241b0..620cbcbea 100644 --- a/src/utils/connectionSecrets.ts +++ b/src/utils/connectionSecrets.ts @@ -1,9 +1,10 @@ -import { cloneDeep } from 'lodash'; import ConnectionString, { CommaAndColonSeparatedRecord, } from 'mongodb-connection-string-url'; import { ConnectionInfo } from 'mongodb-data-service'; +const cloneDeep = require('lodash.clonedeep'); + export interface ConnectionSecrets { password?: string; sshTunnelPassphrase?: string; From 0165fd4f3cd3c1ef7c06458ad41340c1231322ae Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Thu, 16 Dec 2021 16:41:54 +0100 Subject: [PATCH 03/15] fix: set default objects for storages --- package-lock.json | 36 +++++++++---------- src/connectionController.ts | 7 ++-- src/storage/storageController.ts | 21 ++++++++--- src/test/suite/connectionController.test.ts | 15 +++++--- .../suite/storage/storageController.test.ts | 6 ++-- 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a0b0061a..94e0ec7d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14305,9 +14305,9 @@ } }, "node_modules/localforage": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz", - "integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", "dependencies": { "lie": "3.1.1" } @@ -16332,9 +16332,9 @@ } }, "node_modules/mongodb-index-model/node_modules/async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" }, "node_modules/mongodb-js-errors": { "version": "0.5.0", @@ -21091,9 +21091,9 @@ } }, "node_modules/storage-mixin/node_modules/async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" }, "node_modules/storage-mixin/node_modules/debug": { "version": "4.3.0", @@ -36112,9 +36112,9 @@ } }, "localforage": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz", - "integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", "requires": { "lie": "3.1.1" } @@ -37827,9 +37827,9 @@ }, "dependencies": { "async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" } } }, @@ -41697,9 +41697,9 @@ }, "dependencies": { "async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" }, "debug": { "version": "4.3.0", diff --git a/src/connectionController.ts b/src/connectionController.ts index 4205f1216..91bd2536b 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -186,7 +186,7 @@ export default class ConnectionController { private async _loadSavedConnectionsByStore( savedConnections: ConnectionsFromStorage ): Promise { - if (!Object.keys(savedConnections).length) { + if (!savedConnections || !Object.keys(savedConnections).length) { return; } @@ -213,7 +213,10 @@ export default class ConnectionController { async loadSavedConnections(): Promise { // Try to pull in the connections previously saved in the global storage of vscode. - const existingGlobalConnections = this._storageController.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS); + const existingGlobalConnections = this._storageController.get( + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL + ); await this._loadSavedConnectionsByStore(existingGlobalConnections); // Try to pull in the connections previously saved in the workspace storage of vscode. diff --git a/src/storage/storageController.ts b/src/storage/storageController.ts index a542e9f82..037ebbdbc 100644 --- a/src/storage/storageController.ts +++ b/src/storage/storageController.ts @@ -81,7 +81,14 @@ export default class StorageController { async saveConnectionToGlobalStore(storeConnectionInfo: StoreConnectionInfo): Promise { // Get the current save connections. - const globalConnections = this.get(StorageVariables.GLOBAL_SAVED_CONNECTIONS); + let globalConnections = this.get( + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL + ); + + if (!globalConnections) { + globalConnections = {}; + } // Add the new connection. globalConnections[storeConnectionInfo.id] = storeConnectionInfo; @@ -95,11 +102,15 @@ export default class StorageController { async saveConnectionToWorkspaceStore(storeConnectionInfo: StoreConnectionInfo): Promise { // Get the current save connections. - const workspaceConnections = this.get( + let workspaceConnections = this.get( StorageVariables.WORKSPACE_SAVED_CONNECTIONS, StorageLocation.WORKSPACE ); + if (!workspaceConnections) { + workspaceConnections = {}; + } + // Add the new connection. workspaceConnections[storeConnectionInfo.id] = storeConnectionInfo; @@ -164,13 +175,15 @@ export default class StorageController { // See if the connection exists in the saved global or workspace connections // and remove it if it is. const globalStoredConnections = this.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); if (globalStoredConnections && globalStoredConnections[connectionId]) { delete globalStoredConnections[connectionId]; void this.update( StorageVariables.GLOBAL_SAVED_CONNECTIONS, - globalStoredConnections + globalStoredConnections, + StorageLocation.GLOBAL ); } diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index a542f3360..0b068d96c 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -334,7 +334,8 @@ suite('Connection Controller Test Suite', function () { ); const globalStoreConnections = mockStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( @@ -393,7 +394,8 @@ suite('Connection Controller Test Suite', function () { ); const globalStoreConnections = mockStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( @@ -521,7 +523,8 @@ suite('Connection Controller Test Suite', function () { const objectString = JSON.stringify(undefined); const globalStoreConnections = mockStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( @@ -595,7 +598,8 @@ suite('Connection Controller Test Suite', function () { ); const globalStoreConnections = mockStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( @@ -610,7 +614,8 @@ suite('Connection Controller Test Suite', function () { await testConnectionController.removeSavedConnection(connectionId); const postGlobalStoreConnections = mockStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( diff --git a/src/test/suite/storage/storageController.test.ts b/src/test/suite/storage/storageController.test.ts index a82f97d0b..9d3f09f8f 100644 --- a/src/test/suite/storage/storageController.test.ts +++ b/src/test/suite/storage/storageController.test.ts @@ -16,7 +16,8 @@ suite('Storage Controller Test Suite', () => { }; const testStorageController = new StorageController(testExtensionContext); const testVal = testStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( testVal.collOne.name === 'this_gonna_get_saved', @@ -63,7 +64,8 @@ suite('Storage Controller Test Suite', () => { }); const updatedGlobalModels = testStorageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL ); assert( Object.keys(updatedGlobalModels).length === 2, From e54503af0f420ca146d41dd0c35fad24220963e9 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Thu, 16 Dec 2021 20:49:28 +0100 Subject: [PATCH 04/15] test: mock connection model --- src/commands/launchMongoShell.ts | 21 +- src/connectionController.ts | 8 +- src/editors/playgroundController.ts | 4 +- src/explorer/collectionTreeItem.ts | 2 +- src/explorer/databaseTreeItem.ts | 3 +- src/language/mongoDBService.ts | 9 +- .../suite/commands/launchMongoShell.test.ts | 254 ++++++++++++------ src/test/suite/connectionController.test.ts | 5 - .../suite/editors/codeActionProvider.test.ts | 2 +- .../editors/playgroundController.test.ts | 26 +- .../telemetry/connectionTelemetry.test.ts | 1 + src/types/collectionItemType.ts | 7 - 12 files changed, 212 insertions(+), 130 deletions(-) delete mode 100644 src/types/collectionItemType.ts diff --git a/src/commands/launchMongoShell.ts b/src/commands/launchMongoShell.ts index 6c63fd521..2af88fe90 100644 --- a/src/commands/launchMongoShell.ts +++ b/src/commands/launchMongoShell.ts @@ -1,14 +1,13 @@ import * as vscode from 'vscode'; +import ConnectionModel from 'mongodb-connection-model'; import ConnectionController from '../connectionController'; -const isSslConnection = (activeConnectionModel: any): boolean => { +const isSslConnection = (activeDerivedConnectionModel: ConnectionModel): boolean => { return !!( - activeConnectionModel && - activeConnectionModel.driverOptions && - (activeConnectionModel.driverOptions.sslCA || - activeConnectionModel.driverOptions.sslCert || - activeConnectionModel.driverOptions.sslPass) + activeDerivedConnectionModel?.driverOptions?.sslCA || + activeDerivedConnectionModel?.driverOptions?.sslCert || + activeDerivedConnectionModel?.driverOptions?.sslPass ); }; @@ -120,13 +119,11 @@ const openMongoDBShell = (connectionController: ConnectionController): Promise { diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 829b75043..03b43d457 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -574,13 +574,13 @@ export default class PlaygroundController { selection }); - const connectionModel = this._connectionController.getActiveDerivedConnectionModel(); + const mongoClientOptions = this._connectionController.getMongoClientConnectionOptions(); const toCompile = { aggregation: selectedText, options: { collection: namespace.collectionName, database: namespace.databaseName, - uri: connectionModel?.driverUrlWithSsh + uri: mongoClientOptions?.url } }; diff --git a/src/explorer/collectionTreeItem.ts b/src/explorer/collectionTreeItem.ts index 0d8f35aa5..7866a8b4a 100644 --- a/src/explorer/collectionTreeItem.ts +++ b/src/explorer/collectionTreeItem.ts @@ -88,7 +88,7 @@ export default class CollectionTreeItem extends vscode.TreeItem cachedDocumentCount: number | null, existingDocumentListChild?: DocumentListTreeItem, existingSchemaChild?: SchemaTreeItem, - existingIndexListChild?: IndexListTreeItem // Existing cache. + existingIndexListChild?: IndexListTreeItem ) { super( collection.name, diff --git a/src/explorer/databaseTreeItem.ts b/src/explorer/databaseTreeItem.ts index d39dccff0..2e3df8423 100644 --- a/src/explorer/databaseTreeItem.ts +++ b/src/explorer/databaseTreeItem.ts @@ -1,3 +1,4 @@ +import * as util from 'util'; import * as vscode from 'vscode'; import path from 'path'; @@ -15,8 +16,6 @@ function getIconPath(): { light: string; dark: string } { }; } -import * as util from 'util'; - export default class DatabaseTreeItem extends vscode.TreeItem implements TreeItemParent, vscode.TreeDataProvider { contextValue = 'databaseTreeItem'; diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 2010b0097..2895a51e4 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -14,7 +14,6 @@ import { signatures } from '@mongosh/shell-api'; import translator from '@mongosh/i18n'; import { Worker as WorkerThreads } from 'worker_threads'; -import { CollectionItem } from '../types/collectionItemType'; import { ServerCommands } from './serverCommands'; import { ShellExecuteAllResult, @@ -27,6 +26,14 @@ import { Visitor } from './visitor'; export const languageServerWorkerFileName = 'languageServerWorker.js'; +export type CollectionItem = { + name: string; + type?: string; + options?: object, + info?: { readOnly: boolean; uuid: object[] }, + idIndex?: { v: number; key: object[]; name: string; ns: string } +}; + export type ShellCompletionItem = { [symbol: string]: CompletionItem[] | [] }; diff --git a/src/test/suite/commands/launchMongoShell.test.ts b/src/test/suite/commands/launchMongoShell.test.ts index 0dbb58e7f..e24acb328 100644 --- a/src/test/suite/commands/launchMongoShell.test.ts +++ b/src/test/suite/commands/launchMongoShell.test.ts @@ -2,7 +2,6 @@ import * as vscode from 'vscode'; import * as sinon from 'sinon'; import assert from 'assert'; import { beforeEach, afterEach } from 'mocha'; -import ConnectionModel from 'mongodb-connection-model'; import launchMongoShell from '../../../commands/launchMongoShell'; import { mdbTestExtension } from '../stubbableMdbExtension'; @@ -58,8 +57,7 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should display an error message when not connected', async () => { - const errorMessage = - 'You need to be connected before launching the MongoDB Shell.'; + const errorMessage = 'You need to be connected before launching the MongoDB Shell.'; fakeShowErrorMessage.resolves(errorMessage); @@ -71,14 +69,19 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should open a terminal with the active connection driver url', async () => { - const driverUri = - 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; - const connectionModel = new ConnectionModel({ - hostname: 'localhost', - port: 27018 + const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: 'localhost:27018', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverUrlWithSsh: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverOptions: { readPreference: 'primary' }, + sshTunnelOptions: {}, + username: '', + title: 'localhost:27018' }); - - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -99,15 +102,31 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - const connectionModel = new ConnectionModel({ - hostname: '127.0.0.1', - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'my.ssh-server.com', - sshTunnelUsername: 'my-user', - sshTunnelPassword: 'password' + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: '127.0.0.1:27017', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', + driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', + driverUrlWithSsh: 'mongodb://127.0.0.1:29309/?readPreference=primary&ssl=false', + driverOptions: { readPreference: 'primary' }, + sshTunnelOptions: { + readyTimeout: 20000, + forwardTimeout: 20000, + keepaliveInterval: 20000, + srcAddr: '127.0.0.1', + dstPort: 27017, + dstAddr: '127.0.0.1', + localPort: 29309, + localAddr: '127.0.0.1', + host: 'my.ssh-server.com', + port: 22, + username: 'my-user', + password: 'password' + }, + username: '', + title: '127.0.0.1:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -127,16 +146,24 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should open a terminal with ssl config injected', async () => { - const driverUri = - 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - const connectionModel = new ConnectionModel({ - hostname: '127.0.0.1', - ssl: true, - sslMethod: 'SERVER', - sslCA: './path_to_some_file' + const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: '127.0.0.1:27017', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverUrlWithSsh: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverOptions: { + sslValidate: true, + sslCA: ['./path_to_some_file'], + readPreference: 'primary' + }, + sshTunnelOptions: {}, + username: '', + title: '127.0.0.1:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -157,18 +184,27 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should open a terminal with x509 config injected', async () => { - const driverUri = - 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external'; - const connectionModel = new ConnectionModel({ - sslMethod: 'ALL', - sslKey: ['path/to/key'], - sslCert: ['path/to/cert'], - sslCA: ['path/to/ca'], - authStrategy: 'X509', - x509Username: 'testing' + const driverUri = 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: 'localhost:27017', + driverAuthMechanism: 'MONGODB-X509', + safeUrl: 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external', + driverUrl: 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external', + driverUrlWithSsh: 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external', + driverOptions: { + sslValidate: false, + sslCA: ['./path/to/ca'], + sslKey: './path/to/key', + sslCert: './path/to/cert', + tlsAllowInvalidHostnames: true, + readPreference: 'primary' + }, + sshTunnelOptions: {}, + username: 'testing', + title: 'localhost:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -182,7 +218,7 @@ suite('Commands Test Suite', () => { const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - assert.strictEqual(shellCommandText, 'mongosh --tls --tlsAllowInvalidHostnames --tlsCAFile="path/to/ca" --tlsCertificateKeyFile="path/to/cert" $MDB_CONNECTION_STRING;'); + assert.strictEqual(shellCommandText, 'mongosh --tls --tlsAllowInvalidHostnames --tlsCAFile="./path/to/ca" --tlsCertificateKeyFile="./path/to/cert" $MDB_CONNECTION_STRING;'); }); }); @@ -192,14 +228,20 @@ suite('Commands Test Suite', () => { }); test('powershell openMongoDBShell should open a terminal with the active connection driver url', async () => { - const driverUri = - 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; - const connectionModel = new ConnectionModel({ - hostname: 'localhost', - port: 27018 + const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: 'localhost:27018', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverUrlWithSsh: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverOptions: { readPreference: 'primary' }, + sshTunnelOptions: {}, + username: '', + title: 'localhost:27018' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -222,15 +264,31 @@ suite('Commands Test Suite', () => { }); test('powershell openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - const connectionModel = new ConnectionModel({ - hostname: '127.0.0.1', - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'my.ssh-server.com', - sshTunnelUsername: 'my-user', - sshTunnelPassword: 'password' + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: '127.0.0.1:27017', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', + driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', + driverUrlWithSsh: 'mongodb://127.0.0.1:29500/?readPreference=primary&ssl=false', + driverOptions: { readPreference: 'primary' }, + sshTunnelOptions: { + readyTimeout: 20000, + forwardTimeout: 20000, + keepaliveInterval: 20000, + srcAddr: '127.0.0.1', + dstPort: 27017, + dstAddr: '127.0.0.1', + localPort: 29500, + localAddr: '127.0.0.1', + host: 'my.ssh-server.com', + port: 22, + username: 'my-user', + password: 'password' + }, + username: '', + title: '127.0.0.1:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -252,16 +310,24 @@ suite('Commands Test Suite', () => { }); test('powershell openMongoDBShell should open a terminal with ssl config injected', async () => { - const driverUri = - 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - const connectionModel = new ConnectionModel({ - hostname: '127.0.0.1', - ssl: true, - sslMethod: 'SERVER', - sslCA: './path_to_some_file' + const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: '127.0.0.1:27017', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverUrlWithSsh: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverOptions: { + sslValidate: true, + sslCA: ['./path_to_some_file'], + readPreference: 'primary' + }, + sshTunnelOptions: {}, + username: '', + title: '127.0.0.1:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -277,6 +343,7 @@ suite('Commands Test Suite', () => { ); const shellCommandText = fakeSendTerminalText.firstCall.args[0]; + assert( shellCommandText.includes('--tls'), `Expected open terminal to have tls arg "--tls" found "${shellCommandText}"` @@ -298,14 +365,20 @@ suite('Commands Test Suite', () => { }); test('windows cmd openMongoDBShell should open a terminal with the active connection driver url', async () => { - const driverUri = - 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; - const connectionModel = new ConnectionModel({ - hostname: 'localhost', - port: 27018 + const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: 'localhost:27018', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverUrlWithSsh: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + driverOptions: { readPreference: 'primary' }, + sshTunnelOptions: {}, + username: '', + title: 'localhost:27018' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -322,15 +395,31 @@ suite('Commands Test Suite', () => { }); test('windows cmd openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - const connectionModel = new ConnectionModel({ - hostname: '127.0.0.1', - sshTunnel: 'USER_PASSWORD', - sshTunnelHostname: 'my.ssh-server.com', - sshTunnelUsername: 'my-user', - sshTunnelPassword: 'password' + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: '127.0.0.1:27017', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', + driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', + driverUrlWithSsh: 'mongodb://127.0.0.1:29278/?readPreference=primary&ssl=false', + driverOptions: { readPreference: 'primary' }, + sshTunnelOptions: { + readyTimeout: 20000, + forwardTimeout: 20000, + keepaliveInterval: 20000, + srcAddr: '127.0.0.1', + dstPort: 27017, + dstAddr: '127.0.0.1', + localPort: 29278, + localAddr: '127.0.0.1', + host: 'my.ssh-server.com', + port: 22, + username: 'my-user', + password: 'password' + }, + username: '', + title: '127.0.0.1:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -352,16 +441,24 @@ suite('Commands Test Suite', () => { }); test('windows cmd openMongoDBShell should open a terminal with ssl config injected', async () => { - const driverUri = - 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - const connectionModel = new ConnectionModel({ - hostname: '127.0.0.1', - ssl: true, - sslMethod: 'SERVER', - sslCA: './path_to_some_file' + const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; + + fakeGetActiveDerivedConnectionModel.returns({ + instanceId: '127.0.0.1:27017', + driverAuthMechanism: undefined, + safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverUrlWithSsh: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', + driverOptions: { + sslValidate: true, + sslCA: ['./path_to_some_file'], + readPreference: 'primary' + }, + sshTunnelOptions: {}, + username: '', + title: '127.0.0.1:27017' }); - fakeGetActiveDerivedConnectionModel.returns(connectionModel.getAttributes({ derived: true })); fakeIsCurrentlyConnected.returns(true); await launchMongoShell(mockConnectionController); @@ -377,6 +474,7 @@ suite('Commands Test Suite', () => { ); const shellCommandText = fakeSendTerminalText.firstCall.args[0]; + assert( shellCommandText.includes('--tls'), `Expected open terminal to have tls arg "--tls" found "${shellCommandText}"` diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index 0b068d96c..239d5ce93 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -69,7 +69,6 @@ suite('Connection Controller Test Suite', function () { const connnectionId = testConnectionController.getActiveConnectionId() || ''; const name = testConnectionController._connections[connnectionId].name; - const connectionModel = testConnectionController.getActiveDerivedConnectionModel(); const dataService = testConnectionController.getActiveDataService(); assert( @@ -80,12 +79,10 @@ suite('Connection Controller Test Suite', function () { testConnectionController.getSavedConnections().length === 1, 'Expected there to be 1 connection in the connection list.' ); - assert( name === 'localhost:27018', `Expected active connection to be 'localhost:27018' found ${name}` ); - assert(connectionModel !== null); assert(dataService !== null); assert(testConnectionController.isCurrentlyConnected()); }); @@ -107,7 +104,6 @@ suite('Connection Controller Test Suite', function () { const connectionsCount = testConnectionController.getSavedConnections() .length; const connnectionId = testConnectionController.getActiveConnectionId(); - const connectionModel = testConnectionController.getActiveDerivedConnectionModel(); const dataService = testConnectionController.getActiveDataService(); assert(testConnectionController.getConnectionStatus() === 'DISCONNECTED'); @@ -123,7 +119,6 @@ suite('Connection Controller Test Suite', function () { connnectionId === null, `Expected the active connection id to be null, found ${connnectionId}` ); - assert(connectionModel === null); assert(dataService === null); assert(!testConnectionController.isCurrentlyConnected()); }); diff --git a/src/test/suite/editors/codeActionProvider.test.ts b/src/test/suite/editors/codeActionProvider.test.ts index 132a54ca5..ea6bed36a 100644 --- a/src/test/suite/editors/codeActionProvider.test.ts +++ b/src/test/suite/editors/codeActionProvider.test.ts @@ -308,7 +308,7 @@ suite('Code Action Provider Test Suite', function () { expectedResult = { namespace: 'db.coll', type: null, - content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode%200.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", + content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", language: 'python' }; diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 0e91e4020..4b1d303ed 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -102,9 +102,9 @@ suite('Playground Controller Test Suite', function () { options: { appname: 'VSCode Playground Tests', port: 27018, - sslKey: ['path/to/key'], - sslCert: ['path/to/cert'], - sslCA: ['path/to/ca'], + sslKey: './path/to/key', + sslCert: './path/to/cert', + sslCA: ['./path/to/ca'], } }) }); @@ -138,13 +138,9 @@ suite('Playground Controller Test Suite', function () { ); sinon.replace( testPlaygroundController._connectionController, - 'getActiveDerivedConnectionModel', + 'getMongoClientConnectionOptions', () => ({ - driverOptions: { - sslKey: ['path/to/key'], - sslCert: ['path/to/cert'], - sslCA: ['path/to/ca'] - } + url: 'mongodb://localhost' }) ); @@ -166,21 +162,21 @@ suite('Playground Controller Test Suite', function () { sslKey: string[]; } }).connectionOptions.sslKey - ).to.deep.equal(['path/to/key']); + ).to.equal('./path/to/key'); expect( (mockConnectToServiceProvider.firstCall.firstArg as { connectionOptions: { sslCert: string; } }).connectionOptions.sslCert - ).to.deep.equal(['path/to/cert']); + ).to.equal('./path/to/cert'); expect( (mockConnectToServiceProvider.firstCall.firstArg as { connectionOptions: { sslCA: string; } }).connectionOptions.sslCA - ).to.deep.equal(['path/to/ca']); + ).to.deep.equal(['./path/to/ca']); }); }); @@ -261,18 +257,12 @@ suite('Playground Controller Test Suite', function () { suite('user is not connected', () => { before(() => { const mockGetActiveConnectionName = sinon.fake.returns(''); - const mockGetActiveDerivedConnectionModel = sinon.fake.returns(null); sinon.replace( testPlaygroundController._connectionController, 'getActiveConnectionName', mockGetActiveConnectionName ); - sinon.replace( - testPlaygroundController._connectionController, - 'getActiveDerivedConnectionModel', - mockGetActiveDerivedConnectionModel - ); }); test('run all playground blocks should throw the error', async () => { diff --git a/src/test/suite/telemetry/connectionTelemetry.test.ts b/src/test/suite/telemetry/connectionTelemetry.test.ts index 80977ccce..19c58e901 100644 --- a/src/test/suite/telemetry/connectionTelemetry.test.ts +++ b/src/test/suite/telemetry/connectionTelemetry.test.ts @@ -8,6 +8,7 @@ import { getConnectionTelemetryProperties } from '../../../telemetry/connectionTelemetry'; + const TEST_DATABASE_URI = 'mongodb://localhost:27018'; suite('ConnectionTelemetry Controller Test Suite', function () { diff --git a/src/types/collectionItemType.ts b/src/types/collectionItemType.ts deleted file mode 100644 index b0e22cccd..000000000 --- a/src/types/collectionItemType.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type CollectionItem = { - name: string; - type?: string; - options?: object, - info?: { readOnly: boolean; uuid: object[] }, - idIndex?: { v: number; key: object[]; name: string; ns: string } -}; From ef0a91c8b2549061b73e563cd5581a228fc33f08 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Thu, 16 Dec 2021 21:42:04 +0100 Subject: [PATCH 05/15] refactor: use mongodb-connection-string-url instead of connectionModel.from --- src/connectionController.ts | 33 ++++++++++--------- src/test/suite/connectionController.test.ts | 6 ++-- .../suite/editors/codeActionProvider.test.ts | 2 +- .../suite/explorer/explorerController.test.ts | 4 +-- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index efd11d2db..a285f3508 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -9,8 +9,8 @@ import { getConnectionTitle } from 'mongodb-data-service'; import ConnectionModel from 'mongodb-connection-model'; +import ConnectionString from 'mongodb-connection-string-url'; import { EventEmitter } from 'events'; -import { promisify } from 'util'; import { v4 as uuidv4 } from 'uuid'; import { CONNECTION_STATUS } from './views/webview-app/extension-app-message-constants'; @@ -112,15 +112,15 @@ export default class ConnectionController { ): Promise { // Transform a raw connection model from storage to an ampersand model. const newConnectionModelWithSecrets = new ConnectionModel(savedConnectionInfo.connectionModel); - // Convert the connection model to a connection info object. - const newConnectionInfoWithSecrets = convertConnectionModelToInfo(newConnectionModelWithSecrets); - // Use connectionOptions instead of connectionModel. + // Further use connectionOptions instead of connectionModel. const newSavedConnectionInfoWithSecrets = { id: savedConnectionInfo.id, name: savedConnectionInfo.name, storageLocation: savedConnectionInfo.storageLocation, - connectionOptions: newConnectionInfoWithSecrets.connectionOptions + connectionOptions: { + connectionString: this.getDerivedConnectionOptionsByModel(newConnectionModelWithSecrets).driverUrlWithSsh + } }; await this._saveConnection(newSavedConnectionInfoWithSecrets); @@ -273,18 +273,17 @@ export default class ConnectionController { ): Promise { log.info('Trying to connect to a new connection configuration'); - const connectionFrom = promisify(ConnectionModel.from.bind(ConnectionModel)); + const connectionStringData = new ConnectionString(connectionString); - try { - const connectionModel = await connectionFrom(connectionString); + connectionStringData.searchParams.set('appname', `${name} ${version}`); - if (typeof connectionModel.appname === 'undefined') { - connectionModel.appname = `${name} ${version}`; - } - - const connectionInfo = convertConnectionModelToInfo(connectionModel); + try { const connectResult = await this.saveNewConnectionAndConnect( - connectionInfo, + { + connectionOptions: { + connectionString: connectionStringData.toString() + } + }, ConnectionTypes.CONNECTION_STRING ); @@ -714,8 +713,12 @@ export default class ConnectionController { return this._activeDataService?.getMongoClientConnectionOptions() || {}; } + getDerivedConnectionOptionsByModel(connectionModel: ConnectionModel): ConnectionModel { + return connectionModel?.getAttributes({ derived: true }) || {}; + } + getActiveDerivedConnectionModel(): ConnectionModel { - return this._activeConnectionModel?.getAttributes({ derived: true }) || {}; + return this.getDerivedConnectionOptionsByModel(this._activeConnectionModel); } async getConnectionStringByConnectionId(connectionId: string): Promise { diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index 239d5ce93..d02f95aeb 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -270,8 +270,7 @@ suite('Connection Controller Test Suite', function () { }); test('the connection model loads both global and workspace stored connection models', async () => { - const expectedDriverUri = - 'mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false'; + const expectedDriverUri = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; await vscode.workspace .getConfiguration('mdb.connectionSaving') @@ -477,8 +476,7 @@ suite('Connection Controller Test Suite', function () { }); test('"getConnectionStringByConnectionId" returns the driver uri of a connection', async () => { - const expectedDriverUri = - 'mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode%200.0.0-dev.0&directConnection=true&ssl=false'; + const expectedDriverUri = 'mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode%200.0.0-dev.0&directConnection=true&ssl=true'; await testConnectionController.loadSavedConnections(); await testConnectionController.addNewConnectionStringAndConnect( diff --git a/src/test/suite/editors/codeActionProvider.test.ts b/src/test/suite/editors/codeActionProvider.test.ts index ea6bed36a..bac78938f 100644 --- a/src/test/suite/editors/codeActionProvider.test.ts +++ b/src/test/suite/editors/codeActionProvider.test.ts @@ -308,7 +308,7 @@ suite('Code Action Provider Test Suite', function () { expectedResult = { namespace: 'db.coll', type: null, - content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", + content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0&directConnection=true')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", language: 'python' }; diff --git a/src/test/suite/explorer/explorerController.test.ts b/src/test/suite/explorer/explorerController.test.ts index 8cc3167e0..36cbaab25 100644 --- a/src/test/suite/explorer/explorerController.test.ts +++ b/src/test/suite/explorer/explorerController.test.ts @@ -172,8 +172,8 @@ suite('Explorer Controller Test Suite', function () { 'Expected the first connection tree item to not be expanded' ); assert( - connectionsItems[1].label === 'shouldfail:27017', - 'Second connection tree item should have label "shouldfail:27017"' + connectionsItems[1].label === 'shouldfail', + 'Second connection tree item should have label "shouldfail"' ); }); From 3ddf4074bfb77ee0d4e39c081eb3ee9d814d2402 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Fri, 17 Dec 2021 11:08:50 +0100 Subject: [PATCH 06/15] fix: keep all connection options from new connection model --- src/connectionController.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index a285f3508..62e7e6e23 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -112,15 +112,14 @@ export default class ConnectionController { ): Promise { // Transform a raw connection model from storage to an ampersand model. const newConnectionModelWithSecrets = new ConnectionModel(savedConnectionInfo.connectionModel); + const newConnectionInfoWithSecrets = convertConnectionModelToInfo(newConnectionModelWithSecrets); // Further use connectionOptions instead of connectionModel. const newSavedConnectionInfoWithSecrets = { id: savedConnectionInfo.id, name: savedConnectionInfo.name, storageLocation: savedConnectionInfo.storageLocation, - connectionOptions: { - connectionString: this.getDerivedConnectionOptionsByModel(newConnectionModelWithSecrets).driverUrlWithSsh - } + connectionOptions: newConnectionInfoWithSecrets.connectionOptions }; await this._saveConnection(newSavedConnectionInfoWithSecrets); @@ -713,12 +712,8 @@ export default class ConnectionController { return this._activeDataService?.getMongoClientConnectionOptions() || {}; } - getDerivedConnectionOptionsByModel(connectionModel: ConnectionModel): ConnectionModel { - return connectionModel?.getAttributes({ derived: true }) || {}; - } - getActiveDerivedConnectionModel(): ConnectionModel { - return this.getDerivedConnectionOptionsByModel(this._activeConnectionModel); + return this._activeConnectionModel?.getAttributes({ derived: true }) || {}; } async getConnectionStringByConnectionId(connectionId: string): Promise { From d68767933be000210e654c125cf9bf2173c03b1c Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Sat, 18 Dec 2021 01:05:14 +0100 Subject: [PATCH 07/15] refactor: give connectionString to users to copy instead of driverUrlWithSsh --- src/connectionController.ts | 18 +- src/mdbExtensionController.ts | 2 +- src/test/suite/connectionController.test.ts | 10 +- src/test/suite/dbTestHelper.ts | 33 +- .../suite/explorer/databaseTreeItem.test.ts | 33 +- src/test/suite/explorer/fieldTreeItem.test.ts | 446 +++++++++--------- .../suite/explorer/schemaTreeItem.test.ts | 237 +++++----- .../telemetry/connectionTelemetry.test.ts | 1 - .../suite/telemetry/telemetryService.test.ts | 1 - 9 files changed, 383 insertions(+), 398 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index 62e7e6e23..b55bbeedc 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -364,10 +364,7 @@ export default class ConnectionController { } async getDataServiceAndConnect(connectionOptions: ConnectionOptions) { - const dataService = await connect(connectionOptions); - await dataService.connect(); - - return dataService; + return connect(connectionOptions); } private async _connect( @@ -712,20 +709,13 @@ export default class ConnectionController { return this._activeDataService?.getMongoClientConnectionOptions() || {}; } + getActiveDerivedConnectionModel(): ConnectionModel { return this._activeConnectionModel?.getAttributes({ derived: true }) || {}; } - async getConnectionStringByConnectionId(connectionId: string): Promise { - const connectionOptions = this._connections[connectionId].connectionOptions; - - if (!connectionOptions) { - throw new Error('Connection not found.'); - } - - const connectionModel = await convertConnectionInfoToModel({ connectionOptions }); - - return connectionModel.driverUrlWithSsh || ''; + getConnectionStringByConnectionId(connectionId: string): string { + return this._connections[connectionId].connectionOptions.connectionString; } getConnectionStatus(): CONNECTION_STATUS { diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 8dd19531d..91c9465c8 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -296,7 +296,7 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand( EXTENSION_COMMANDS.MDB_COPY_CONNECTION_STRING, async (element: ConnectionTreeItem): Promise => { - const connectionString = await this._connectionController.getConnectionStringByConnectionId( + const connectionString = this._connectionController.getConnectionStringByConnectionId( element.connectionId ); diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index d02f95aeb..70d172541 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -476,7 +476,7 @@ suite('Connection Controller Test Suite', function () { }); test('"getConnectionStringByConnectionId" returns the driver uri of a connection', async () => { - const expectedDriverUri = 'mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode%200.0.0-dev.0&directConnection=true&ssl=true'; + const expectedDriverUri = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; await testConnectionController.loadSavedConnections(); await testConnectionController.addNewConnectionStringAndConnect( @@ -490,7 +490,7 @@ suite('Connection Controller Test Suite', function () { 'Expected active connection to not be null' ); - const testDriverUri = await testConnectionController.getConnectionStringByConnectionId( + const testDriverUri = testConnectionController.getConnectionStringByConnectionId( activeConnectionId || '' ); @@ -893,11 +893,7 @@ suite('Connection Controller Test Suite', function () { 'getDataServiceAndConnect', sinon.fake(async (connectionOptions) => { await sleep(50); - - const dataService = await connect(connectionOptions); - await dataService.connect(); - - return dataService; + return await connect(connectionOptions); }) ); diff --git a/src/test/suite/dbTestHelper.ts b/src/test/suite/dbTestHelper.ts index ce6075363..d873412f1 100644 --- a/src/test/suite/dbTestHelper.ts +++ b/src/test/suite/dbTestHelper.ts @@ -1,6 +1,6 @@ import { connect, DataService } from 'mongodb-data-service'; import { EJSON } from 'bson'; -import { promisify } from 'util'; +import * as util from 'util'; export const TEST_USER_USERNAME = 'testUser'; export const TEST_USER_PASSWORD = 'password'; @@ -12,31 +12,24 @@ export const TEST_DB_NAME = 'vscodeTestDatabaseAA'; let testDataService; -// Note: Be sure to disconnect from the dataservice to free up connections. -export const seedDataAndCreateDataService = async ( +export const createTestDataService = async (): Promise => { + testDataService = await connect({ connectionString: TEST_DATABASE_URI }); + return testDataService; +}; + +export const seedTestDB = async ( collectionName: string, documentsArray: EJSON.SerializableTypes[] -): Promise => { - if (!testDataService) { - testDataService = await connect({ connectionString: TEST_DATABASE_URI }); - } - - const insertMany = promisify(testDataService.insertMany.bind(testDataService)); - - await testDataService.connect(); +): Promise => { + const insertMany = util.promisify(testDataService.insertMany.bind(testDataService)); await insertMany(`${TEST_DB_NAME}.${collectionName}`, documentsArray, {}); - - return testDataService; }; export const cleanupTestDB = async (): Promise => { - if (!testDataService) { - testDataService = await connect({ connectionString: TEST_DATABASE_URI }); - } - - const dropDatabase = promisify(testDataService.dropDatabase.bind(testDataService)); - - await testDataService.connect(); + const dropDatabase = util.promisify(testDataService.dropDatabase.bind(testDataService)); await dropDatabase(TEST_DB_NAME); +}; + +export const disconnectFromTestDB = async (): Promise => { await testDataService.disconnect(); }; diff --git a/src/test/suite/explorer/databaseTreeItem.test.ts b/src/test/suite/explorer/databaseTreeItem.test.ts index 0acea3657..90c7a67b2 100644 --- a/src/test/suite/explorer/databaseTreeItem.test.ts +++ b/src/test/suite/explorer/databaseTreeItem.test.ts @@ -1,13 +1,15 @@ import * as vscode from 'vscode'; -import { afterEach } from 'mocha'; +import { after, before } from 'mocha'; import assert from 'assert'; import { CollectionTreeItem } from '../../../explorer'; import DatabaseTreeItem from '../../../explorer/databaseTreeItem'; import { DataServiceStub, mockDatabaseNames, mockDatabases } from '../stubs'; import { - seedDataAndCreateDataService, + createTestDataService, + seedTestDB, cleanupTestDB, + disconnectFromTestDB, TEST_DB_NAME } from '../dbTestHelper'; @@ -66,7 +68,7 @@ suite('DatabaseTreeItem Test Suite', () => { {} ); - void testDatabaseTreeItem.onDidExpand(); + await testDatabaseTreeItem.onDidExpand(); const collections = await testDatabaseTreeItem.getChildren(); assert( @@ -180,9 +182,15 @@ suite('DatabaseTreeItem Test Suite', () => { suite('Live Database Tests', function () { this.timeout(5000); + let dataService; + + before(async () => { + dataService = await createTestDataService(); + }); - afterEach(async () => { + after(async () => { await cleanupTestDB(); + await disconnectFromTestDB(); }); test('schema is cached when a database is collapsed and expanded', async () => { @@ -197,9 +205,8 @@ suite('DatabaseTreeItem Test Suite', () => { mockDocWithThirtyFields[`field${i}`] = 'some value'; } - const dataService = await seedDataAndCreateDataService('ramenNoodles', [ - mockDocWithThirtyFields - ]); + await seedTestDB('ramenNoodles', [mockDocWithThirtyFields]); + const testDatabaseTreeItem = new DatabaseTreeItem( TEST_DB_NAME, dataService, @@ -214,12 +221,12 @@ suite('DatabaseTreeItem Test Suite', () => { 'Expected collection tree item not to be expanded on default.' ); - void collectionTreeItems[0].onDidExpand(); + await collectionTreeItems[0].onDidExpand(); const schemaTreeItem = collectionTreeItems[0].getSchemaChild(); if (!schemaTreeItem) { assert(false, 'No schema tree item found on collection.'); } - void schemaTreeItem.onDidExpand(); + await schemaTreeItem.onDidExpand(); schemaTreeItem.onShowMoreClicked(); const fields: any[] = await schemaTreeItem.getChildren(); @@ -236,20 +243,18 @@ suite('DatabaseTreeItem Test Suite', () => { ); // Expand the subdocument. - void schemaTreeItem.childrenCache.testerObject.onDidExpand(); + await schemaTreeItem.childrenCache.testerObject.onDidExpand(); testDatabaseTreeItem.onDidCollapse(); - const postCollapseCollectionTreeItems = await testDatabaseTreeItem - .getChildren(); + const postCollapseCollectionTreeItems = await testDatabaseTreeItem.getChildren(); assert( postCollapseCollectionTreeItems.length === 0, `Expected the database tree to return no children when collapsed, found ${collectionTreeItems.length}` ); - void testDatabaseTreeItem.onDidExpand(); + await testDatabaseTreeItem.onDidExpand(); const newCollectionTreeItems = await testDatabaseTreeItem .getChildren(); - void dataService.disconnect(); const postCollapseSchemaTreeItem = newCollectionTreeItems[0].getSchemaChild(); assert( diff --git a/src/test/suite/explorer/fieldTreeItem.test.ts b/src/test/suite/explorer/fieldTreeItem.test.ts index 10ef3d908..1217e1809 100644 --- a/src/test/suite/explorer/fieldTreeItem.test.ts +++ b/src/test/suite/explorer/fieldTreeItem.test.ts @@ -1,4 +1,4 @@ -import { afterEach } from 'mocha'; +import { after, afterEach, before } from 'mocha'; import assert from 'assert'; import { ext } from '../../../extensionConstants'; @@ -8,8 +8,10 @@ import FieldTreeItem, { getIconFileNameForField } from '../../../explorer/fieldTreeItem'; import { - seedDataAndCreateDataService, + createTestDataService, + seedTestDB, cleanupTestDB, + disconnectFromTestDB, TEST_DB_NAME } from '../dbTestHelper'; import SchemaTreeItem from '../../../explorer/schemaTreeItem'; @@ -132,241 +134,239 @@ suite('FieldTreeItem Test Suite', function () { suite('Full database tests', () => { this.timeout(5000); + let dataService; + + before(async () => { + dataService = await createTestDataService(); + }); afterEach(async () => { await cleanupTestDB(); }); - test('field name is pulled from the name of a field', (done) => { - void seedDataAndCreateDataService('pie', [ - { - _id: 1, - blueberryPie: 'yes' - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'pie', - TEST_DB_NAME, - dataService, - true, - false, - false, - false, - {} - ); - - testSchemaTreeItem - .getChildren() - .then((schemaFields) => { - void dataService.disconnect(); - - assert( - schemaFields[0].label === '_id', - `Expected field name to be "_id" recieved ${schemaFields[0].label}` - ); - assert( - schemaFields[1].label === 'blueberryPie', - `Expected field name to be "blueberryPie" recieved ${schemaFields[0].label}` - ); - assert( - schemaFields[1].fieldName === 'blueberryPie', - `Expected field name to be "blueberryPie" recieved ${schemaFields[0].label}` - ); - }) - .then(done, done); - }); + after(async () => { + dataService = await disconnectFromTestDB(); }); - test('it shows dropdowns for nested subdocuments', (done) => { - void seedDataAndCreateDataService('gryffindor', [ - { - _id: 1, - alwaysDocument: { - nestedSubDocument: { - magic: true, - harry: 'potter' + test('field name is pulled from the name of a field', async () => { + await seedTestDB( + 'pie', + [{ _id: 1, blueberryPie: 'yes' }] + ); + const testSchemaTreeItem = new SchemaTreeItem( + 'pie', + TEST_DB_NAME, + dataService, + true, + false, + false, + false, + {} + ); + const schemaFields = await testSchemaTreeItem.getChildren(); + + assert( + schemaFields[0].label === '_id', + `Expected field name to be "_id" recieved ${schemaFields[0].label}` + ); + assert( + schemaFields[1].label === 'blueberryPie', + `Expected field name to be "blueberryPie" recieved ${schemaFields[0].label}` + ); + assert( + schemaFields[1].fieldName === 'blueberryPie', + `Expected field name to be "blueberryPie" recieved ${schemaFields[0].label}` + ); + }); + + test('it shows dropdowns for nested subdocuments', async () => { + await seedTestDB( + 'gryffindor', + [ + { + _id: 1, + alwaysDocument: { + nestedSubDocument: { + magic: true, + harry: 'potter' + } } - } - }, - { - _id: 2, - alwaysDocument: { - nestedSubDocument: { - magic: true, - hermione: 'granger' + }, + { + _id: 2, + alwaysDocument: { + nestedSubDocument: { + magic: true, + hermione: 'granger' + } } } - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'gryffindor', - TEST_DB_NAME, - dataService, - false, - false, - false, - false, - {} - ); - - void testSchemaTreeItem.onDidExpand(); - - void testSchemaTreeItem.getChildren().then((schemaFields) => { - void dataService.disconnect(); - assert( - schemaFields.length === 2, - `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` - ); - assert( - !fieldIsExpandable(schemaFields[0].field), - 'Expected _id field not to have expandable state' - ); - assert( - fieldIsExpandable(schemaFields[1].field), - 'Expected field to have expandable state' - ); - schemaFields[1] - .getChildren() - .then((subdocuments) => { - assert( - subdocuments.length === 1, - `Expected subdocument to have 1 field found ${subdocuments.length}` - ); - assert( - fieldIsExpandable(subdocuments[0].field), - 'Expected subdocument to be expandable' - ); - subdocuments[0] - .getChildren() - .then((nestedSubDocument) => { - assert( - nestedSubDocument.length === 3, - 'Expected nested subdocument to have 3 fields' - ); - }) - .then(done, done); - }) - .catch(done); - }); - }); + ] + ); + + const testSchemaTreeItem = new SchemaTreeItem( + 'gryffindor', + TEST_DB_NAME, + dataService, + false, + false, + false, + false, + {} + ); + + await testSchemaTreeItem.onDidExpand(); + + const schemaFields = await testSchemaTreeItem.getChildren(); + + assert( + schemaFields.length === 2, + `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` + ); + assert( + !fieldIsExpandable(schemaFields[0].field), + 'Expected _id field not to have expandable state' + ); + assert( + fieldIsExpandable(schemaFields[1].field), + 'Expected field to have expandable state' + ); + + const subdocuments = await schemaFields[1].getChildren(); + + assert( + subdocuments.length === 1, + `Expected subdocument to have 1 field found ${subdocuments.length}` + ); + assert( + fieldIsExpandable(subdocuments[0].field), + 'Expected subdocument to be expandable' + ); + + const nestedSubDocument = await subdocuments[0].getChildren(); + + assert( + nestedSubDocument.length === 3, + 'Expected nested subdocument to have 3 fields' + ); }); - test('it shows dropdowns for arrays', (done) => { - void seedDataAndCreateDataService('gryffindor', [ - { - _id: 1, - testingArray: ['okay', 'nice'] - }, - { - _id: 2, - testingArray: ['dobby'] - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'gryffindor', - TEST_DB_NAME, - dataService, - false, - false, - false, - false, - {} - ); - - void testSchemaTreeItem.onDidExpand(); - - testSchemaTreeItem.getChildren().then((schemaFields) => { - void dataService.disconnect(); - assert( - schemaFields.length === 2, - `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` - ); - assert( - fieldIsExpandable(schemaFields[1].field), - 'Expected field to have expandable state' - ); - schemaFields[1] - .getChildren() - .then((arrayFieldContainer) => { - assert( - arrayFieldContainer.length === 1, - `Expected array field to have 1 field found ${arrayFieldContainer.length}` - ); - assert( - !fieldIsExpandable(arrayFieldContainer[0].field), - 'Expected array field container to not be expandable' - ); - }).then(done, done); - }, done); - }); + test('it shows dropdowns for arrays', async () => { + await seedTestDB( + 'gryffindor', + [ + { + _id: 1, + testingArray: ['okay', 'nice'] + }, + { + _id: 2, + testingArray: ['dobby'] + } + ] + ); + + const testSchemaTreeItem = new SchemaTreeItem( + 'gryffindor', + TEST_DB_NAME, + dataService, + false, + false, + false, + false, + {} + ); + + await testSchemaTreeItem.onDidExpand(); + + const schemaFields = await testSchemaTreeItem.getChildren(); + + assert( + schemaFields.length === 2, + `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` + ); + assert( + fieldIsExpandable(schemaFields[1].field), + 'Expected field to have expandable state' + ); + + const arrayFieldContainer = await schemaFields[1].getChildren(); + + assert( + arrayFieldContainer.length === 1, + `Expected array field to have 1 field found ${arrayFieldContainer.length}` + ); + assert( + !fieldIsExpandable(arrayFieldContainer[0].field), + 'Expected array field container to not be expandable' + ); }); - test('it shows dropdowns and fields for document fields in arrays', (done) => { - void seedDataAndCreateDataService('beach', [ - { - _id: 1, - testingArray: [ - { - color: 'orange', - sunset: false - } - ] - }, - { - _id: 2, - testingArray: [ - { - color: 'violet', - sunset: true - } - ] - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'beach', - TEST_DB_NAME, - dataService, - false, - false, - false, - false, - {} - ); - - void testSchemaTreeItem.onDidExpand(); - - void testSchemaTreeItem.getChildren().then((schemaFields) => { - void dataService.disconnect(); - - schemaFields[1].getChildren().then((nestedSubDocuments) => { - assert( - nestedSubDocuments.length === 1, - `Expected array field fields to have 1 field found ${nestedSubDocuments.length}` - ); - assert( - fieldIsExpandable(nestedSubDocuments[0].field), - 'Expected subdocument in array to be expandable' - ); - nestedSubDocuments[0] - .getChildren() - .then((subdocFields) => { - assert( - subdocFields.length === 2, - `Expected subdocument in array field to have 2 fields found ${subdocFields.length}` - ); - assert( - subdocFields[1].label === 'sunset', - 'Expected subdocument field to have correct label' - ); - assert( - !fieldIsExpandable(subdocFields[1].field), - 'Expected subdocument boolean field to not be expandable' - ); - }) - .then(done, done); - }); - }); - }); + test('it shows dropdowns and fields for document fields in arrays', async () => { + await seedTestDB( + 'beach', + [ + { + _id: 1, + testingArray: [ + { + color: 'orange', + sunset: false + } + ] + }, + { + _id: 2, + testingArray: [ + { + color: 'violet', + sunset: true + } + ] + } + ] + ); + + const testSchemaTreeItem = new SchemaTreeItem( + 'beach', + TEST_DB_NAME, + dataService, + false, + false, + false, + false, + {} + ); + + await testSchemaTreeItem.onDidExpand(); + + const schemaFields = await testSchemaTreeItem.getChildren(); + + const nestedSubDocuments = await schemaFields[1].getChildren(); + + assert( + nestedSubDocuments.length === 1, + `Expected array field fields to have 1 field found ${nestedSubDocuments.length}` + ); + assert( + fieldIsExpandable(nestedSubDocuments[0].field), + 'Expected subdocument in array to be expandable' + ); + + const subdocFields = await nestedSubDocuments[0].getChildren(); + + assert( + subdocFields.length === 2, + `Expected subdocument in array field to have 2 fields found ${subdocFields.length}` + ); + assert( + subdocFields[1].label === 'sunset', + 'Expected subdocument field to have correct label' + ); + assert( + !fieldIsExpandable(subdocFields[1].field), + 'Expected subdocument boolean field to not be expandable' + ); }); }); }); diff --git a/src/test/suite/explorer/schemaTreeItem.test.ts b/src/test/suite/explorer/schemaTreeItem.test.ts index 04b4f3561..c2d028754 100644 --- a/src/test/suite/explorer/schemaTreeItem.test.ts +++ b/src/test/suite/explorer/schemaTreeItem.test.ts @@ -1,15 +1,17 @@ import * as vscode from 'vscode'; import * as sinon from 'sinon'; -import { afterEach } from 'mocha'; +import { after, afterEach, before } from 'mocha'; import assert from 'assert'; import { inspect } from 'util'; import { ext } from '../../../extensionConstants'; import { fieldIsExpandable } from '../../../explorer/fieldTreeItem'; import { - seedDataAndCreateDataService, + createTestDataService, + seedTestDB, cleanupTestDB, - TEST_DB_NAME + TEST_DB_NAME, + disconnectFromTestDB } from '../dbTestHelper'; import SchemaTreeItem, { FIELDS_TO_SHOW @@ -226,135 +228,136 @@ suite('SchemaTreeItem Test Suite', function () { suite('Live Database Tests', () => { this.timeout(5000); + let dataService; + + before(async () => { + dataService = await createTestDataService(); + }); afterEach(async () => { await cleanupTestDB(); }); - test('when not expanded it has not yet pulled the schema', (done) => { - void seedDataAndCreateDataService('favoritePiesIWantToEatRightNow', [ - { + after(async () => { + await disconnectFromTestDB(); + }); + + test('when not expanded it has not yet pulled the schema', async () => { + await seedTestDB( + 'favoritePiesIWantToEatRightNow', + [{ _id: 10, someField: 'applePie' - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'favoritePiesIWantToEatRightNow', - TEST_DB_NAME, - dataService, - false, - false, - false, - false, - {} - ); - - testSchemaTreeItem - .getChildren() - .then((schemaFields) => { - void dataService.disconnect(); - - assert( - schemaFields.length === 0, - `Expected no schema tree items to be returned, recieved ${schemaFields.length}` - ); - }) - .then(done, done); - }); + }] + ); + + const testSchemaTreeItem = new SchemaTreeItem( + 'favoritePiesIWantToEatRightNow', + TEST_DB_NAME, + dataService, + false, + false, + false, + false, + {} + ); + + const schemaFields = await testSchemaTreeItem.getChildren(); + + assert( + schemaFields.length === 0, + `Expected no schema tree items to be returned, recieved ${schemaFields.length}` + ); }); - test('when expanded shows the fields of a schema', (done) => { - void seedDataAndCreateDataService('favoritePiesIWantToEatRightNow', [ - { + test('when expanded shows the fields of a schema', async () => { + await seedTestDB( + 'favoritePiesIWantToEatRightNow', + [{ _id: 1, nameOfTastyPie: 'applePie' - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'favoritePiesIWantToEatRightNow', - TEST_DB_NAME, - dataService, - false, - false, - false, - false, - {} - ); - - void testSchemaTreeItem.onDidExpand(); - - testSchemaTreeItem - .getChildren() - .then((schemaFields) => { - void dataService.disconnect(); - assert( - schemaFields.length === 2, - `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` - ); - assert( - schemaFields[0].label === '_id', - `Expected label of schema tree item to be the field name, recieved ${schemaFields[0].label}` - ); - assert( - schemaFields[1].label === 'nameOfTastyPie', - `Expected label of schema tree item to be the field name, recieved ${schemaFields[1].label}` - ); - }) - .then(done, done); - }); + }] + ); + + const testSchemaTreeItem = new SchemaTreeItem( + 'favoritePiesIWantToEatRightNow', + TEST_DB_NAME, + dataService, + false, + false, + false, + false, + {} + ); + + await testSchemaTreeItem.onDidExpand(); + + const schemaFields = await testSchemaTreeItem.getChildren(); + + assert( + schemaFields.length === 2, + `Expected 2 schema tree items to be returned, recieved ${schemaFields.length}` + ); + assert( + schemaFields[0].label === '_id', + `Expected label of schema tree item to be the field name, recieved ${schemaFields[0].label}` + ); + assert( + schemaFields[1].label === 'nameOfTastyPie', + `Expected label of schema tree item to be the field name, recieved ${schemaFields[1].label}` + ); }); - test('it only shows a dropdown for fields which are always documents - and not for polymorphic', (done) => { - void seedDataAndCreateDataService('favoritePiesIWantToEatRightNow', [ - { - _id: 1, - alwaysDocument: { - fieldName: 'nice' + test('it only shows a dropdown for fields which are always documents - and not for polymorphic', async () => { + await seedTestDB( + 'favoritePiesIWantToEatRightNow', + [ + { + _id: 1, + alwaysDocument: { + fieldName: 'nice' + }, + notAlwaysADocument: { + sureImADocument: 'hmmmm' + } }, - notAlwaysADocument: { - sureImADocument: 'hmmmm' + { + _id: 2, + alwaysDocument: { + fieldName: 'nice' + }, + notAlwaysADocument: 'A spy!' } - }, - { - _id: 2, - alwaysDocument: { - fieldName: 'nice' - }, - notAlwaysADocument: 'A spy!' - } - ]).then((dataService) => { - const testSchemaTreeItem = new SchemaTreeItem( - 'favoritePiesIWantToEatRightNow', - TEST_DB_NAME, - dataService, - false, - false, - false, - false, - {} - ); - - void testSchemaTreeItem.onDidExpand(); - - testSchemaTreeItem - .getChildren() - .then((schemaFields) => { - void dataService.disconnect(); - assert( - schemaFields.length === 3, - `Expected 3 schema tree items to be returned, recieved ${schemaFields.length}: ${inspect(schemaFields)}` - ); - assert( - fieldIsExpandable(schemaFields[1].field), - 'Expected field to have expandable state' - ); - assert( - fieldIsExpandable(schemaFields[2].field) === false, - 'Expected field to have none expandable state' - ); - }) - .then(done, done); - }); + ] + ); + + const testSchemaTreeItem = new SchemaTreeItem( + 'favoritePiesIWantToEatRightNow', + TEST_DB_NAME, + dataService, + false, + false, + false, + false, + {} + ); + + await testSchemaTreeItem.onDidExpand(); + + const schemaFields = await testSchemaTreeItem.getChildren(); + + assert( + schemaFields.length === 3, + `Expected 3 schema tree items to be returned, recieved ${schemaFields.length}: ${inspect(schemaFields)}` + ); + assert( + fieldIsExpandable(schemaFields[1].field), + 'Expected field to have expandable state' + ); + assert( + fieldIsExpandable(schemaFields[2].field) === false, + 'Expected field to have none expandable state' + ); }); }); diff --git a/src/test/suite/telemetry/connectionTelemetry.test.ts b/src/test/suite/telemetry/connectionTelemetry.test.ts index 19c58e901..63a7c42a9 100644 --- a/src/test/suite/telemetry/connectionTelemetry.test.ts +++ b/src/test/suite/telemetry/connectionTelemetry.test.ts @@ -95,7 +95,6 @@ suite('ConnectionTelemetry Controller Test Suite', function () { beforeEach(async () => { dataServ = await connect({ connectionString: TEST_DATABASE_URI }); - await dataServ.connect(); }); afterEach(async () => { diff --git a/src/test/suite/telemetry/telemetryService.test.ts b/src/test/suite/telemetry/telemetryService.test.ts index eb6116cc3..493c00c50 100644 --- a/src/test/suite/telemetry/telemetryService.test.ts +++ b/src/test/suite/telemetry/telemetryService.test.ts @@ -304,7 +304,6 @@ suite('Telemetry Controller Test Suite', () => { let dataServ; beforeEach(async () => { dataServ = await connect({ connectionString: TEST_DATABASE_URI }); - await dataServ.connect(); }); afterEach(async () => { From f445cce5cd3031df150365c8ca113060cee68eec Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 20 Dec 2021 00:04:55 +0100 Subject: [PATCH 08/15] refactor: use getMongoClientConnectionOptions to launch shell --- src/commands/launchMongoShell.ts | 81 ++-- src/connectionController.ts | 37 +- .../suite/commands/launchMongoShell.test.ts | 355 +----------------- .../editors/playgroundController.test.ts | 3 +- .../suite/views/webviewController.test.ts | 42 +-- src/views/webviewController.ts | 1 - 6 files changed, 71 insertions(+), 448 deletions(-) diff --git a/src/commands/launchMongoShell.ts b/src/commands/launchMongoShell.ts index 2af88fe90..4ac4aceb9 100644 --- a/src/commands/launchMongoShell.ts +++ b/src/commands/launchMongoShell.ts @@ -1,42 +1,10 @@ import * as vscode from 'vscode'; -import ConnectionModel from 'mongodb-connection-model'; import ConnectionController from '../connectionController'; -const isSslConnection = (activeDerivedConnectionModel: ConnectionModel): boolean => { - return !!( - activeDerivedConnectionModel?.driverOptions?.sslCA || - activeDerivedConnectionModel?.driverOptions?.sslCert || - activeDerivedConnectionModel?.driverOptions?.sslPass - ); -}; - -const getSslOptions = (driverOptions: any): string[] => { - const mdbSslOptions = ['--tls']; - - if (!driverOptions.checkServerIdentity) { - mdbSslOptions.push('--tlsAllowInvalidHostnames'); - } - - if (driverOptions.sslCA) { - mdbSslOptions.push(`--tlsCAFile="${driverOptions.sslCA}"`); - } - - if (driverOptions.sslCert) { - mdbSslOptions.push(`--tlsCertificateKeyFile="${driverOptions.sslCert}"`); - } - - if (driverOptions.sslPass) { - mdbSslOptions.push(`--tlsCertificateKeyFilePassword="${driverOptions.sslPass}"`); - } - - return mdbSslOptions; -}; - const launchMongoDBShellWithEnv = ( shellCommand: string, mdbConnectionString: string, - mdbSslOptions: string[], envVariableString: string ) => { const mongoDBShell = vscode.window.createTerminal({ @@ -46,51 +14,41 @@ const launchMongoDBShellWithEnv = ( } }); - const mdbSslOptionsString = mdbSslOptions.length > 0 - ? `${mdbSslOptions.join(' ')} ` - : ''; - mongoDBShell.sendText( - `${shellCommand} ${mdbSslOptionsString}${envVariableString};` + `${shellCommand} ${envVariableString};` ); mongoDBShell.show(); }; const launchMongoDBShellOnPowershell = ( shellCommand: string, - mdbConnectionString: string, - mdbSslOptions: string[] + mdbConnectionString: string ): void => { - launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, mdbSslOptions, '$Env:MDB_CONNECTION_STRING'); + launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, '$Env:MDB_CONNECTION_STRING'); }; const launchMongoDBShellOnCmd = ( shellCommand: string, - mdbConnectionString: string, - mdbSslOptions: string[] + mdbConnectionString: string ): void => { - launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, mdbSslOptions, '%MDB_CONNECTION_STRING%'); + launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, '%MDB_CONNECTION_STRING%'); }; const launchMongoDBShellOnGitBash = ( shellCommand: string, - mdbConnectionString: string, - mdbSslOptions: string[] + mdbConnectionString: string ): void => { - launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, mdbSslOptions, '$MDB_CONNECTION_STRING'); + launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, '$MDB_CONNECTION_STRING'); }; const launchMongoDBShellOnBash = ( shellCommand: string, - mdbConnectionString: string, - mdbSslOptions: string[] + mdbConnectionString: string ): void => { - launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, mdbSslOptions, '$MDB_CONNECTION_STRING'); + launchMongoDBShellWithEnv(shellCommand, mdbConnectionString, '$MDB_CONNECTION_STRING'); }; const openMongoDBShell = (connectionController: ConnectionController): Promise => { - let mdbSslOptions: string[] = []; - if ( !connectionController.isCurrentlyConnected() ) { @@ -119,23 +77,28 @@ const openMongoDBShell = (connectionController: ConnectionController): Promise { - if (!ConnectionModel.isURI(uri)) { - return 'MongoDB connection strings begin with "mongodb://" or "mongodb+srv://"'; + try { + // eslint-disable-next-line no-new + new ConnectionString(uri); + } catch (err) { + return (err as { message: string }).message; } return null; @@ -274,7 +278,9 @@ export default class ConnectionController { const connectionStringData = new ConnectionString(connectionString); - connectionStringData.searchParams.set('appname', `${name} ${version}`); + // TODO: Allow overriding appname + use driverInfo instead + // (https://jira.mongodb.org/browse/MONGOSH-1015) + connectionStringData.searchParams.set('appname', `${packageJSON.name} ${packageJSON.version}`); try { const connectResult = await this.saveNewConnectionAndConnect( @@ -298,14 +304,15 @@ export default class ConnectionController { parseNewConnection(rawConnectionModel: RawConnectionModel): ConnectionInfo { const connectionModel = new ConnectionModel(rawConnectionModel); + const connectionInfo = convertConnectionModelToInfo(connectionModel); - return convertConnectionModelToInfo(connectionModel); + return connectionInfo; } private async _saveSecretsToKeychain( { connectionId, secrets }: ConnectionSecretsInfo ): Promise { - if (!ext.keytarModule || !Object.keys(secrets).length) { + if (!ext.keytarModule) { return; } @@ -327,7 +334,7 @@ export default class ConnectionController { ); const savedConnectionInfo = await this._storageController.saveConnection({ ...newStoreConnectionInfoWithSecrets, - connectionOptions: safeConnectionInfo.connectionOptions // The connection info without secrtes. + connectionOptions: safeConnectionInfo.connectionOptions // The connection info without secrets. }); await this._saveSecretsToKeychain({ @@ -356,7 +363,7 @@ export default class ConnectionController { this._connections[savedConnectionInfo.id] = { ...savedConnectionInfo, - connectionOptions: originalConnectionInfo.connectionOptions // The connection options with secrtes. + connectionOptions: originalConnectionInfo.connectionOptions // The connection options with secrets. }; log.info(`Connect called to connect to instance: ${savedConnectionInfo.name}`); @@ -387,10 +394,6 @@ export default class ConnectionController { const connectionOptions = this._connections[connectionId].connectionOptions; - if (!connectionOptions) { - throw new Error('Connection not found.'); - } - let newDataService; let connectError; @@ -705,8 +708,8 @@ export default class ConnectionController { return this._activeDataService; } - getMongoClientConnectionOptions(): ConnectionModel { - return this._activeDataService?.getMongoClientConnectionOptions() || {}; + getMongoClientConnectionOptions(): { url: string; options: MongoClientOptions; } | undefined { + return this._activeDataService?.getMongoClientConnectionOptions(); } diff --git a/src/test/suite/commands/launchMongoShell.test.ts b/src/test/suite/commands/launchMongoShell.test.ts index e24acb328..890e1e9c3 100644 --- a/src/test/suite/commands/launchMongoShell.test.ts +++ b/src/test/suite/commands/launchMongoShell.test.ts @@ -14,7 +14,7 @@ suite('Commands Test Suite', () => { const sandbox = sinon.createSandbox(); let fakeShowErrorMessage: any; - let fakeGetActiveDerivedConnectionModel: any; + let fakeGetMongoClientConnectionOptions: any; let fakeIsCurrentlyConnected: any; let createTerminalStub: any; let fakeSendTerminalText: any; @@ -23,9 +23,9 @@ suite('Commands Test Suite', () => { sandbox.stub(vscode.window, 'showInformationMessage'); fakeShowErrorMessage = sandbox.stub(vscode.window, 'showErrorMessage'); - fakeGetActiveDerivedConnectionModel = sandbox.stub( + fakeGetMongoClientConnectionOptions = sandbox.stub( mockConnectionController, - 'getActiveDerivedConnectionModel' + 'getMongoClientConnectionOptions' ); fakeIsCurrentlyConnected = sandbox.stub( @@ -71,16 +71,9 @@ suite('Commands Test Suite', () => { test('openMongoDBShell should open a terminal with the active connection driver url', async () => { const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: 'localhost:27018', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverUrlWithSsh: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverOptions: { readPreference: 'primary' }, - sshTunnelOptions: {}, - username: '', - title: 'localhost:27018' + fakeGetMongoClientConnectionOptions.returns({ + url: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + options: {} }); fakeIsCurrentlyConnected.returns(true); @@ -100,126 +93,6 @@ suite('Commands Test Suite', () => { assert.strictEqual(shellCommandText, 'mongosh $MDB_CONNECTION_STRING;'); }); - - test('openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: '127.0.0.1:27017', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', - driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', - driverUrlWithSsh: 'mongodb://127.0.0.1:29309/?readPreference=primary&ssl=false', - driverOptions: { readPreference: 'primary' }, - sshTunnelOptions: { - readyTimeout: 20000, - forwardTimeout: 20000, - keepaliveInterval: 20000, - srcAddr: '127.0.0.1', - dstPort: 27017, - dstAddr: '127.0.0.1', - localPort: 29309, - localAddr: '127.0.0.1', - host: 'my.ssh-server.com', - port: 22, - username: 'my-user', - password: 'password' - }, - username: '', - title: '127.0.0.1:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const connectionString: string = - createTerminalStub.firstCall.args[0].env?.MDB_CONNECTION_STRING; - - assert(connectionString.includes('mongodb://127.0.0.1')); - assert(!connectionString.includes('27017')); - assert(connectionString.includes('?readPreference=primary&ssl=false')); - - const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - - assert.strictEqual(shellCommandText, 'mongosh $MDB_CONNECTION_STRING;'); - }); - - test('openMongoDBShell should open a terminal with ssl config injected', async () => { - const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: '127.0.0.1:27017', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverUrlWithSsh: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverOptions: { - sslValidate: true, - sslCA: ['./path_to_some_file'], - readPreference: 'primary' - }, - sshTunnelOptions: {}, - username: '', - title: '127.0.0.1:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const terminalOptions: vscode.TerminalOptions = - createTerminalStub.firstCall.args[0]; - - assert( - terminalOptions.env?.MDB_CONNECTION_STRING === driverUri, - `Expected open terminal to set shell arg as driver url "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` - ); - - const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - - assert.strictEqual(shellCommandText, 'mongosh --tls --tlsAllowInvalidHostnames --tlsCAFile="./path_to_some_file" $MDB_CONNECTION_STRING;'); - }); - - test('openMongoDBShell should open a terminal with x509 config injected', async () => { - const driverUri = 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external'; - - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: 'localhost:27017', - driverAuthMechanism: 'MONGODB-X509', - safeUrl: 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external', - driverUrl: 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external', - driverUrlWithSsh: 'mongodb://testing@localhost:27017/?authMechanism=MONGODB-X509&readPreference=primary&ssl=true&authSource=%24external', - driverOptions: { - sslValidate: false, - sslCA: ['./path/to/ca'], - sslKey: './path/to/key', - sslCert: './path/to/cert', - tlsAllowInvalidHostnames: true, - readPreference: 'primary' - }, - sshTunnelOptions: {}, - username: 'testing', - title: 'localhost:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const terminalOptions: vscode.TerminalOptions = - createTerminalStub.firstCall.args[0]; - - assert.strictEqual(terminalOptions.env?.MDB_CONNECTION_STRING, driverUri); - - const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - - assert.strictEqual(shellCommandText, 'mongosh --tls --tlsAllowInvalidHostnames --tlsCAFile="./path/to/ca" --tlsCertificateKeyFile="./path/to/cert" $MDB_CONNECTION_STRING;'); - }); }); suite('Windows powershell env shell', () => { @@ -230,16 +103,9 @@ suite('Commands Test Suite', () => { test('powershell openMongoDBShell should open a terminal with the active connection driver url', async () => { const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: 'localhost:27018', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverUrlWithSsh: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverOptions: { readPreference: 'primary' }, - sshTunnelOptions: {}, - username: '', - title: 'localhost:27018' + fakeGetMongoClientConnectionOptions.returns({ + url: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + options: {}, }); fakeIsCurrentlyConnected.returns(true); @@ -262,101 +128,6 @@ suite('Commands Test Suite', () => { `Expected sendText to terminal (${shellCommandText}) to use powershell env variable syntax` ); }); - - test('powershell openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: '127.0.0.1:27017', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', - driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', - driverUrlWithSsh: 'mongodb://127.0.0.1:29500/?readPreference=primary&ssl=false', - driverOptions: { readPreference: 'primary' }, - sshTunnelOptions: { - readyTimeout: 20000, - forwardTimeout: 20000, - keepaliveInterval: 20000, - srcAddr: '127.0.0.1', - dstPort: 27017, - dstAddr: '127.0.0.1', - localPort: 29500, - localAddr: '127.0.0.1', - host: 'my.ssh-server.com', - port: 22, - username: 'my-user', - password: 'password' - }, - username: '', - title: '127.0.0.1:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const terminalOptions: vscode.TerminalOptions = - createTerminalStub.firstCall.args[0]; - - assert( - terminalOptions.env?.MDB_CONNECTION_STRING?.includes( - 'mongodb://127.0.0.1' - ) && - terminalOptions.env?.MDB_CONNECTION_STRING.includes( - '/?readPreference=primary&ssl=false' - ), - `Expected open terminal to set shell arg as driver url with ssh tunnel port injected found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` - ); - }); - - test('powershell openMongoDBShell should open a terminal with ssl config injected', async () => { - const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: '127.0.0.1:27017', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverUrlWithSsh: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverOptions: { - sslValidate: true, - sslCA: ['./path_to_some_file'], - readPreference: 'primary' - }, - sshTunnelOptions: {}, - username: '', - title: '127.0.0.1:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const terminalOptions: vscode.TerminalOptions = - createTerminalStub.firstCall.args[0]; - - assert( - terminalOptions.env?.MDB_CONNECTION_STRING === driverUri, - `Expected open terminal to set shell arg as driver url with ssl injected "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` - ); - - const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - - assert( - shellCommandText.includes('--tls'), - `Expected open terminal to have tls arg "--tls" found "${shellCommandText}"` - ); - assert( - shellCommandText.includes('--tlsAllowInvalidHostnames'), - `Expected open terminal to have tls arg "--tlsAllowInvalidHostnames" found "${shellCommandText}"` - ); - assert( - shellCommandText.includes('--tlsCAFile="./path_to_some_file"'), - `Expected open terminal to have tlsCAFile arg "--tlsCAFile="./path_to_some_file"" found "${shellCommandText}"` - ); - }); }); suite('Windows cmd env shell', () => { @@ -367,16 +138,9 @@ suite('Commands Test Suite', () => { test('windows cmd openMongoDBShell should open a terminal with the active connection driver url', async () => { const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: 'localhost:27018', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverUrl: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverUrlWithSsh: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', - driverOptions: { readPreference: 'primary' }, - sshTunnelOptions: {}, - username: '', - title: 'localhost:27018' + fakeGetMongoClientConnectionOptions.returns({ + url: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', + options: {} }); fakeIsCurrentlyConnected.returns(true); @@ -393,100 +157,5 @@ suite('Commands Test Suite', () => { `Expected open terminal to set shell arg as driver url "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` ); }); - - test('windows cmd openMongoDBShell should open a terminal with ssh tunnel port injected', async () => { - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: '127.0.0.1:27017', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', - driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=false', - driverUrlWithSsh: 'mongodb://127.0.0.1:29278/?readPreference=primary&ssl=false', - driverOptions: { readPreference: 'primary' }, - sshTunnelOptions: { - readyTimeout: 20000, - forwardTimeout: 20000, - keepaliveInterval: 20000, - srcAddr: '127.0.0.1', - dstPort: 27017, - dstAddr: '127.0.0.1', - localPort: 29278, - localAddr: '127.0.0.1', - host: 'my.ssh-server.com', - port: 22, - username: 'my-user', - password: 'password' - }, - username: '', - title: '127.0.0.1:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const terminalOptions: vscode.TerminalOptions = - createTerminalStub.firstCall.args[0]; - - assert( - terminalOptions.env?.MDB_CONNECTION_STRING?.includes( - 'mongodb://127.0.0.1' - ) && - terminalOptions.env?.MDB_CONNECTION_STRING.includes( - '/?readPreference=primary&ssl=false' - ), - `Expected open terminal to set shell arg as driver url with ssh tunnel port injected found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` - ); - }); - - test('windows cmd openMongoDBShell should open a terminal with ssl config injected', async () => { - const driverUri = 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true'; - - fakeGetActiveDerivedConnectionModel.returns({ - instanceId: '127.0.0.1:27017', - driverAuthMechanism: undefined, - safeUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverUrl: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverUrlWithSsh: 'mongodb://127.0.0.1:27017/?readPreference=primary&ssl=true', - driverOptions: { - sslValidate: true, - sslCA: ['./path_to_some_file'], - readPreference: 'primary' - }, - sshTunnelOptions: {}, - username: '', - title: '127.0.0.1:27017' - }); - - fakeIsCurrentlyConnected.returns(true); - - await launchMongoShell(mockConnectionController); - - assert(createTerminalStub.called); - - const terminalOptions: vscode.TerminalOptions = - createTerminalStub.firstCall.args[0]; - - assert( - terminalOptions.env?.MDB_CONNECTION_STRING === driverUri, - `Expected open terminal to set shell arg as driver url with ssl injected "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` - ); - - const shellCommandText = fakeSendTerminalText.firstCall.args[0]; - - assert( - shellCommandText.includes('--tls'), - `Expected open terminal to have tls arg "--tls" found "${shellCommandText}"` - ); - assert( - shellCommandText.includes('--tlsAllowInvalidHostnames'), - `Expected open terminal to have tls arg "--tlsAllowInvalidHostnames" found "${shellCommandText}"` - ); - assert( - shellCommandText.includes('--tlsCAFile="./path_to_some_file"'), - `Expected open terminal to have tlsCAFile arg "--tlsCAFile="./path_to_some_file"" found "${shellCommandText}"` - ); - }); }); }); diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 4b1d303ed..1b45ad912 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -140,7 +140,8 @@ suite('Playground Controller Test Suite', function () { testPlaygroundController._connectionController, 'getMongoClientConnectionOptions', () => ({ - url: 'mongodb://localhost' + url: 'mongodb://localhost', + options: {} }) ); diff --git a/src/test/suite/views/webviewController.test.ts b/src/test/suite/views/webviewController.test.ts index c3fc5eb59..fd9052d81 100644 --- a/src/test/suite/views/webviewController.test.ts +++ b/src/test/suite/views/webviewController.test.ts @@ -3,7 +3,6 @@ import * as sinon from 'sinon'; import * as vscode from 'vscode'; import assert from 'assert'; import { beforeEach, afterEach } from 'mocha'; -import ConnectionModel from 'mongodb-connection-model'; import ConnectionController from '../../../connectionController'; import { mdbTestExtension } from '../stubbableMdbExtension'; @@ -134,6 +133,7 @@ suite('Webview Test Suite', () => { }, asWebviewUri: sinon.fake.returns('') }; + const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ webview: fakeWebview }); @@ -157,20 +157,14 @@ suite('Webview Test Suite', () => { 'Ensure it starts listening for messages from the webview.' ); - ConnectionModel.from(TEST_DATABASE_URI, (err, connectionModel) => { - if (err) { - assert(false); + // Mock a connection call. + messageReceived({ + command: MESSAGE_TYPES.CONNECT, + connectionModel: { + port: 27018, + hostname: 'localhost', + hosts: [{ host: 'localhost', port: 27018 }] } - - // Mock a connection call. - messageReceived({ - command: MESSAGE_TYPES.CONNECT, - connectionModel: { - port: connectionModel.port, - hostname: connectionModel.hostname, - hosts: connectionModel.hosts - } - }); }); }); @@ -230,20 +224,14 @@ suite('Webview Test Suite', () => { 'Ensure it starts listening for messages from the webview.' ); - ConnectionModel.from(TEST_DATABASE_URI, (err, connectionModel) => { - if (err) { - assert(false); + // Mock a connection call. + messageReceived({ + command: MESSAGE_TYPES.CONNECT, + connectionModel: { + port: 27018, + hostname: 'localhost', + hosts: [{ host: 'localhost', port: 27018 }] } - - // Mock a connection call. - messageReceived({ - command: MESSAGE_TYPES.CONNECT, - connectionModel: { - port: connectionModel.port, - hostname: connectionModel.hostname, - hosts: connectionModel.hosts - } - }); }); }); diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index 88fc91fb2..d12cdb856 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -84,7 +84,6 @@ export default class WebviewController { ): Promise => { try { const connectionModel = this._connectionController.parseNewConnection(rawConnectionModel); - const { successfullyConnected, connectionErrorMessage From e2b1f36647b00e0a2fb5046d9e0bb8a2f11fdc75 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 20 Dec 2021 14:43:25 +0100 Subject: [PATCH 09/15] refactor: adress pr comments --- src/connectionController.ts | 24 +++++++----------- src/explorer/collectionTreeItem.ts | 2 +- src/explorer/databaseTreeItem.ts | 2 +- src/explorer/helpTree.ts | 2 +- src/explorer/indexListTreeItem.ts | 2 +- src/explorer/indexTreeItem.ts | 2 +- src/explorer/playgroundsTree.ts | 2 +- src/explorer/playgroundsTreeHeader.ts | 2 +- src/explorer/schemaTreeItem.ts | 2 +- src/language/languageServerController.ts | 2 +- src/language/mongoDBService.ts | 2 +- src/language/worker.ts | 6 ++--- .../suite/commands/launchMongoShell.test.ts | 18 ++++++------- src/test/suite/connectionController.test.ts | 25 ++++++++++--------- 14 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index d10657d41..ce48111d9 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -1,6 +1,5 @@ import * as vscode from 'vscode'; import { - connect, convertConnectionModelToInfo, convertConnectionInfoToModel, ConnectionInfo, @@ -11,7 +10,7 @@ import { import ConnectionModel from 'mongodb-connection-model'; import ConnectionString from 'mongodb-connection-string-url'; import { EventEmitter } from 'events'; -import { MongoClientOptions } from 'mongodb'; +import type { MongoClientOptions } from 'mongodb'; import { v4 as uuidv4 } from 'uuid'; import { CONNECTION_STATUS } from './views/webview-app/extension-app-message-constants'; @@ -304,6 +303,10 @@ export default class ConnectionController { parseNewConnection(rawConnectionModel: RawConnectionModel): ConnectionInfo { const connectionModel = new ConnectionModel(rawConnectionModel); + + // Override the default connection `appname`. + connectionModel.appname = `${packageJSON.name} ${packageJSON.version}`; + const connectionInfo = convertConnectionModelToInfo(connectionModel); return connectionInfo; @@ -370,10 +373,6 @@ export default class ConnectionController { return this._connect(savedConnectionInfo.id, connectionType); } - async getDataServiceAndConnect(connectionOptions: ConnectionOptions) { - return connect(connectionOptions); - } - private async _connect( connectionId: string, connectionType: ConnectionTypes @@ -393,12 +392,11 @@ export default class ConnectionController { this._statusView.showMessage('Connecting to MongoDB...'); const connectionOptions = this._connections[connectionId].connectionOptions; - - let newDataService; + const newDataService = new DataService(connectionOptions); let connectError; try { - newDataService = await this.getDataServiceAndConnect(connectionOptions); + await newDataService.connect(); } catch (error) { connectError = error; } @@ -454,11 +452,7 @@ export default class ConnectionController { // If the current attempt is no longer the most recent attempt // or the connection no longer exists we silently end the connection // and return. - try { - void newDataService.disconnect(); - } catch (e) { - /* */ - } + void newDataService.disconnect().catch(() => { /* ignore */ }); return true; } @@ -476,7 +470,7 @@ export default class ConnectionController { return true; } catch (error) { - throw new Error(`Unable to connection: ${error}`); + throw new Error(`Unable to connect: ${error}`); } } diff --git a/src/explorer/collectionTreeItem.ts b/src/explorer/collectionTreeItem.ts index 7866a8b4a..67baf2526 100644 --- a/src/explorer/collectionTreeItem.ts +++ b/src/explorer/collectionTreeItem.ts @@ -58,7 +58,7 @@ function isChildCacheOutOfSync( export default class CollectionTreeItem extends vscode.TreeItem implements TreeItemParent, vscode.TreeDataProvider { - contextValue = 'collectionTreeItem'; + contextValue = 'collectionTreeItem' as const; private _documentListChild: DocumentListTreeItem; private _schemaChild: SchemaTreeItem; diff --git a/src/explorer/databaseTreeItem.ts b/src/explorer/databaseTreeItem.ts index 2e3df8423..89fdded48 100644 --- a/src/explorer/databaseTreeItem.ts +++ b/src/explorer/databaseTreeItem.ts @@ -18,7 +18,7 @@ function getIconPath(): { light: string; dark: string } { export default class DatabaseTreeItem extends vscode.TreeItem implements TreeItemParent, vscode.TreeDataProvider { - contextValue = 'databaseTreeItem'; + contextValue = 'databaseTreeItem' as const; cacheIsUpToDate: boolean; private _childrenCache: { [collectionName: string]: CollectionTreeItem }; diff --git a/src/explorer/helpTree.ts b/src/explorer/helpTree.ts index c23fbd243..7f3579e22 100644 --- a/src/explorer/helpTree.ts +++ b/src/explorer/helpTree.ts @@ -42,7 +42,7 @@ export class HelpLinkTreeItem extends vscode.TreeItem { } export default class HelpTree implements vscode.TreeDataProvider { - contextValue = 'helpTree'; + contextValue = 'helpTree' as const; _telemetryService?: TelemetryService; getTreeItem(element: vscode.TreeItem): vscode.TreeItem { diff --git a/src/explorer/indexListTreeItem.ts b/src/explorer/indexListTreeItem.ts index 26b4d7fad..6152e1018 100644 --- a/src/explorer/indexListTreeItem.ts +++ b/src/explorer/indexListTreeItem.ts @@ -27,7 +27,7 @@ export default class IndexListTreeItem extends vscode.TreeItem isExpanded: boolean; cacheIsUpToDate = false; - contextValue = 'indexListTreeItem'; + contextValue = 'indexListTreeItem' as const; private _namespace: string; private _dataService: DataService; diff --git a/src/explorer/indexTreeItem.ts b/src/explorer/indexTreeItem.ts index fa939fdc4..d8fcaa76d 100644 --- a/src/explorer/indexTreeItem.ts +++ b/src/explorer/indexTreeItem.ts @@ -89,7 +89,7 @@ export class IndexFieldTreeItem extends vscode.TreeItem export default class IndexTreeItem extends vscode.TreeItem implements vscode.TreeDataProvider, TreeItemParent { - contextValue = 'indexTreeItem'; + contextValue = 'indexTreeItem' as const; index: IndexModel; diff --git a/src/explorer/playgroundsTree.ts b/src/explorer/playgroundsTree.ts index cc08f0fb3..d7fecd9be 100644 --- a/src/explorer/playgroundsTree.ts +++ b/src/explorer/playgroundsTree.ts @@ -60,7 +60,7 @@ implements vscode.TreeDataProvider { private _playgroundsTreeItems: { [key: string]: PlaygroundsTreeItem }; readonly onDidChangeTreeData: vscode.Event; - contextValue = 'playgroundsTree'; + contextValue = 'playgroundsTree' as const; constructor() { this.excludeFromPlaygroundsSearch = diff --git a/src/explorer/playgroundsTreeHeader.ts b/src/explorer/playgroundsTreeHeader.ts index 5eb3f3a4b..c6f303ffd 100644 --- a/src/explorer/playgroundsTreeHeader.ts +++ b/src/explorer/playgroundsTreeHeader.ts @@ -7,7 +7,7 @@ export default class PlaygroundsTreeHeader extends vscode.TreeItem implements TreeItemParent, vscode.TreeDataProvider { private _playgroundsTreeItems: { [key: string]: PlaygroundsTreeItem }; - contextValue = 'playgroundsTreeHeader'; + contextValue = 'playgroundsTreeHeader' as const; isExpanded = true; doesNotRequireTreeUpdate = true; cacheIsUpToDate = true; diff --git a/src/explorer/schemaTreeItem.ts b/src/explorer/schemaTreeItem.ts index 6e83aae18..a927a6478 100644 --- a/src/explorer/schemaTreeItem.ts +++ b/src/explorer/schemaTreeItem.ts @@ -44,7 +44,7 @@ export default class SchemaTreeItem extends vscode.TreeItem cacheIsUpToDate: boolean; childrenCache: { [fieldName: string]: FieldTreeItem }; - contextValue = 'schemaTreeItem'; + contextValue = 'schemaTreeItem' as const; collectionName: string; databaseName: string; diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index afb683271..8a243b050 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; -import { MongoClientOptions } from 'mongodb'; +import type { MongoClientOptions } from 'mongodb'; import { LanguageClient, LanguageClientOptions, diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 2895a51e4..5a07ffee7 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -8,7 +8,7 @@ import { MarkupContent, MarkupKind } from 'vscode-languageserver/node'; -import { MongoClientOptions } from 'mongodb'; +import type { MongoClientOptions } from 'mongodb'; import path from 'path'; import { signatures } from '@mongosh/shell-api'; import translator from '@mongosh/i18n'; diff --git a/src/language/worker.ts b/src/language/worker.ts index 62972af6d..949ed2794 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -1,7 +1,5 @@ -import { - CliServiceProvider, - MongoClientOptions -} from '@mongosh/service-provider-server'; +import { CliServiceProvider } from '@mongosh/service-provider-server'; +import type { MongoClientOptions } from '@mongosh/service-provider-server'; import { CompletionItemKind } from 'vscode-languageserver/node'; import { EJSON, Document } from 'bson'; import { ElectronRuntime } from '@mongosh/browser-runtime-electron'; diff --git a/src/test/suite/commands/launchMongoShell.test.ts b/src/test/suite/commands/launchMongoShell.test.ts index 890e1e9c3..99368f751 100644 --- a/src/test/suite/commands/launchMongoShell.test.ts +++ b/src/test/suite/commands/launchMongoShell.test.ts @@ -69,7 +69,7 @@ suite('Commands Test Suite', () => { }); test('openMongoDBShell should open a terminal with the active connection driver url', async () => { - const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + const expectedDriverUrl = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; fakeGetMongoClientConnectionOptions.returns({ url: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', @@ -85,8 +85,8 @@ suite('Commands Test Suite', () => { createTerminalStub.firstCall.args[0]; assert( - terminalOptions.env?.MDB_CONNECTION_STRING === driverUri, - `Expected open terminal to set shell arg as driver url "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` + terminalOptions.env?.MDB_CONNECTION_STRING === expectedDriverUrl, + `Expected open terminal to set shell arg as driver url "${expectedDriverUrl}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` ); const shellCommandText = fakeSendTerminalText.firstCall.args[0]; @@ -101,7 +101,7 @@ suite('Commands Test Suite', () => { }); test('powershell openMongoDBShell should open a terminal with the active connection driver url', async () => { - const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + const expectedDriverUrl = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; fakeGetMongoClientConnectionOptions.returns({ url: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', @@ -118,8 +118,8 @@ suite('Commands Test Suite', () => { createTerminalStub.firstCall.args[0]; assert( - terminalOptions.env?.MDB_CONNECTION_STRING === driverUri, - `Expected open terminal to set shell arg as driver url "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` + terminalOptions.env?.MDB_CONNECTION_STRING === expectedDriverUrl, + `Expected open terminal to set shell arg as driver url "${expectedDriverUrl}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` ); const shellCommandText = fakeSendTerminalText.firstCall.args[0]; @@ -136,7 +136,7 @@ suite('Commands Test Suite', () => { }); test('windows cmd openMongoDBShell should open a terminal with the active connection driver url', async () => { - const driverUri = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; + const expectedDriverUrl = 'mongodb://localhost:27018/?readPreference=primary&ssl=false'; fakeGetMongoClientConnectionOptions.returns({ url: 'mongodb://localhost:27018/?readPreference=primary&ssl=false', @@ -153,8 +153,8 @@ suite('Commands Test Suite', () => { createTerminalStub.firstCall.args[0]; assert( - terminalOptions.env?.MDB_CONNECTION_STRING === driverUri, - `Expected open terminal to set shell arg as driver url "${driverUri}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` + terminalOptions.env?.MDB_CONNECTION_STRING === expectedDriverUrl, + `Expected open terminal to set shell arg as driver url "${expectedDriverUrl}" found "${terminalOptions.env?.MDB_CONNECTION_STRING}"` ); }); }); diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index 70d172541..c7f2d03aa 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -2,7 +2,7 @@ import * as sinon from 'sinon'; import * as vscode from 'vscode'; import { afterEach, beforeEach } from 'mocha'; import assert from 'assert'; -import { connect } from 'mongodb-data-service'; +import { DataService } from 'mongodb-data-service'; import { promisify } from 'util'; import ConnectionController, { @@ -270,7 +270,7 @@ suite('Connection Controller Test Suite', function () { }); test('the connection model loads both global and workspace stored connection models', async () => { - const expectedDriverUri = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; + const expectedDriverUrl = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; await vscode.workspace .getConfiguration('mdb.connectionSaving') @@ -311,8 +311,8 @@ suite('Connection Controller Test Suite', function () { ); assert( connections[Object.keys(connections)[2]].connectionOptions?.connectionString === - expectedDriverUri, - `Expected loaded connection to include driver url '${expectedDriverUri}' found '${ + expectedDriverUrl, + `Expected loaded connection to include driver url '${expectedDriverUrl}' found '${ connections[Object.keys(connections)[2]].connectionOptions?.connectionString }'` ); @@ -476,7 +476,7 @@ suite('Connection Controller Test Suite', function () { }); test('"getConnectionStringByConnectionId" returns the driver uri of a connection', async () => { - const expectedDriverUri = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; + const expectedDriverUrl = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; await testConnectionController.loadSavedConnections(); await testConnectionController.addNewConnectionStringAndConnect( @@ -490,13 +490,13 @@ suite('Connection Controller Test Suite', function () { 'Expected active connection to not be null' ); - const testDriverUri = testConnectionController.getConnectionStringByConnectionId( + const testDriverUrl = testConnectionController.getConnectionStringByConnectionId( activeConnectionId || '' ); assert( - testDriverUri === expectedDriverUri, - `Expected to be returned the driver uri "${expectedDriverUri}" found ${testDriverUri}` + testDriverUrl === expectedDriverUrl, + `Expected to be returned the driver uri "${expectedDriverUrl}" found ${testDriverUrl}` ); }); @@ -889,11 +889,12 @@ suite('Connection Controller Test Suite', function () { }; sinon.replace( - testConnectionController, - 'getDataServiceAndConnect', - sinon.fake(async (connectionOptions) => { + DataService.prototype, + 'connect', + sinon.fake(async (callback) => { await sleep(50); - return await connect(connectionOptions); + + return callback(null, DataService); }) ); From ad3bc19e2b702f013f7d27b2555861c46eefd4b2 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 21 Dec 2021 01:10:40 +0100 Subject: [PATCH 10/15] refactor: code style cleaning up --- src/connectionController.ts | 44 +++++------ src/storage/storageController.ts | 77 +++++++------------ src/telemetry/connectionTelemetry.ts | 2 +- .../suite/storage/storageController.test.ts | 4 +- src/views/webviewController.ts | 4 +- 5 files changed, 49 insertions(+), 82 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index ce48111d9..6ae3f8bfe 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -1,7 +1,6 @@ import * as vscode from 'vscode'; import { convertConnectionModelToInfo, - convertConnectionInfoToModel, ConnectionInfo, ConnectionOptions, DataService, @@ -73,7 +72,7 @@ export default class ConnectionController { // This is a map of connection ids to their configurations. // These connections can be saved on the session (runtime), // on the workspace, or globally in vscode. - _connections: { [key: string]: StoreConnectionInfo } = {}; + _connections: { [connectionId: string]: StoreConnectionInfo } = {}; _activeDataService: DataService| null = null; _activeConnectionModel: ConnectionModel | null = null; @@ -177,7 +176,7 @@ export default class ConnectionController { // Here we're lenient when loading connections in case their // connections have become corrupted. const printableError = error as { message: string }; - log.error(`Mergin connection with secrets failed: ${printableError.message}`); + log.error(`Merging connection with secrets failed: ${printableError.message}`); return; } } @@ -211,19 +210,21 @@ export default class ConnectionController { } async loadSavedConnections(): Promise { - // Try to pull in the connections previously saved in the global storage of vscode. - const existingGlobalConnections = this._storageController.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS, - StorageLocation.GLOBAL - ); - await this._loadSavedConnectionsByStore(existingGlobalConnections); - - // Try to pull in the connections previously saved in the workspace storage of vscode. - const existingWorkspaceConnections = this._storageController.get( - StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageLocation.WORKSPACE - ); - await this._loadSavedConnectionsByStore(existingWorkspaceConnections); + await Promise.all([(async() => { + // Try to pull in the connections previously saved in the global storage of vscode. + const existingGlobalConnections = this._storageController.get( + StorageVariables.GLOBAL_SAVED_CONNECTIONS, + StorageLocation.GLOBAL + ); + await this._loadSavedConnectionsByStore(existingGlobalConnections); + })(), (async() => { + // Try to pull in the connections previously saved in the workspace storage of vscode. + const existingWorkspaceConnections = this._storageController.get( + StorageVariables.WORKSPACE_SAVED_CONNECTIONS, + StorageLocation.WORKSPACE + ); + await this._loadSavedConnectionsByStore(existingWorkspaceConnections); + })()]); } async connectWithURI(): Promise { @@ -422,7 +423,6 @@ export default class ConnectionController { void vscode.window.showInformationMessage('MongoDB connection successful.'); this._activeDataService = newDataService; - this._activeConnectionModel = await convertConnectionInfoToModel({ connectionOptions }); this._currentConnectionId = connectionId; this._connecting = false; this._connectingConnectionId = null; @@ -442,7 +442,7 @@ export default class ConnectionController { connectionId: string, connectingAttemptVersion: null | string, newDataService: DataService - }): Boolean { + }): boolean { const { connectionId, connectingAttemptVersion, newDataService } = attempt; if ( @@ -500,9 +500,7 @@ export default class ConnectionController { // Disconnect from the active connection. await this._activeDataService.disconnect(); void vscode.window.showInformationMessage('MongoDB disconnected.'); - this._activeDataService = null; - this._activeConnectionModel = null; } catch (error) { // Show an error, however we still reset the active connection to free up the extension. void vscode.window.showErrorMessage( @@ -706,11 +704,6 @@ export default class ConnectionController { return this._activeDataService?.getMongoClientConnectionOptions(); } - - getActiveDerivedConnectionModel(): ConnectionModel { - return this._activeConnectionModel?.getAttributes({ derived: true }) || {}; - } - getConnectionStringByConnectionId(connectionId: string): string { return this._connections[connectionId].connectionOptions.connectionString; } @@ -754,7 +747,6 @@ export default class ConnectionController { clearAllConnections(): void { this._connections = {}; this._activeDataService = null; - this._activeConnectionModel = null; this._currentConnectionId = null; this._connecting = false; this._disconnecting = false; diff --git a/src/storage/storageController.ts b/src/storage/storageController.ts index 037ebbdbc..99a0468d4 100644 --- a/src/storage/storageController.ts +++ b/src/storage/storageController.ts @@ -27,20 +27,17 @@ export enum DefaultSavingLocations { } export type ConnectionsFromStorage = { - [key: string]: StoreConnectionInfo + [connectionId: string]: StoreConnectionInfo }; -type StoredVariableName = StorageVariables.GLOBAL_USER_ID | - StorageVariables.GLOBAL_SAVED_CONNECTIONS | - StorageVariables.WORKSPACE_SAVED_CONNECTIONS | - StorageVariables.GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW; - -type StoredItem = - T extends StorageVariables.GLOBAL_USER_ID ? string : - T extends StorageVariables.GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW ? boolean : - T extends StorageVariables.GLOBAL_SAVED_CONNECTIONS ? ConnectionsFromStorage : - T extends StorageVariables.WORKSPACE_SAVED_CONNECTIONS ? ConnectionsFromStorage : - never; +interface StorageVariableContents { + [StorageVariables.GLOBAL_USER_ID]: string; + [StorageVariables.GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW]: boolean; + [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: ConnectionsFromStorage; + [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: ConnectionsFromStorage; +} +type StoredVariableName = keyof StorageVariableContents; +type StoredItem = StorageVariableContents[T]; export default class StorageController { _storage: { [StorageLocation.GLOBAL]: vscode.Memento, [StorageLocation.WORKSPACE]: vscode.Memento }; @@ -57,9 +54,9 @@ export default class StorageController { } // Update something in the storage. Defaults to global storage (not workspace). - update( - variableName: StorageVariables, - value: boolean | string | ConnectionsFromStorage, + update( + variableName: T, + value: StoredItem, storageLocation: StorageLocation = StorageLocation.GLOBAL ): Thenable { this._storage[storageLocation].update(variableName, value); @@ -79,46 +76,26 @@ export default class StorageController { return globalUserId; } - async saveConnectionToGlobalStore(storeConnectionInfo: StoreConnectionInfo): Promise { - // Get the current save connections. - let globalConnections = this.get( - StorageVariables.GLOBAL_SAVED_CONNECTIONS, - StorageLocation.GLOBAL - ); - - if (!globalConnections) { - globalConnections = {}; - } - - // Add the new connection. - globalConnections[storeConnectionInfo.id] = storeConnectionInfo; - - // Update the store. - return this.update( - StorageVariables.GLOBAL_SAVED_CONNECTIONS, - globalConnections - ); - } + async saveConnectionToStore(storeConnectionInfo: StoreConnectionInfo): Promise { + const variableName = (storeConnectionInfo.storageLocation === StorageLocation.GLOBAL) + ? StorageVariables.GLOBAL_SAVED_CONNECTIONS + : StorageVariables.WORKSPACE_SAVED_CONNECTIONS; - async saveConnectionToWorkspaceStore(storeConnectionInfo: StoreConnectionInfo): Promise { - // Get the current save connections. - let workspaceConnections = this.get( - StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - StorageLocation.WORKSPACE - ); + // Get the current saved connections. + let savedConnections = this.get(variableName, storeConnectionInfo.storageLocation); - if (!workspaceConnections) { - workspaceConnections = {}; + if (!savedConnections) { + savedConnections = {}; } // Add the new connection. - workspaceConnections[storeConnectionInfo.id] = storeConnectionInfo; + savedConnections[storeConnectionInfo.id] = storeConnectionInfo; // Update the store. return this.update( - StorageVariables.WORKSPACE_SAVED_CONNECTIONS, - workspaceConnections, - StorageLocation.WORKSPACE + variableName, + savedConnections, + storeConnectionInfo.storageLocation ); } @@ -135,10 +112,8 @@ export default class StorageController { storeConnectionInfo.storageLocation = await this.getStorageLocationFromPrompt(); } - if (storeConnectionInfo.storageLocation === StorageLocation.WORKSPACE) { - await this.saveConnectionToWorkspaceStore(storeConnectionInfo); - } else if (storeConnectionInfo.storageLocation === StorageLocation.GLOBAL) { - await this.saveConnectionToGlobalStore(storeConnectionInfo); + if ([StorageLocation.GLOBAL, StorageLocation.WORKSPACE].includes(storeConnectionInfo.storageLocation)) { + await this.saveConnectionToStore(storeConnectionInfo); } return storeConnectionInfo; diff --git a/src/telemetry/connectionTelemetry.ts b/src/telemetry/connectionTelemetry.ts index 1c87070a2..f38402aa7 100644 --- a/src/telemetry/connectionTelemetry.ts +++ b/src/telemetry/connectionTelemetry.ts @@ -84,7 +84,7 @@ export async function getConnectionTelemetryProperties( const [hostname] = firstHost.split(':'); const authMechanism = connectionString.searchParams.get('authMechanism'); const username = connectionString.username ? 'DEFAULT' : 'NONE'; - const authStrategy = authMechanism ? authMechanism : username; + const authStrategy = authMechanism ?? username; const instance = await dataService.instance(); const cloudInfo = await getCloudInfoFromDataService(hostname); const preparedProperties: NewConnectionTelemetryEventProperties = { diff --git a/src/test/suite/storage/storageController.test.ts b/src/test/suite/storage/storageController.test.ts index 9d3f09f8f..03cfdf191 100644 --- a/src/test/suite/storage/storageController.test.ts +++ b/src/test/suite/storage/storageController.test.ts @@ -56,7 +56,7 @@ suite('Storage Controller Test Suite', () => { } }; const testStorageController = new StorageController(testExtensionContext); - void testStorageController.saveConnectionToGlobalStore({ + void testStorageController.saveConnectionToStore({ id: 'conn2', name: 'saved2', storageLocation: StorageLocation.GLOBAL, @@ -96,7 +96,7 @@ suite('Storage Controller Test Suite', () => { } }; const testStorageController = new StorageController(testExtensionContext); - void testStorageController.saveConnectionToWorkspaceStore({ + void testStorageController.saveConnectionToStore({ id: 'conn2', name: 'saved2', storageLocation: StorageLocation.WORKSPACE, diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index d12cdb856..40a096dd3 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -83,12 +83,12 @@ export default class WebviewController { connectionAttemptId: string ): Promise => { try { - const connectionModel = this._connectionController.parseNewConnection(rawConnectionModel); + const connectionInfo = this._connectionController.parseNewConnection(rawConnectionModel); const { successfullyConnected, connectionErrorMessage } = await this._connectionController.saveNewConnectionAndConnect( - connectionModel, + connectionInfo, ConnectionTypes.CONNECTION_FORM ); From c9570f4d014bf0d8b4b473d1a5c65d981d5dcf54 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 21 Dec 2021 16:30:36 +0100 Subject: [PATCH 11/15] refactor: pull secrets methods from data service --- package-lock.json | 513 ++++-------------- package.json | 7 +- src/connectionController.ts | 23 +- .../suite/utils/connection-secrets.test.ts | 167 ------ src/utils/connectionSecrets.ts | 121 ----- 5 files changed, 122 insertions(+), 709 deletions(-) delete mode 100644 src/test/suite/utils/connection-secrets.test.ts delete mode 100644 src/utils/connectionSecrets.ts diff --git a/package-lock.json b/package-lock.json index 94e0ec7d1..15a7c3a4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,13 +28,12 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.6.0", - "lodash.clonedeep": "^4.5.0", "micromatch": "^4.0.4", "mongodb": "^4.2.0", "mongodb-cloud-info": "^1.1.3", - "mongodb-connection-model": "^21.9.0", - "mongodb-connection-string-url": "^1.1.0", - "mongodb-data-service": "^21.13.0", + "mongodb-connection-model": "^21.10.0", + "mongodb-connection-string-url": "^2.4.0", + "mongodb-data-service": "^21.14.0", "mongodb-ns": "^2.2.0", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", @@ -1546,9 +1545,9 @@ } }, "node_modules/@mongodb-js/compass-logging": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.4.0.tgz", - "integrity": "sha512-7zahgRFbD9bGM595zIHQc5wOOwQUrf+V38LL+rS71/LZjFrWKsGBW+u0Hmxe7XnwM04AJoZilAcBhSxWwhPllQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.5.0.tgz", + "integrity": "sha512-rHpfrWrHNDunXBnrB+44Dzu/w716AosiWVPvBhSIi/wTJmg/bopYMsOihkr8y27r3JPlraJXkpLOWbNk1n+kEA==", "dependencies": { "debug": "4.3.0", "is-electron-renderer": "^2.0.1", @@ -1689,46 +1688,6 @@ "mongodb-client-encryption": "^2.0.0-beta.0" } }, - "node_modules/@mongosh/service-provider-core/node_modules/mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/@mongosh/service-provider-core/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@mongosh/service-provider-core/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/@mongosh/service-provider-core/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@mongosh/service-provider-server": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@mongosh/service-provider-server/-/service-provider-server-1.1.4.tgz", @@ -1753,46 +1712,6 @@ "resolve-mongodb-srv": "^1.1.0" } }, - "node_modules/@mongosh/service-provider-server/node_modules/mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/@mongosh/service-provider-server/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@mongosh/service-provider-server/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/@mongosh/service-provider-server/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@mongosh/shell-api": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@mongosh/shell-api/-/shell-api-1.1.4.tgz", @@ -10077,9 +9996,9 @@ "optional": true }, "node_modules/hadron-ipc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.6.0.tgz", - "integrity": "sha512-+lHfkoPvnuCdXrTvmDrweZlrWPDis3aofif0P/2gLibpKZvRr0N3tD9YeOg/MYcfOJ1n91nIKWboXFHii5eKDA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.7.0.tgz", + "integrity": "sha512-C78E3r94+grlw10Nid1EOqdln9zBdoAUF2oYtHay+i6PyY4i76QgVtZOyO6RcYeANd1WYRpEubIrT9cURKUUyg==", "dependencies": { "debug": "4.3.0", "is-electron-renderer": "^2.0.1", @@ -14358,11 +14277,6 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "optional": true }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -15737,9 +15651,12 @@ } }, "node_modules/mongodb-build-info": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.2.0.tgz", - "integrity": "sha512-HsAk6w9TZ7erX+DGP81T9G7dA6JV3etT5VC5GGGvNqSg4NojVRdn5rij4kD4vSUz75LgcAbGbUZ/QHvlpb/UfQ==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.3.0.tgz", + "integrity": "sha512-R6ZLF/X//66zukcOfRg08TZ1JhDlTP16FKouZASv2qIxYtDV6pXhcTxmgYJqS1sK9jkjZciNklZa6leZ/ejvog==", + "dependencies": { + "mongodb-connection-string-url": "^2.2.0" + } }, "node_modules/mongodb-client-encryption": { "version": "2.0.0-beta.0", @@ -16019,24 +15936,24 @@ } }, "node_modules/mongodb-connection-model": { - "version": "21.9.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.9.0.tgz", - "integrity": "sha512-V8rgd6ssauIbQGVvmdszGaLda8zRd3nVMJzDExniLVQQAZbCWeDvdcp4pIVWqJtD3dAMZMn/jyYJmYC4wqZpRg==", + "version": "21.10.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.10.0.tgz", + "integrity": "sha512-CmADVBdi7mTnl8LVPScqfmbYU/GnzCWcM+xTlNAkBQa0M4JSAmry7yf9AewdtwB1mO6Lyu6P97fWK8vTEp1K5g==", "dependencies": { "@mongodb-js/ssh-tunnel": "^1.2.1", "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "debug": "4.3.0", "lodash": "^4.17.15", - "mongodb-connection-string-url": "^2.1.0", + "mongodb-connection-string-url": "^2.3.2", "mongodb3": "npm:mongodb@^3.6.3", "raf": "^3.4.1", - "resolve-mongodb-srv": "^1.1.0", + "resolve-mongodb-srv": "^1.1.1", "ssh2": "^0.8.7", - "storage-mixin": "^4.8.0" + "storage-mixin": "^4.9.0" }, "peerDependencies": { - "mongodb": "^4.1.0" + "mongodb": "^4.2.2" } }, "node_modules/mongodb-connection-model/node_modules/debug": { @@ -16056,16 +15973,16 @@ } } }, - "node_modules/mongodb-connection-model/node_modules/mongodb-connection-string-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", - "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", + "node_modules/mongodb-connection-string-url": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", + "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==", "dependencies": { "@types/whatwg-url": "^8.2.1", "whatwg-url": "^11.0.0" } }, - "node_modules/mongodb-connection-model/node_modules/tr46": { + "node_modules/mongodb-connection-string-url/node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", @@ -16076,7 +15993,7 @@ "node": ">=12" } }, - "node_modules/mongodb-connection-model/node_modules/webidl-conversions": { + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", @@ -16084,7 +16001,7 @@ "node": ">=12" } }, - "node_modules/mongodb-connection-model/node_modules/whatwg-url": { + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", @@ -16096,15 +16013,6 @@ "node": ">=12" } }, - "node_modules/mongodb-connection-string-url": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-1.1.0.tgz", - "integrity": "sha512-g0Qaj4AzIaktWKBkfjMjwzvBzZQN1mtb2DVOTbjdvlaqTa5lGLcnTeh0/9R9mPiIt2lvRGOrDgUdazeP5rD9oA==", - "dependencies": { - "@types/whatwg-url": "^8.0.0", - "whatwg-url": "^8.4.0" - } - }, "node_modules/mongodb-core": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.7.tgz", @@ -16129,28 +16037,28 @@ } }, "node_modules/mongodb-data-service": { - "version": "21.13.0", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.13.0.tgz", - "integrity": "sha512-dRZJ6+hvFJMeAYdayhX2x/BN2q9EjOaXEcWCRS2BND9WTtAum3QZvrEqROc2ViGldaiJfLcYjZorocSjPKwylw==", + "version": "21.14.0", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.14.0.tgz", + "integrity": "sha512-m4tC2lCGBm0I9rxq10W1wgXBDcJ5I+tyXaSYWtLJnrF/Y/hgy2pynA0rDPu7sDlLNQBEuDyU844kXT0QCyz1EQ==", "dependencies": { - "@mongodb-js/compass-logging": "^0.4.0", + "@mongodb-js/compass-logging": "^0.5.0", "@mongodb-js/ssh-tunnel": "^1.2.1", "async": "^3.2.0", "debug": "4.3.0", "get-port": "^5.1.1", "lodash": "^4.17.20", - "mongodb-build-info": "^1.1.1", - "mongodb-connection-string-url": "^2.1.0", - "mongodb-index-model": "^3.6.0", + "mongodb-build-info": "^1.3.0", + "mongodb-connection-string-url": "^2.3.2", + "mongodb-index-model": "^3.7.0", "mongodb-ns": "^2.2.0", - "resolve-mongodb-srv": "^1.1.0", + "resolve-mongodb-srv": "^1.1.1", "uuid": "^8.3.2" }, "engines": { "node": ">=14.17.5" }, "peerDependencies": { - "mongodb": "^4.1.0", + "mongodb": "^4.2.2", "mongodb-connection-model": "*" } }, @@ -16176,46 +16084,6 @@ } } }, - "node_modules/mongodb-data-service/node_modules/mongodb-connection-string-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", - "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/mongodb-data-service/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-data-service/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-data-service/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/mongodb-dbpath": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/mongodb-dbpath/-/mongodb-dbpath-0.0.1.tgz", @@ -16318,9 +16186,9 @@ } }, "node_modules/mongodb-index-model": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.6.0.tgz", - "integrity": "sha512-tOx0joqNPOKN9mEsSOk2hUQ018uflL/gD5oqCF6LhwQMx7hVG3o3VBGZRTLpObEICH1KqoNW9wmU+P2PLj4xvQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.7.0.tgz", + "integrity": "sha512-ev/bzTlP9cs4F/MvWbVWxL3G7UZtAj9h2GrvI7qwCfglyBR2qOlmat9c57f2SAcbKQZVS0y9wbKvZurG/IAQFQ==", "dependencies": { "ampersand-collection": "^2.0.1", "ampersand-model": "^8.0.1", @@ -16346,9 +16214,9 @@ } }, "node_modules/mongodb-log-writer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.2.tgz", - "integrity": "sha512-SkurMSeQDZw9o+gG6Gz38okEsV5890HBBHDSpTgHeWZukKvSLQIpMqRc4MJSWKvXr8B+Y7iByHebykcjhd/RBA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.3.tgz", + "integrity": "sha512-PRNUD9spaTqOg19EYGaVNEnyyPsp7qCPI0Py2iUZDtL+uWA7IokA0Eb6M1yOpZn0MNVNQAjLg7+iVeZ82Ise4A==", "dependencies": { "bson": "^4.5.1" } @@ -16961,46 +16829,6 @@ "node": ">=0.10" } }, - "node_modules/mongodb/node_modules/mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/mongodb/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/mongodb3": { "name": "mongodb", "version": "3.6.9", @@ -19979,9 +19807,9 @@ } }, "node_modules/resolve-mongodb-srv": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-mongodb-srv/-/resolve-mongodb-srv-1.1.0.tgz", - "integrity": "sha512-CM4Ma7dLRwAR4KhuKWEqj4Be+LVIVfmks+X/ioHGvblhBglqD+gn+TOpJE2npEm9/vjleVS+quSk91w6vA/2hg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve-mongodb-srv/-/resolve-mongodb-srv-1.1.1.tgz", + "integrity": "sha512-dKOOF9Fz+CGcwjMSggO8B/Vjdrv+cEBfd6erU6bfeJnacvc6wpxIc0S+V83g/yoPYCo6ztRYv/1z5gN7erlM6w==", "dependencies": { "whatwg-url": "^8.5.0" }, @@ -21072,16 +20900,16 @@ } }, "node_modules/storage-mixin": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.8.0.tgz", - "integrity": "sha512-qwpGc069ORwNJHMnvxM+xP7qLlXfhCvxkxb4vqVFNeM2UR1IyuRCW51bAahVmrrDGOAWShg7CY7Z7E7NK6bCGA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.9.0.tgz", + "integrity": "sha512-F046hz8MehWM5+iKONQIUZccGNvGYpgJ9r7oL9/afdTvkZFW0OwW85pLVdNHMxW1U/goYCZtsJ+lLW0zbmL4pQ==", "dependencies": { "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "ampersand-sync": "^5.1.0", "async": "^3.2.0", "debug": "4.3.0", - "hadron-ipc": "^2.6.0", + "hadron-ipc": "^2.7.0", "keytar": "^7.7.0", "localforage": "^1.7.3", "lodash": "^4.17.15", @@ -25439,9 +25267,9 @@ } }, "@mongodb-js/compass-logging": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.4.0.tgz", - "integrity": "sha512-7zahgRFbD9bGM595zIHQc5wOOwQUrf+V38LL+rS71/LZjFrWKsGBW+u0Hmxe7XnwM04AJoZilAcBhSxWwhPllQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.5.0.tgz", + "integrity": "sha512-rHpfrWrHNDunXBnrB+44Dzu/w716AosiWVPvBhSIi/wTJmg/bopYMsOihkr8y27r3JPlraJXkpLOWbNk1n+kEA==", "requires": { "debug": "4.3.0", "is-electron-renderer": "^2.0.1", @@ -25544,39 +25372,6 @@ "mongodb-build-info": "^1.2.0", "mongodb-client-encryption": "^2.0.0-beta.0", "mongodb-connection-string-url": "^2.2.0" - }, - "dependencies": { - "mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - } } }, "@mongosh/service-provider-server": { @@ -25596,39 +25391,6 @@ "os-dns-native": "^1.0.3", "resolve-mongodb-srv": "^1.1.0", "saslprep": "github:mongodb-js/saslprep#v1.0.4" - }, - "dependencies": { - "mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - } } }, "@mongosh/shell-api": { @@ -32586,9 +32348,9 @@ "optional": true }, "hadron-ipc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.6.0.tgz", - "integrity": "sha512-+lHfkoPvnuCdXrTvmDrweZlrWPDis3aofif0P/2gLibpKZvRr0N3tD9YeOg/MYcfOJ1n91nIKWboXFHii5eKDA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-2.7.0.tgz", + "integrity": "sha512-C78E3r94+grlw10Nid1EOqdln9zBdoAUF2oYtHay+i6PyY4i76QgVtZOyO6RcYeANd1WYRpEubIrT9cURKUUyg==", "requires": { "debug": "4.3.0", "is-electron-renderer": "^2.0.1", @@ -36162,11 +35924,6 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "optional": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -37296,37 +37053,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" - }, - "mongodb-connection-string-url": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz", - "integrity": "sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } } } }, @@ -37339,9 +37065,12 @@ } }, "mongodb-build-info": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.2.0.tgz", - "integrity": "sha512-HsAk6w9TZ7erX+DGP81T9G7dA6JV3etT5VC5GGGvNqSg4NojVRdn5rij4kD4vSUz75LgcAbGbUZ/QHvlpb/UfQ==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.3.0.tgz", + "integrity": "sha512-R6ZLF/X//66zukcOfRg08TZ1JhDlTP16FKouZASv2qIxYtDV6pXhcTxmgYJqS1sK9jkjZciNklZa6leZ/ejvog==", + "requires": { + "mongodb-connection-string-url": "^2.2.0" + } }, "mongodb-client-encryption": { "version": "2.0.0-beta.0", @@ -37570,21 +37299,21 @@ } }, "mongodb-connection-model": { - "version": "21.9.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.9.0.tgz", - "integrity": "sha512-V8rgd6ssauIbQGVvmdszGaLda8zRd3nVMJzDExniLVQQAZbCWeDvdcp4pIVWqJtD3dAMZMn/jyYJmYC4wqZpRg==", + "version": "21.10.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-model/-/mongodb-connection-model-21.10.0.tgz", + "integrity": "sha512-CmADVBdi7mTnl8LVPScqfmbYU/GnzCWcM+xTlNAkBQa0M4JSAmry7yf9AewdtwB1mO6Lyu6P97fWK8vTEp1K5g==", "requires": { "@mongodb-js/ssh-tunnel": "^1.2.1", "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "debug": "4.3.0", "lodash": "^4.17.15", - "mongodb-connection-string-url": "^2.1.0", + "mongodb-connection-string-url": "^2.3.2", "mongodb3": "npm:mongodb@^3.6.3", "raf": "^3.4.1", - "resolve-mongodb-srv": "^1.1.0", + "resolve-mongodb-srv": "^1.1.1", "ssh2": "^0.8.7", - "storage-mixin": "^4.8.0" + "storage-mixin": "^4.9.0" }, "dependencies": { "debug": { @@ -37594,16 +37323,18 @@ "requires": { "ms": "2.1.2" } - }, - "mongodb-connection-string-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", - "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, + } + } + }, + "mongodb-connection-string-url": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", + "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + }, + "dependencies": { "tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -37628,15 +37359,6 @@ } } }, - "mongodb-connection-string-url": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-1.1.0.tgz", - "integrity": "sha512-g0Qaj4AzIaktWKBkfjMjwzvBzZQN1mtb2DVOTbjdvlaqTa5lGLcnTeh0/9R9mPiIt2lvRGOrDgUdazeP5rD9oA==", - "requires": { - "@types/whatwg-url": "^8.0.0", - "whatwg-url": "^8.4.0" - } - }, "mongodb-core": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.7.tgz", @@ -37658,21 +37380,21 @@ } }, "mongodb-data-service": { - "version": "21.13.0", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.13.0.tgz", - "integrity": "sha512-dRZJ6+hvFJMeAYdayhX2x/BN2q9EjOaXEcWCRS2BND9WTtAum3QZvrEqROc2ViGldaiJfLcYjZorocSjPKwylw==", + "version": "21.14.0", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.14.0.tgz", + "integrity": "sha512-m4tC2lCGBm0I9rxq10W1wgXBDcJ5I+tyXaSYWtLJnrF/Y/hgy2pynA0rDPu7sDlLNQBEuDyU844kXT0QCyz1EQ==", "requires": { - "@mongodb-js/compass-logging": "^0.4.0", + "@mongodb-js/compass-logging": "^0.5.0", "@mongodb-js/ssh-tunnel": "^1.2.1", "async": "^3.2.0", "debug": "4.3.0", "get-port": "^5.1.1", "lodash": "^4.17.20", - "mongodb-build-info": "^1.1.1", - "mongodb-connection-string-url": "^2.1.0", - "mongodb-index-model": "^3.6.0", + "mongodb-build-info": "^1.3.0", + "mongodb-connection-string-url": "^2.3.2", + "mongodb-index-model": "^3.7.0", "mongodb-ns": "^2.2.0", - "resolve-mongodb-srv": "^1.1.0", + "resolve-mongodb-srv": "^1.1.1", "uuid": "^8.3.2" }, "dependencies": { @@ -37688,37 +37410,6 @@ "requires": { "ms": "2.1.2" } - }, - "mongodb-connection-string-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.3.2.tgz", - "integrity": "sha512-2LkmS0ny7LamAyhEs2Q+zuFFxeGNSc2DaGHBevjqkoPt7bgh+67mg1sFU6awnMsdLKpdEt7zUy466K9x7RsYcQ==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } } } }, @@ -37813,9 +37504,9 @@ } }, "mongodb-index-model": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.6.0.tgz", - "integrity": "sha512-tOx0joqNPOKN9mEsSOk2hUQ018uflL/gD5oqCF6LhwQMx7hVG3o3VBGZRTLpObEICH1KqoNW9wmU+P2PLj4xvQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mongodb-index-model/-/mongodb-index-model-3.7.0.tgz", + "integrity": "sha512-ev/bzTlP9cs4F/MvWbVWxL3G7UZtAj9h2GrvI7qwCfglyBR2qOlmat9c57f2SAcbKQZVS0y9wbKvZurG/IAQFQ==", "requires": { "ampersand-collection": "^2.0.1", "ampersand-model": "^8.0.1", @@ -37843,9 +37534,9 @@ } }, "mongodb-log-writer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.2.tgz", - "integrity": "sha512-SkurMSeQDZw9o+gG6Gz38okEsV5890HBBHDSpTgHeWZukKvSLQIpMqRc4MJSWKvXr8B+Y7iByHebykcjhd/RBA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.3.tgz", + "integrity": "sha512-PRNUD9spaTqOg19EYGaVNEnyyPsp7qCPI0Py2iUZDtL+uWA7IokA0Eb6M1yOpZn0MNVNQAjLg7+iVeZ82Ise4A==", "requires": { "bson": "^4.5.1" } @@ -40755,9 +40446,9 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "resolve-mongodb-srv": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-mongodb-srv/-/resolve-mongodb-srv-1.1.0.tgz", - "integrity": "sha512-CM4Ma7dLRwAR4KhuKWEqj4Be+LVIVfmks+X/ioHGvblhBglqD+gn+TOpJE2npEm9/vjleVS+quSk91w6vA/2hg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve-mongodb-srv/-/resolve-mongodb-srv-1.1.1.tgz", + "integrity": "sha512-dKOOF9Fz+CGcwjMSggO8B/Vjdrv+cEBfd6erU6bfeJnacvc6wpxIc0S+V83g/yoPYCo6ztRYv/1z5gN7erlM6w==", "requires": { "whatwg-url": "^8.5.0" } @@ -41678,16 +41369,16 @@ "dev": true }, "storage-mixin": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.8.0.tgz", - "integrity": "sha512-qwpGc069ORwNJHMnvxM+xP7qLlXfhCvxkxb4vqVFNeM2UR1IyuRCW51bAahVmrrDGOAWShg7CY7Z7E7NK6bCGA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/storage-mixin/-/storage-mixin-4.9.0.tgz", + "integrity": "sha512-F046hz8MehWM5+iKONQIUZccGNvGYpgJ9r7oL9/afdTvkZFW0OwW85pLVdNHMxW1U/goYCZtsJ+lLW0zbmL4pQ==", "requires": { "ampersand-model": "^8.0.1", "ampersand-rest-collection": "^6.0.0", "ampersand-sync": "^5.1.0", "async": "^3.2.0", "debug": "4.3.0", - "hadron-ipc": "^2.6.0", + "hadron-ipc": "^2.7.0", "keytar": "^7.7.0", "localforage": "^1.7.3", "lodash": "^4.17.15", diff --git a/package.json b/package.json index 918fcb103..6299988aa 100644 --- a/package.json +++ b/package.json @@ -916,13 +916,12 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.6.0", - "lodash.clonedeep": "^4.5.0", "micromatch": "^4.0.4", "mongodb": "^4.2.0", "mongodb-cloud-info": "^1.1.3", - "mongodb-connection-model": "^21.9.0", - "mongodb-connection-string-url": "^1.1.0", - "mongodb-data-service": "^21.13.0", + "mongodb-connection-model": "^21.10.0", + "mongodb-connection-string-url": "^2.4.0", + "mongodb-data-service": "^21.14.0", "mongodb-ns": "^2.2.0", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", diff --git a/src/connectionController.ts b/src/connectionController.ts index 6ae3f8bfe..b6a630973 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -4,7 +4,10 @@ import { ConnectionInfo, ConnectionOptions, DataService, - getConnectionTitle + getConnectionTitle, + ConnectionSecrets, + extractSecrets, + mergeSecrets } from 'mongodb-data-service'; import ConnectionModel from 'mongodb-connection-model'; import ConnectionString from 'mongodb-connection-string-url'; @@ -21,9 +24,6 @@ import { StorageController, StorageVariables } from './storage'; import { StatusView } from './views'; import TelemetryService from './telemetry/telemetryService'; -// TODO: export these helpers from mongodb-data-service. -import { ConnectionSecrets, extractSecrets, mergeSecrets } from './utils/connectionSecrets'; - const log = createLogger('connection controller'); const packageJSON = require('../package.json'); @@ -166,7 +166,13 @@ export default class ConnectionController { const secrets = JSON.parse(unparsedSecrets); const connectionOptions = savedConnectionInfo.connectionOptions; - const connectionInfoWithSecrets = mergeSecrets({ connectionOptions }, secrets); + const connectionInfoWithSecrets = mergeSecrets( + { + id: savedConnectionInfo.id, + connectionOptions + }, + secrets + ); return { ...savedConnectionInfo, @@ -240,6 +246,10 @@ export default class ConnectionController { 'e.g. mongodb+srv://username:password@cluster0.mongodb.net/admin', prompt: 'Enter your connection string (SRV or standard)', validateInput: (uri: string) => { + if (!uri.startsWith('mongodb://') && !uri.startsWith('mongodb+srv://')) { + return 'MongoDB connection strings begin with "mongodb://" or "mongodb+srv://"'; + } + try { // eslint-disable-next-line no-new new ConnectionString(uri); @@ -285,6 +295,7 @@ export default class ConnectionController { try { const connectResult = await this.saveNewConnectionAndConnect( { + id: uuidv4(), connectionOptions: { connectionString: connectionStringData.toString() } @@ -355,7 +366,7 @@ export default class ConnectionController { ): Promise { const name = getConnectionTitle(originalConnectionInfo); const newConnectionInfo = { - id: uuidv4(), + id: originalConnectionInfo.id, name, // To begin we just store it on the session, the storage controller // handles changing this based on user preference. diff --git a/src/test/suite/utils/connection-secrets.test.ts b/src/test/suite/utils/connection-secrets.test.ts deleted file mode 100644 index 2e29036c2..000000000 --- a/src/test/suite/utils/connection-secrets.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { ConnectionInfo } from 'mongodb-data-service'; -import { expect } from 'chai'; - -import { - mergeSecrets, - extractSecrets, - ConnectionSecrets, -} from '../../../utils/connectionSecrets'; - -suite('connection secrets', () => { - suite('mergeSecrets', () => { - test('does not modify the original object', () => { - const originalConnectionInfo: ConnectionInfo = { - connectionOptions: { - connectionString: 'mongodb://localhost:27017', - sshTunnel: { - host: 'localhost', - username: 'user', - port: 22, - }, - }, - favorite: { - name: 'connection 1', - }, - }; - - const originalConnectionInfoStr = JSON.stringify(originalConnectionInfo); - - const newConnectionInfo = mergeSecrets(originalConnectionInfo, { - password: 'xxx', - awsSessionToken: 'xxx', - sshTunnelPassphrase: 'xxx', - tlsCertificateKeyFilePassword: 'xxx', - }); - - expect(newConnectionInfo).to.not.equal(originalConnectionInfo); - - expect(newConnectionInfo.connectionOptions).to.not.equal( - originalConnectionInfo.connectionOptions - ); - - expect(newConnectionInfo.connectionOptions.sshTunnel).to.not.equal( - originalConnectionInfo.connectionOptions.sshTunnel - ); - - expect(newConnectionInfo.favorite).to.not.equal( - originalConnectionInfo.favorite - ); - - expect(originalConnectionInfoStr).to.equal( - JSON.stringify(originalConnectionInfo) - ); - }); - - test('merges secrets', () => { - const originalConnectionInfo: ConnectionInfo = { - connectionOptions: { - connectionString: 'mongodb://username@localhost:27017/', - sshTunnel: { - host: 'localhost', - username: 'user', - port: 22, - }, - }, - }; - - const newConnectionInfo = mergeSecrets(originalConnectionInfo, { - awsSessionToken: 'sessionToken', - password: 'userPassword', - sshTunnelPassphrase: 'passphrase', - tlsCertificateKeyFilePassword: 'tlsCertPassword', - }); - - expect(newConnectionInfo).to.be.deep.equal({ - connectionOptions: { - connectionString: - 'mongodb://username:userPassword@localhost:27017/?tlsCertificateKeyFilePassword=tlsCertPassword&authMechanismProperties=AWS_SESSION_TOKEN%3AsessionToken', - sshTunnel: { - host: 'localhost', - username: 'user', - port: 22, - identityKeyPassphrase: 'passphrase', - }, - }, - } as ConnectionInfo); - }); - }); - - suite('extractSecrets', () => { - test('does not modify the original object', () => { - const originalConnectionInfo: ConnectionInfo = { - connectionOptions: { - connectionString: 'mongodb://localhost:27017', - sshTunnel: { - host: 'localhost', - username: 'user', - port: 22, - }, - }, - favorite: { - name: 'connection 1', - }, - }; - - const originalConnectionInfoStr = JSON.stringify(originalConnectionInfo); - - const { connectionInfo: newConnectionInfo } = extractSecrets( - originalConnectionInfo - ); - - expect(newConnectionInfo).to.not.equal(originalConnectionInfo); - - expect(newConnectionInfo.connectionOptions).to.not.equal( - originalConnectionInfo.connectionOptions - ); - - expect(newConnectionInfo.connectionOptions.sshTunnel).to.not.equal( - originalConnectionInfo.connectionOptions.sshTunnel - ); - - expect(newConnectionInfo.favorite).to.not.equal( - originalConnectionInfo.favorite - ); - - expect(originalConnectionInfoStr).to.equal( - JSON.stringify(originalConnectionInfo) - ); - }); - - test('extracts secrets', () => { - const originalConnectionInfo: ConnectionInfo = { - connectionOptions: { - connectionString: - 'mongodb://username:userPassword@localhost:27017/?tlsCertificateKeyFilePassword=tlsCertPassword&authMechanismProperties=AWS_SESSION_TOKEN%3AsessionToken', - sshTunnel: { - host: 'localhost', - username: 'user', - port: 22, - identityKeyPassphrase: 'passphrase', - }, - }, - }; - - const { connectionInfo: newConnectionInfo, secrets } = extractSecrets( - originalConnectionInfo - ); - - expect(newConnectionInfo).to.be.deep.equal({ - connectionOptions: { - connectionString: 'mongodb://username@localhost:27017/', - sshTunnel: { - host: 'localhost', - username: 'user', - port: 22, - }, - }, - } as ConnectionInfo); - - expect(secrets).to.be.deep.equal({ - awsSessionToken: 'sessionToken', - password: 'userPassword', - sshTunnelPassphrase: 'passphrase', - tlsCertificateKeyFilePassword: 'tlsCertPassword', - } as ConnectionSecrets); - }); - }); -}); diff --git a/src/utils/connectionSecrets.ts b/src/utils/connectionSecrets.ts deleted file mode 100644 index 620cbcbea..000000000 --- a/src/utils/connectionSecrets.ts +++ /dev/null @@ -1,121 +0,0 @@ -import ConnectionString, { - CommaAndColonSeparatedRecord, -} from 'mongodb-connection-string-url'; -import { ConnectionInfo } from 'mongodb-data-service'; - -const cloneDeep = require('lodash.clonedeep'); - -export interface ConnectionSecrets { - password?: string; - sshTunnelPassphrase?: string; - awsSessionToken?: string; - tlsCertificateKeyFilePassword?: string; -} - -const AWS_SESSION_TOKEN_PROPERTY = 'AWS_SESSION_TOKEN'; -const AUTH_MECHANISM_PROPERTIES_PARAM = 'authMechanismProperties'; -const TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM = 'tlsCertificateKeyFilePassword'; - -export const mergeSecrets = ( - connectionInfo: Readonly, - secrets: ConnectionSecrets | undefined -): ConnectionInfo => { - const connectionInfoWithSecrets = cloneDeep(connectionInfo); - - if (!secrets) { - return connectionInfoWithSecrets; - } - - const connectionOptions = connectionInfoWithSecrets.connectionOptions; - - const uri = new ConnectionString(connectionOptions.connectionString); - - if (secrets.password) { - uri.password = secrets.password; - } - - if (secrets.sshTunnelPassphrase && connectionOptions.sshTunnel) { - connectionOptions.sshTunnel.identityKeyPassphrase = - secrets.sshTunnelPassphrase; - } - - if (secrets.tlsCertificateKeyFilePassword) { - uri.searchParams.set( - TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM, - secrets.tlsCertificateKeyFilePassword - ); - } - - if (secrets.awsSessionToken) { - const authMechanismProperties = new CommaAndColonSeparatedRecord( - uri.searchParams.get(AUTH_MECHANISM_PROPERTIES_PARAM) - ); - - authMechanismProperties.set( - AWS_SESSION_TOKEN_PROPERTY, - secrets.awsSessionToken - ); - - uri.searchParams.set( - AUTH_MECHANISM_PROPERTIES_PARAM, - authMechanismProperties.toString() - ); - } - - connectionInfoWithSecrets.connectionOptions.connectionString = uri.href; - - return connectionInfoWithSecrets; -}; - -export const extractSecrets = (connectionInfo: Readonly): { - connectionInfo: ConnectionInfo; - secrets: ConnectionSecrets; -} => { - const connectionInfoWithoutSecrets = cloneDeep(connectionInfo); - const secrets: ConnectionSecrets = {}; - - const connectionOptions = connectionInfoWithoutSecrets.connectionOptions; - const uri = new ConnectionString(connectionOptions.connectionString); - - if (uri.password) { - secrets.password = uri.password; - uri.password = ''; - } - - if (connectionOptions.sshTunnel?.identityKeyPassphrase) { - secrets.sshTunnelPassphrase = - connectionOptions.sshTunnel.identityKeyPassphrase; - delete connectionOptions.sshTunnel.identityKeyPassphrase; - } - - if (uri.searchParams.has(TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM)) { - secrets.tlsCertificateKeyFilePassword = - uri.searchParams.get(TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM) || - undefined; - uri.searchParams.delete(TLS_CERTIFICATE_KEY_FILE_PASSWORD_PARAM); - } - - const authMechanismProperties = new CommaAndColonSeparatedRecord( - uri.searchParams.get(AUTH_MECHANISM_PROPERTIES_PARAM) - ); - - if (authMechanismProperties.has(AWS_SESSION_TOKEN_PROPERTY)) { - secrets.awsSessionToken = authMechanismProperties.get( - AWS_SESSION_TOKEN_PROPERTY - ); - authMechanismProperties.delete(AWS_SESSION_TOKEN_PROPERTY); - - if (authMechanismProperties.toString()) { - uri.searchParams.set( - AUTH_MECHANISM_PROPERTIES_PARAM, - authMechanismProperties.toString() - ); - } else { - uri.searchParams.delete(AUTH_MECHANISM_PROPERTIES_PARAM); - } - } - - connectionInfoWithoutSecrets.connectionOptions.connectionString = uri.href; - - return { connectionInfo: connectionInfoWithoutSecrets, secrets }; -}; From a8e614a804eea04391824614daaaec6228e51410 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 22 Dec 2021 15:27:13 +0100 Subject: [PATCH 12/15] refactor: convertConnectionModelToInfo accepts raw models now --- src/connectionController.ts | 20 +++++-------- .../connect-form/general-tab/host/host.tsx | 2 +- ...on-model.ts => legacy-connection-model.ts} | 29 ++++++++++++------- .../extension-app-message-constants.ts | 4 +-- src/views/webview-app/store/actions.ts | 2 +- src/views/webview-app/store/store.ts | 10 +++---- src/views/webviewController.ts | 4 +-- 7 files changed, 36 insertions(+), 35 deletions(-) rename src/views/webview-app/connection-model/{connection-model.ts => legacy-connection-model.ts} (87%) diff --git a/src/connectionController.ts b/src/connectionController.ts index b6a630973..b3f84cbfa 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -18,7 +18,7 @@ import { v4 as uuidv4 } from 'uuid'; import { CONNECTION_STATUS } from './views/webview-app/extension-app-message-constants'; import { createLogger } from './logging'; import { ext } from './extensionConstants'; -import RawConnectionModel from './views/webview-app/connection-model/connection-model'; +import LegacyConnectionModel from './views/webview-app/connection-model/legacy-connection-model'; import { StorageLocation, ConnectionsFromStorage } from './storage/storageController'; import { StorageController, StorageVariables } from './storage'; import { StatusView } from './views'; @@ -74,7 +74,6 @@ export default class ConnectionController { // on the workspace, or globally in vscode. _connections: { [connectionId: string]: StoreConnectionInfo } = {}; _activeDataService: DataService| null = null; - _activeConnectionModel: ConnectionModel | null = null; private readonly _serviceName = 'mdb.vscode.savedConnections'; private _currentConnectionId: null | string = null; @@ -110,8 +109,7 @@ export default class ConnectionController { savedConnectionInfo: StoreConnectionInfo ): Promise { // Transform a raw connection model from storage to an ampersand model. - const newConnectionModelWithSecrets = new ConnectionModel(savedConnectionInfo.connectionModel); - const newConnectionInfoWithSecrets = convertConnectionModelToInfo(newConnectionModelWithSecrets); + const newConnectionInfoWithSecrets = convertConnectionModelToInfo(savedConnectionInfo.connectionModel); // Further use connectionOptions instead of connectionModel. const newSavedConnectionInfoWithSecrets = { @@ -313,15 +311,11 @@ export default class ConnectionController { void this._telemetryService.trackNewConnection(newDataService, connectionType); } - parseNewConnection(rawConnectionModel: RawConnectionModel): ConnectionInfo { - const connectionModel = new ConnectionModel(rawConnectionModel); - - // Override the default connection `appname`. - connectionModel.appname = `${packageJSON.name} ${packageJSON.version}`; - - const connectionInfo = convertConnectionModelToInfo(connectionModel); - - return connectionInfo; + parseNewConnection(rawConnectionModel: LegacyConnectionModel): ConnectionInfo { + return convertConnectionModelToInfo({ + ...rawConnectionModel, + appname: `${packageJSON.name} ${packageJSON.version}` // Override the default connection appname. + }); } private async _saveSecretsToKeychain( diff --git a/src/views/webview-app/components/connect-form/general-tab/host/host.tsx b/src/views/webview-app/components/connect-form/general-tab/host/host.tsx index 040e49e88..cdb8d0ab1 100644 --- a/src/views/webview-app/components/connect-form/general-tab/host/host.tsx +++ b/src/views/webview-app/components/connect-form/general-tab/host/host.tsx @@ -14,7 +14,7 @@ import FormInput from '../../../form/form-input'; import { AppState } from '../../../../store/store'; import FormGroup from '../../../form/form-group'; import RadioBoxGroup from '../../../form/radio-box-group/radio-box-group'; -import { DEFAULT_HOST, Host } from '../../../../connection-model/connection-model'; +import { DEFAULT_HOST, Host } from '../../../../connection-model/legacy-connection-model'; import ReplicaSetInput from './replica-set-input'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'; diff --git a/src/views/webview-app/connection-model/connection-model.ts b/src/views/webview-app/connection-model/legacy-connection-model.ts similarity index 87% rename from src/views/webview-app/connection-model/connection-model.ts rename to src/views/webview-app/connection-model/legacy-connection-model.ts index e38d38ad1..38df5ab88 100644 --- a/src/views/webview-app/connection-model/connection-model.ts +++ b/src/views/webview-app/connection-model/legacy-connection-model.ts @@ -3,6 +3,8 @@ import READ_PREFERENCES from './constants/read-preferences'; import SSL_METHODS from './constants/ssl-methods'; import SSH_TUNNEL_TYPES from './constants/ssh-tunnel-types'; +import { v4 as uuidv4 } from 'uuid'; + // Defaults. const AUTH_STRATEGY_DEFAULT = AUTH_STRATEGIES.NONE; const READ_PREFERENCE_DEFAULT = READ_PREFERENCES.PRIMARY; @@ -19,7 +21,10 @@ export interface Host { export const DEFAULT_HOST: Host = { host: 'localhost', port: 27017 }; // NOTE: This is currently tightly coupled with `mongodb-connection-model`. -class ConnectionModel { +class LegacyConnectionModel { + _id = uuidv4(); + isFavorite = false; + name = 'Local'; isSrvRecord = false; hostname = 'localhost'; port: port = 27017; @@ -32,7 +37,9 @@ class ConnectionModel { * Authentication. */ authStrategy: AUTH_STRATEGIES = AUTH_STRATEGY_DEFAULT; - kerberosCanonicalizeHostname = false; + // TODO: Fix kerberosCanonicalizeHostname type in DataService, should be boolean instaead of string. + // https://github.com/mongodb-js/compass/blob/main/packages/data-service/src/legacy/legacy-connection-model.ts#L94 + kerberosCanonicalizeHostname: any = false; kerberosPassword?: string; kerberosPrincipal?: string; kerberosServiceName?: string; @@ -92,7 +99,7 @@ class ConnectionModel { * Enforce constraints for SSL. * @param {Object} attrs - Incoming attributes. */ -const validateSsl = (attrs: ConnectionModel): void => { +const validateSsl = (attrs: LegacyConnectionModel): void => { if ( !attrs.sslMethod || ['NONE', 'UNVALIDATED', 'IFAVAILABLE', 'SYSTEMCA'].includes(attrs.sslMethod) @@ -113,7 +120,7 @@ const validateSsl = (attrs: ConnectionModel): void => { } }; -const validateMongodb = (attrs: ConnectionModel): void => { +const validateMongodb = (attrs: LegacyConnectionModel): void => { if ( attrs.authStrategy === AUTH_STRATEGIES.MONGODB || attrs.authStrategy === AUTH_STRATEGIES['SCRAM-SHA-256'] @@ -138,7 +145,7 @@ const validateMongodb = (attrs: ConnectionModel): void => { * Enforce constraints for Kerberos. * @param {Object} attrs - Incoming attributes. */ -const validateKerberos = (attrs: ConnectionModel): void => { +const validateKerberos = (attrs: LegacyConnectionModel): void => { if (attrs.authStrategy !== AUTH_STRATEGIES.KERBEROS) { if (attrs.kerberosServiceName) { throw new TypeError( @@ -162,7 +169,7 @@ const validateKerberos = (attrs: ConnectionModel): void => { } }; -const validateX509 = (attrs: ConnectionModel): void => { +const validateX509 = (attrs: LegacyConnectionModel): void => { if (attrs.authStrategy === AUTH_STRATEGIES.X509 && attrs.sslMethod !== SSL_METHODS.ALL) { throw new TypeError( 'SSL method is required to be set to \'Server and Client\' when using x509 authentication' @@ -170,7 +177,7 @@ const validateX509 = (attrs: ConnectionModel): void => { } }; -const validateLdap = (attrs: ConnectionModel): void => { +const validateLdap = (attrs: LegacyConnectionModel): void => { if (attrs.authStrategy === AUTH_STRATEGIES.LDAP) { if (!attrs.ldapUsername) { throw new TypeError( @@ -185,7 +192,7 @@ const validateLdap = (attrs: ConnectionModel): void => { } }; -const validateStandardSshTunnelOptions = (attrs: ConnectionModel): void => { +const validateStandardSshTunnelOptions = (attrs: LegacyConnectionModel): void => { if (attrs.sshTunnel !== SSH_TUNNEL_TYPES.NONE && attrs.isSrvRecord) { throw new TypeError( 'SSH Tunnel connections are not currently supported with srv records, please specify an individual server to connect to.' @@ -211,7 +218,7 @@ const validateStandardSshTunnelOptions = (attrs: ConnectionModel): void => { } }; -const validateSshTunnel = (attrs: ConnectionModel): void => { +const validateSshTunnel = (attrs: LegacyConnectionModel): void => { if (!attrs.sshTunnel || attrs.sshTunnel === SSH_TUNNEL_DEFAULT) { return; } @@ -235,7 +242,7 @@ const validateSshTunnel = (attrs: ConnectionModel): void => { } }; -export const validateConnectionModel = (attrs: ConnectionModel): Error | undefined => { +export const validateConnectionModel = (attrs: LegacyConnectionModel): Error | undefined => { try { validateSsl(attrs); validateMongodb(attrs); @@ -248,4 +255,4 @@ export const validateConnectionModel = (attrs: ConnectionModel): Error | undefin } }; -export default ConnectionModel; +export default LegacyConnectionModel; diff --git a/src/views/webview-app/extension-app-message-constants.ts b/src/views/webview-app/extension-app-message-constants.ts index 8b7b5456d..7ca94b425 100644 --- a/src/views/webview-app/extension-app-message-constants.ts +++ b/src/views/webview-app/extension-app-message-constants.ts @@ -1,4 +1,4 @@ -import ConnectionModel from './connection-model/connection-model'; +import LegacyConnectionModel from './connection-model/legacy-connection-model'; import { FilePickerActionTypes } from './store/actions'; export enum CONNECTION_STATUS { @@ -41,7 +41,7 @@ export interface ConnectionStatusMessage extends BasicWebviewMessage { export interface ConnectMessage extends BasicWebviewMessage { command: MESSAGE_TYPES.CONNECT; - connectionModel: ConnectionModel; + connectionModel: LegacyConnectionModel; connectionAttemptId: string; } diff --git a/src/views/webview-app/store/actions.ts b/src/views/webview-app/store/actions.ts index 184cfefae..3e0cf58de 100644 --- a/src/views/webview-app/store/actions.ts +++ b/src/views/webview-app/store/actions.ts @@ -4,7 +4,7 @@ import SSL_METHODS from '../connection-model/constants/ssl-methods'; import SSH_TUNNEL_TYPES from '../connection-model/constants/ssh-tunnel-types'; import { CONNECTION_STATUS } from '../extension-app-message-constants'; import { CONNECTION_FORM_TABS } from './constants'; -import { Host } from '../connection-model/connection-model'; +import { Host } from '../connection-model/legacy-connection-model'; export enum ActionTypes { AUTH_SOURCE_CHANGED = 'AUTH_SOURCE_CHANGED', diff --git a/src/views/webview-app/store/store.ts b/src/views/webview-app/store/store.ts index a07952f42..0dc577f70 100644 --- a/src/views/webview-app/store/store.ts +++ b/src/views/webview-app/store/store.ts @@ -1,10 +1,10 @@ import { Actions, ActionTypes, FilePickerActionTypes } from './actions'; import { v4 as uuidv4 } from 'uuid'; -import ConnectionModel, { +import LegacyConnectionModel, { DEFAULT_HOST, validateConnectionModel -} from '../connection-model/connection-model'; +} from '../connection-model/legacy-connection-model'; import SSL_METHODS from '../connection-model/constants/ssl-methods'; import { CONNECTION_STATUS, @@ -26,7 +26,7 @@ export interface AppState { connectionFormTab: CONNECTION_FORM_TABS; connectionMessage: string; connectionStatus: CONNECTION_STATUS; - currentConnection: ConnectionModel; + currentConnection: LegacyConnectionModel; isValid: boolean; isConnecting: boolean; isConnected: boolean; @@ -43,7 +43,7 @@ export const initialState: AppState = { connectionFormTab: CONNECTION_FORM_TABS.GENERAL, connectionMessage: '', connectionStatus: CONNECTION_STATUS.LOADING, - currentConnection: new ConnectionModel(), + currentConnection: new LegacyConnectionModel(), isValid: true, isConnecting: false, isConnected: false, @@ -65,7 +65,7 @@ const showFilePicker = ( }); }; -const sendConnectToExtension = (connectionModel: ConnectionModel): string => { +const sendConnectToExtension = (connectionModel: LegacyConnectionModel): string => { const connectionAttemptId = uuidv4(); vscode.postMessage({ diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index 40a096dd3..ff34336e8 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -4,7 +4,7 @@ import path from 'path'; import ConnectionController, { ConnectionTypes } from '../connectionController'; -import ConnectionModel from './webview-app/connection-model/connection-model'; +import LegacyConnectionModel from './webview-app/connection-model/legacy-connection-model'; import { createLogger } from '../logging'; import EXTENSION_COMMANDS from '../commands'; import { @@ -79,7 +79,7 @@ export default class WebviewController { handleWebviewConnectAttempt = async ( panel: vscode.WebviewPanel, - rawConnectionModel: ConnectionModel, + rawConnectionModel: LegacyConnectionModel, connectionAttemptId: string ): Promise => { try { From 5e5172d159d7e762e858f1e7f9b192b045fb7124 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 5 Jan 2022 14:11:12 +0100 Subject: [PATCH 13/15] fix: copy connection string without appname --- src/connectionController.ts | 9 +++++++-- src/mdbExtensionController.ts | 2 +- src/test/suite/connectionController.test.ts | 6 +++--- src/test/suite/mdbExtensionController.test.ts | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index b3f84cbfa..68817d819 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -709,8 +709,13 @@ export default class ConnectionController { return this._activeDataService?.getMongoClientConnectionOptions(); } - getConnectionStringByConnectionId(connectionId: string): string { - return this._connections[connectionId].connectionOptions.connectionString; + // Copy connection string from the sidebar does not need appname in it. + copyConnectionStringByConnectionId(connectionId: string): string { + const url = new ConnectionString( + this._connections[connectionId].connectionOptions.connectionString + ); + url.searchParams.delete('appname'); + return url.toString(); } getConnectionStatus(): CONNECTION_STATUS { diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 91c9465c8..0d7cfb296 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -296,7 +296,7 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand( EXTENSION_COMMANDS.MDB_COPY_CONNECTION_STRING, async (element: ConnectionTreeItem): Promise => { - const connectionString = this._connectionController.getConnectionStringByConnectionId( + const connectionString = this._connectionController.copyConnectionStringByConnectionId( element.connectionId ); diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index c7f2d03aa..9ab4616b0 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -475,8 +475,8 @@ suite('Connection Controller Test Suite', function () { ); }); - test('"getConnectionStringByConnectionId" returns the driver uri of a connection', async () => { - const expectedDriverUrl = 'mongodb://localhost:27018/?appname=mongodb-vscode+0.0.0-dev.0'; + test('"copyConnectionStringByConnectionId" returns the driver uri of a connection', async () => { + const expectedDriverUrl = 'mongodb://localhost:27018/'; await testConnectionController.loadSavedConnections(); await testConnectionController.addNewConnectionStringAndConnect( @@ -490,7 +490,7 @@ suite('Connection Controller Test Suite', function () { 'Expected active connection to not be null' ); - const testDriverUrl = testConnectionController.getConnectionStringByConnectionId( + const testDriverUrl = testConnectionController.copyConnectionStringByConnectionId( activeConnectionId || '' ); diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 29731e155..9c4426cb7 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -255,7 +255,7 @@ suite('MDBExtensionController Test Suite', function () { const mockStubUri: any = sinon.fake.returns('weStubThisUri'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, - 'getConnectionStringByConnectionId', + 'copyConnectionStringByConnectionId', mockStubUri ); From 7fa2ff5861451df5e59c95e7125f11048b570b7f Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Fri, 7 Jan 2022 14:59:28 +0100 Subject: [PATCH 14/15] fix: do not repeat indexes error for each expand action in the sidebar --- src/explorer/documentListTreeItem.ts | 14 ++++++---- src/explorer/indexListTreeItem.ts | 27 +++++++++++++------ src/explorer/schemaTreeItem.ts | 8 ++++-- .../suite/explorer/indexListTreeItem.test.ts | 22 +++++++++++---- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/explorer/documentListTreeItem.ts b/src/explorer/documentListTreeItem.ts index a100725e9..fb6fd8a49 100644 --- a/src/explorer/documentListTreeItem.ts +++ b/src/explorer/documentListTreeItem.ts @@ -188,6 +188,9 @@ export default class DocumentListTreeItem extends vscode.TreeItem return this._childrenCache; } + this.cacheIsUpToDate = true; + this._childrenCache = []; + let documents; try { @@ -197,13 +200,14 @@ export default class DocumentListTreeItem extends vscode.TreeItem {}, // No filter. { limit: this._maxDocumentsToShow } ); - } catch (err) { - return Promise.reject(err); + } catch (error) { + const printableError = error as { message: string }; + void vscode.window.showErrorMessage( + `Fetch documents failed: ${printableError.message}` + ); + return []; } - this.cacheIsUpToDate = true; - this._childrenCache = []; - if (documents) { documents.forEach((document, index) => { this._childrenCache.push( diff --git a/src/explorer/indexListTreeItem.ts b/src/explorer/indexListTreeItem.ts index 6152e1018..e5eaa439a 100644 --- a/src/explorer/indexListTreeItem.ts +++ b/src/explorer/indexListTreeItem.ts @@ -88,15 +88,26 @@ export default class IndexListTreeItem extends vscode.TreeItem return this._childrenCache; } - const fetchIndexes = util.promisify( - this._dataService.indexes.bind(this._dataService) - ); - const indexes = await fetchIndexes( - this._namespace, - {} // No options. - ); - this.cacheIsUpToDate = true; + this._childrenCache = []; + + let indexes; + + try { + const fetchIndexes = util.promisify( + this._dataService.indexes.bind(this._dataService) + ); + indexes = await fetchIndexes( + this._namespace, + {} // No options. + ); + } catch (error) { + const printableError = error as { message: string }; + void vscode.window.showErrorMessage( + `Fetch indexes failed: ${printableError.message}` + ); + return []; + } if (indexes) { this._childrenCache = sortTreeItemsByLabel( diff --git a/src/explorer/schemaTreeItem.ts b/src/explorer/schemaTreeItem.ts index a927a6478..e5f59f4d1 100644 --- a/src/explorer/schemaTreeItem.ts +++ b/src/explorer/schemaTreeItem.ts @@ -103,8 +103,12 @@ export default class SchemaTreeItem extends vscode.TreeItem {}, // No filter. { limit: MAX_DOCUMENTS_VISIBLE } ); - } catch (err) { - return Promise.reject(err); + } catch (error) { + const printableError = error as { message: string }; + void vscode.window.showErrorMessage( + `Get schema failed: ${printableError.message}` + ); + return []; } return new Promise((resolve, reject) => { diff --git a/src/test/suite/explorer/indexListTreeItem.test.ts b/src/test/suite/explorer/indexListTreeItem.test.ts index 06fcd0014..42b4f1bdc 100644 --- a/src/test/suite/explorer/indexListTreeItem.test.ts +++ b/src/test/suite/explorer/indexListTreeItem.test.ts @@ -1,5 +1,7 @@ import * as vscode from 'vscode'; +import { afterEach } from 'mocha'; import assert from 'assert'; +import sinon from 'sinon'; import { DataService } from 'mongodb-data-service'; import IndexListTreeItem from '../../../explorer/indexListTreeItem'; @@ -7,6 +9,10 @@ import IndexListTreeItem from '../../../explorer/indexListTreeItem'; const { contributes } = require('../../../../package.json'); suite('IndexListTreeItem Test Suite', () => { + afterEach(() => { + sinon.restore(); + }); + test('its context value should be in the package json', () => { let indexListRegisteredCommandInPackageJson = false; const testIndexListTreeItem = new IndexListTreeItem( @@ -120,13 +126,17 @@ suite('IndexListTreeItem Test Suite', () => { }); test('when theres an error fetching indexes, the error is thrown in the caller (no timeout)', async () => { - const expectedErrorMessage = 'Some error message indexes could throw'; + const expectedMessage = 'Some error message indexes could throw'; + const fakeVscodeErrorMessage = sinon.fake(); + + sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); + const testIndexListTreeItem = new IndexListTreeItem( 'pineapple', 'tasty_fruits', { indexes: (ns, opts, cb): void => { - cb(new Error(expectedErrorMessage), []); + cb(new Error(expectedMessage), []); } } as DataService, false, @@ -139,10 +149,12 @@ suite('IndexListTreeItem Test Suite', () => { try { await testIndexListTreeItem.getChildren(); - assert(false, 'Expected an error to be thrown'); + assert( + fakeVscodeErrorMessage.firstCall.args[0] === expectedMessage, + `Expected error message "${expectedMessage}" when disconnecting with no active connection, recieved "${fakeVscodeErrorMessage.firstCall.args[0]}"` + ); } catch (error) { - const printableError = error as { message: string }; - assert(printableError.message === expectedErrorMessage, `Expected error message to be '${expectedErrorMessage}' found '${printableError.message}'`); + assert(!!error, 'Expected an error disconnect response.'); } }); From 74c69b0a2aaacdccd58cb716dd4108458bd8fc86 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Fri, 7 Jan 2022 19:26:25 +0100 Subject: [PATCH 15/15] fix: change kerberosCanonicalizeHostname to boolean --- package-lock.json | 34 +++++++++---------- package.json | 2 +- .../legacy-connection-model.ts | 4 +-- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15a7c3a4c..53025b3b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "mongodb-cloud-info": "^1.1.3", "mongodb-connection-model": "^21.10.0", "mongodb-connection-string-url": "^2.4.0", - "mongodb-data-service": "^21.14.0", + "mongodb-data-service": "^21.15.0", "mongodb-ns": "^2.2.0", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", @@ -1545,9 +1545,9 @@ } }, "node_modules/@mongodb-js/compass-logging": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.5.0.tgz", - "integrity": "sha512-rHpfrWrHNDunXBnrB+44Dzu/w716AosiWVPvBhSIi/wTJmg/bopYMsOihkr8y27r3JPlraJXkpLOWbNk1n+kEA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.6.0.tgz", + "integrity": "sha512-B+wONmawuNq7UQAvLfp32OqPBLH8e/UX4N871qd4/TFG86qeACfFdrvhcspU/7HU04kYmTL/1HvggeiDZa6DjQ==", "dependencies": { "debug": "4.3.0", "is-electron-renderer": "^2.0.1", @@ -16037,18 +16037,18 @@ } }, "node_modules/mongodb-data-service": { - "version": "21.14.0", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.14.0.tgz", - "integrity": "sha512-m4tC2lCGBm0I9rxq10W1wgXBDcJ5I+tyXaSYWtLJnrF/Y/hgy2pynA0rDPu7sDlLNQBEuDyU844kXT0QCyz1EQ==", + "version": "21.15.0", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.15.0.tgz", + "integrity": "sha512-CrYD7AH4wH1oYQvjibpN5NNEh9GImtKZ/utQ8kT14nQCbSI7hlY4rA0DOY+uV8SHXjDEsC3FfMy7buo+4eNWhA==", "dependencies": { - "@mongodb-js/compass-logging": "^0.5.0", + "@mongodb-js/compass-logging": "^0.6.0", "@mongodb-js/ssh-tunnel": "^1.2.1", "async": "^3.2.0", "debug": "4.3.0", "get-port": "^5.1.1", "lodash": "^4.17.20", "mongodb-build-info": "^1.3.0", - "mongodb-connection-string-url": "^2.3.2", + "mongodb-connection-string-url": "^2.4.1", "mongodb-index-model": "^3.7.0", "mongodb-ns": "^2.2.0", "resolve-mongodb-srv": "^1.1.1", @@ -25267,9 +25267,9 @@ } }, "@mongodb-js/compass-logging": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.5.0.tgz", - "integrity": "sha512-rHpfrWrHNDunXBnrB+44Dzu/w716AosiWVPvBhSIi/wTJmg/bopYMsOihkr8y27r3JPlraJXkpLOWbNk1n+kEA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-0.6.0.tgz", + "integrity": "sha512-B+wONmawuNq7UQAvLfp32OqPBLH8e/UX4N871qd4/TFG86qeACfFdrvhcspU/7HU04kYmTL/1HvggeiDZa6DjQ==", "requires": { "debug": "4.3.0", "is-electron-renderer": "^2.0.1", @@ -37380,18 +37380,18 @@ } }, "mongodb-data-service": { - "version": "21.14.0", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.14.0.tgz", - "integrity": "sha512-m4tC2lCGBm0I9rxq10W1wgXBDcJ5I+tyXaSYWtLJnrF/Y/hgy2pynA0rDPu7sDlLNQBEuDyU844kXT0QCyz1EQ==", + "version": "21.15.0", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-21.15.0.tgz", + "integrity": "sha512-CrYD7AH4wH1oYQvjibpN5NNEh9GImtKZ/utQ8kT14nQCbSI7hlY4rA0DOY+uV8SHXjDEsC3FfMy7buo+4eNWhA==", "requires": { - "@mongodb-js/compass-logging": "^0.5.0", + "@mongodb-js/compass-logging": "^0.6.0", "@mongodb-js/ssh-tunnel": "^1.2.1", "async": "^3.2.0", "debug": "4.3.0", "get-port": "^5.1.1", "lodash": "^4.17.20", "mongodb-build-info": "^1.3.0", - "mongodb-connection-string-url": "^2.3.2", + "mongodb-connection-string-url": "^2.4.1", "mongodb-index-model": "^3.7.0", "mongodb-ns": "^2.2.0", "resolve-mongodb-srv": "^1.1.1", diff --git a/package.json b/package.json index 6299988aa..65f0f9ab6 100644 --- a/package.json +++ b/package.json @@ -921,7 +921,7 @@ "mongodb-cloud-info": "^1.1.3", "mongodb-connection-model": "^21.10.0", "mongodb-connection-string-url": "^2.4.0", - "mongodb-data-service": "^21.14.0", + "mongodb-data-service": "^21.15.0", "mongodb-ns": "^2.2.0", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", diff --git a/src/views/webview-app/connection-model/legacy-connection-model.ts b/src/views/webview-app/connection-model/legacy-connection-model.ts index 38df5ab88..96a074435 100644 --- a/src/views/webview-app/connection-model/legacy-connection-model.ts +++ b/src/views/webview-app/connection-model/legacy-connection-model.ts @@ -37,9 +37,7 @@ class LegacyConnectionModel { * Authentication. */ authStrategy: AUTH_STRATEGIES = AUTH_STRATEGY_DEFAULT; - // TODO: Fix kerberosCanonicalizeHostname type in DataService, should be boolean instaead of string. - // https://github.com/mongodb-js/compass/blob/main/packages/data-service/src/legacy/legacy-connection-model.ts#L94 - kerberosCanonicalizeHostname: any = false; + kerberosCanonicalizeHostname = false; kerberosPassword?: string; kerberosPrincipal?: string; kerberosServiceName?: string;