From 593a7d2e874c81c5c322ac7463d7fb6eca9b90f9 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 01:28:21 +0200 Subject: [PATCH 1/9] feat: add integration tests --- .gitignore | 2 + .prettierignore | 1 + package-lock.json | 694 +++++++++++++++++- package.json | 1 + src/server.ts | 20 +- src/state.ts | 4 +- src/tools/mongodb/mongodbTool.ts | 4 +- tests/integration/helpers.ts | 69 ++ tests/integration/inMemoryTransport.ts | 58 ++ tests/integration/server.test.ts | 47 ++ .../integration/tools/mongodb/connect.test.ts | 117 +++ tests/unit/index.test.ts | 2 +- 12 files changed, 1000 insertions(+), 19 deletions(-) create mode 100644 tests/integration/helpers.ts create mode 100644 tests/integration/inMemoryTransport.ts create mode 100644 tests/integration/server.test.ts create mode 100644 tests/integration/tools/mongodb/connect.test.ts diff --git a/.gitignore b/.gitignore index 26c2b9c7..ec17480c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ node_modules # Sensitive state.json + +tests/tmp diff --git a/.prettierignore b/.prettierignore index 9eb4595e..ffb0dcd0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ dist coverage package-lock.json +tests/tmp diff --git a/package-lock.json b/package-lock.json index 17a06913..0cc61f32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "globals": "^16.0.0", "jest": "^29.7.0", "jest-environment-node": "^29.7.0", + "mongodb-runner": "^5.8.2", "openapi-types": "^12.1.3", "openapi-typescript": "^7.6.1", "prettier": "^3.5.3", @@ -3069,6 +3070,66 @@ "system-ca": "^2.0.1" } }, + "node_modules/@mongodb-js/mongodb-downloader": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-0.3.9.tgz", + "integrity": "sha512-6lEIESINiIAeQUw95+hkfxG6129r6KiPU2TNOcxb30PsGgFHPJFg7QY8UoSQXjDE9YaENlr6oQm3c1XDixWeEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "decompress": "^4.2.1", + "mongodb-download-url": "^1.5.7", + "node-fetch": "^2.7.0", + "tar": "^6.1.15" + } + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@mongodb-js/oidc-http-server-pages": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@mongodb-js/oidc-http-server-pages/-/oidc-http-server-pages-1.1.4.tgz", @@ -6731,6 +6792,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "devOptional": true, "funding": [ { "type": "github", @@ -6745,8 +6807,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/basic-ftp": { "version": "5.0.5", @@ -6962,6 +7023,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -6977,12 +7039,46 @@ } ], "license": "MIT", - "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7354,6 +7450,13 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7480,6 +7583,13 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -7612,6 +7722,26 @@ "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==", "dev": true }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -7628,6 +7758,219 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tar/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/decompress-tar/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-tar/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -7905,8 +8248,8 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "once": "^1.4.0" } @@ -8511,6 +8854,16 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -8547,6 +8900,16 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -8737,8 +9100,34 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -9147,6 +9536,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "devOptional": true, "funding": [ { "type": "github", @@ -9161,8 +9551,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -9400,6 +9789,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9456,6 +9852,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10788,6 +11191,56 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -10945,6 +11398,68 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/mongodb-download-url": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/mongodb-download-url/-/mongodb-download-url-1.5.7.tgz", + "integrity": "sha512-GpQJAfYmfYwqVXUyfIUQXe5kFoiCK5kORBJnPixAmQGmabZix6fBTpX7vSy3J46VgiAe+VEOjSikK/TcGKTw+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "minimist": "^1.2.8", + "node-fetch": "^2.7.0", + "semver": "^7.7.1" + }, + "bin": { + "mongodb-download-url": "bin/mongodb-download-url.js" + } + }, + "node_modules/mongodb-download-url/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/mongodb-download-url/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mongodb-download-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/mongodb-download-url/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/mongodb-log-writer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-2.4.1.tgz", @@ -10970,6 +11485,24 @@ "integrity": "sha512-L4L3byUH/V/L6YH954NBM/zJpyDHQYmm9eUCxMxqMUfiYCPtmCK1sv/LhxE7UonOkFNEAT6eq2J8gIWGUpHcJA==", "license": "Apache-2.0" }, + "node_modules/mongodb-runner": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.8.2.tgz", + "integrity": "sha512-Fsr87S3P75jAd/D1ly0/lODpjtFpHd+q9Ml2KjQQmPeGisdjCDO9bFOJ1F9xrdtvww2eeR8xKBXrtZYdP5P59A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/mongodb-downloader": "^0.3.9", + "@mongodb-js/saslprep": "^1.2.2", + "debug": "^4.4.0", + "mongodb": "^6.9.0", + "mongodb-connection-string-url": "^3.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "mongodb-runner": "bin/runner.js" + } + }, "node_modules/mongodb-schema": { "version": "12.6.2", "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.6.2.tgz", @@ -11740,6 +12273,13 @@ "node": ">=16" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-scrollbar": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", @@ -11767,6 +12307,39 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -12006,6 +12579,13 @@ "node": ">=6" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -12754,6 +13334,20 @@ "loose-envify": "^1.1.0" } }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -13423,6 +14017,16 @@ "node": ">=8" } }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -13652,6 +14256,24 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", @@ -13682,6 +14304,16 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13697,6 +14329,13 @@ "node": ">=8" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -13716,6 +14355,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14021,6 +14667,17 @@ "node": ">=0.8.0" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici": { "version": "6.21.2", "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", @@ -14392,6 +15049,16 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14456,6 +15123,17 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 40c350b5..3f40f9bb 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "globals": "^16.0.0", "jest": "^29.7.0", "jest-environment-node": "^29.7.0", + "mongodb-runner": "^5.8.2", "openapi-types": "^12.1.3", "openapi-typescript": "^7.6.1", "prettier": "^3.5.3", diff --git a/src/server.ts b/src/server.ts index 0415f038..49055245 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,6 +12,7 @@ export class Server { state: State = defaultState; apiClient: ApiClient | undefined = undefined; initialized: boolean = false; + private server?: McpServer; private async init() { if (this.initialized) { @@ -36,7 +37,8 @@ export class Server { this.initialized = true; } - private createMcpServer(): McpServer { + async connect(transport: Transport) { + await this.init(); const server = new McpServer({ name: "MongoDB Atlas", version: config.version, @@ -47,15 +49,19 @@ export class Server { registerAtlasTools(server, this.state, this.apiClient!); registerMongoDBTools(server, this.state); - return server; - } - - async connect(transport: Transport) { - await this.init(); - const server = this.createMcpServer(); await server.connect(transport); await initializeLogger(server); + this.server = server; logger.info(mongoLogId(1_000_004), "server", `Server started with transport ${transport.constructor.name}`); } + + async close(): Promise { + try { + await this.state.serviceProvider?.close(true); + } catch { + // Ignore errors during service provider close + } + await this.server?.close(); + } } diff --git a/src/state.ts b/src/state.ts index 9cc79626..1a1968da 100644 --- a/src/state.ts +++ b/src/state.ts @@ -15,12 +15,12 @@ interface Credentials { export class State { private entry = new AsyncEntry("mongodb-mcp", "credentials"); - credentials: Credentials = { + public credentials: Credentials = { auth: { status: "not_auth", }, }; - serviceProvider?: NodeDriverServiceProvider; + public serviceProvider?: NodeDriverServiceProvider; public async persistCredentials(): Promise { await this.entry.setPassword(JSON.stringify(this.credentials)); diff --git a/src/tools/mongodb/mongodbTool.ts b/src/tools/mongodb/mongodbTool.ts index 5ca8a1c5..e445f970 100644 --- a/src/tools/mongodb/mongodbTool.ts +++ b/src/tools/mongodb/mongodbTool.ts @@ -56,7 +56,9 @@ export abstract class MongoDBToolBase extends ToolBase { const provider = await NodeDriverServiceProvider.connect(connectionString, { productDocsLink: "https://docs.mongodb.com/todo-mcp", productName: "MongoDB MCP", - readConcern: config.connectOptions.readConcern, + readConcern: { + level: config.connectOptions.readConcern, + }, readPreference: config.connectOptions.readPreference, writeConcern: { w: config.connectOptions.writeConcern, diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts new file mode 100644 index 00000000..d45495d0 --- /dev/null +++ b/tests/integration/helpers.ts @@ -0,0 +1,69 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "./inMemoryTransport.js"; +import { Server } from "../../src/server.js"; +import runner, { MongoCluster } from "mongodb-runner"; +import path from "path"; +import fs from "fs/promises"; + +export async function setupIntegrationTest(): Promise<{ + client: Client; + server: Server; +}> { + const clientTransport = new InMemoryTransport(); + const serverTransport = new InMemoryTransport(); + + await serverTransport.start(); + await clientTransport.start(); + + clientTransport.output.pipeTo(serverTransport.input); + serverTransport.output.pipeTo(clientTransport.input); + + const client = new Client( + { + name: "test-client", + version: "1.2.3", + }, + { + capabilities: {}, + } + ); + + const server = new Server(); + await server.connect(serverTransport); + await client.connect(clientTransport); + + return { + client, + server, + }; +} + +export async function runMongoDB(): Promise { + const tmpDir = path.join(__dirname, "..", "tmp"); + await fs.mkdir(tmpDir, { recursive: true }); + + try { + const cluster = await MongoCluster.start({ + tmpDir: path.join(tmpDir, "mongodb-runner", "dbs"), + logDir: path.join(tmpDir, "mongodb-runner", "logs"), + topology: "standalone", + }); + + return cluster; + } catch (err) { + throw err; + } +} + +export function validateToolResponse(content: unknown): string { + expect(Array.isArray(content)).toBe(true); + + const response = content as Array<{ type: string; text: string }>; + for (const item of response) { + expect(item).toHaveProperty("type"); + expect(item).toHaveProperty("text"); + expect(item.type).toBe("text"); + } + + return response.map((item) => item.text).join("\n"); +} diff --git a/tests/integration/inMemoryTransport.ts b/tests/integration/inMemoryTransport.ts new file mode 100644 index 00000000..a12c4625 --- /dev/null +++ b/tests/integration/inMemoryTransport.ts @@ -0,0 +1,58 @@ +import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; + +export class InMemoryTransport implements Transport { + private outputController: ReadableStreamDefaultController; + + private startPromise: Promise; + + public output: ReadableStream; + public input: WritableStream; + + constructor() { + const [inputReady, inputResolve] = InMemoryTransport.getPromise(); + const [outputReady, outputResolve] = InMemoryTransport.getPromise(); + + this.output = new ReadableStream({ + start: (controller) => { + this.outputController = controller; + outputResolve(); + }, + }); + + this.input = new WritableStream({ + write: (message) => this.onmessage?.(message), + start: () => { + inputResolve(); + }, + }); + + this.startPromise = Promise.all([inputReady, outputReady]); + } + + async start(): Promise { + await this.startPromise; + } + + send(message: JSONRPCMessage): Promise { + this.outputController.enqueue(message); + return Promise.resolve(); + } + + async close(): Promise { + this.outputController.close(); + this.onclose?.(); + } + onclose?: (() => void) | undefined; + onerror?: ((error: Error) => void) | undefined; + onmessage?: ((message: JSONRPCMessage) => void) | undefined; + sessionId?: string | undefined; + + private static getPromise(): [Promise, resolve: () => void] { + let resolve: () => void; + const promise = new Promise((res) => { + resolve = res; + }); + return [promise, resolve!]; + } +} diff --git a/tests/integration/server.test.ts b/tests/integration/server.test.ts new file mode 100644 index 00000000..90a01e7f --- /dev/null +++ b/tests/integration/server.test.ts @@ -0,0 +1,47 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { Server } from "../../src/server.js"; +import { setupIntegrationTest } from "./helpers.js"; + +let client: Client; +let server: Server; + +beforeEach(async () => { + ({ client, server } = await setupIntegrationTest()); +}); + +afterEach(async () => { + await client?.close(); + await server?.close(); +}); + +describe("Server integration test", () => { + describe("list capabilities", () => { + it("should return positive number of tools", async () => { + const tools = await client.listTools(); + expect(tools).toBeDefined(); + expect(tools.tools.length).toBeGreaterThan(0); + }); + + it("should return no resources", async () => { + await expect(() => client.listResources()).rejects.toMatchObject({ + message: "MCP error -32601: Method not found", + }); + }); + + it("should return no prompts", async () => { + await expect(() => client.listPrompts()).rejects.toMatchObject({ + message: "MCP error -32601: Method not found", + }); + }); + + it("should return capabilities", async () => { + const capabilities = client.getServerCapabilities(); + expect(capabilities).toBeDefined(); + expect(capabilities?.completions).toBeUndefined(); + expect(capabilities?.experimental).toBeUndefined(); + expect(capabilities?.tools).toBeDefined(); + expect(capabilities?.logging).toBeDefined(); + expect(capabilities?.prompts).toBeUndefined(); + }); + }); +}); diff --git a/tests/integration/tools/mongodb/connect.test.ts b/tests/integration/tools/mongodb/connect.test.ts new file mode 100644 index 00000000..484d6c66 --- /dev/null +++ b/tests/integration/tools/mongodb/connect.test.ts @@ -0,0 +1,117 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { Server } from "../../../../src/server.js"; +import { runMongoDB, setupIntegrationTest, validateToolResponse } from "../../helpers.js"; +import runner from "mongodb-runner"; + +import defaultState from "../../../../src/state.js"; +import config from "../../../../src/config.js"; + +let client: Client; +let server: Server; + +let loadCredentialsMock: jest.SpyInstance | undefined; +let cluster: runner.MongoCluster; + +beforeAll(async () => { + jest.setTimeout(30000); + cluster = await runMongoDB(); +}); + +afterEach(async () => { + loadCredentialsMock?.mockRestore(); + await client?.close(); + await server?.close(); +}); + +afterAll(async () => { + await cluster.close(); +}); + +describe("Connect tool", () => { + describe("with default config", () => { + beforeEach(async () => { + loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(async () => true); + + ({ client, server } = await setupIntegrationTest()); + }); + + it("should have correct metadata", async () => { + const tools = await client.listTools(); + const connectTool = tools.tools.find((tool) => tool.name === "connect"); + expect(connectTool).toBeDefined(); + expect(connectTool!.description).toBe("Connect to a MongoDB instance"); + expect(connectTool!.inputSchema.type).toBe("object"); + expect(connectTool!.inputSchema.properties).toBeDefined(); + + const propertyNames = Object.keys(connectTool!.inputSchema.properties!); + expect(propertyNames).toHaveLength(1); + expect(propertyNames[0]).toBe("connectionStringOrClusterName"); + + const connectionStringOrClusterNameProp = connectTool!.inputSchema.properties![propertyNames[0]] as { + type: string; + description: string; + }; + expect(connectionStringOrClusterNameProp.type).toBe("string"); + expect(connectionStringOrClusterNameProp.description).toContain("MongoDB connection string"); + expect(connectionStringOrClusterNameProp.description).toContain("cluster name"); + }); + + describe("without connection string", () => { + it("prompts for connection string", async () => { + const response = await client.callTool({ name: "connect", arguments: {} }); + const content = validateToolResponse(response.content); + expect(content).toContain("No connection details provided"); + expect(content).toContain("mongodb://localhost:27017"); + }); + }); + + describe("with connection string", () => { + it("connects to the database", async () => { + const response = await client.callTool({ + name: "connect", + arguments: { connectionStringOrClusterName: cluster.connectionString }, + }); + const content = validateToolResponse(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(cluster.connectionString); + }); + }); + + describe("with invalid connection string", () => { + it("returns error message", async () => { + const response = await client.callTool({ + name: "connect", + arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" }, + }); + const content = validateToolResponse(response.content); + expect(content).toContain("Error running connect"); + }); + }); + }); + + describe("with connection string in config", () => { + beforeEach(async () => { + config.connectionString = cluster.connectionString; + + ({ client, server } = await setupIntegrationTest()); + }); + + it("uses the connection string from config", async () => { + const response = await client.callTool({ name: "connect", arguments: {} }); + const content = validateToolResponse(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(cluster.connectionString); + }); + + it("prefers connection string from arguments", async () => { + const newConnectionString = `${cluster.connectionString}?appName=foo-bar`; + const response = await client.callTool({ + name: "connect", + arguments: { connectionStringOrClusterName: newConnectionString }, + }); + const content = validateToolResponse(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(newConnectionString); + }); + }); +}); diff --git a/tests/unit/index.test.ts b/tests/unit/index.test.ts index 8773fd75..2e307bfb 100644 --- a/tests/unit/index.test.ts +++ b/tests/unit/index.test.ts @@ -1,6 +1,6 @@ import { describe, it } from "@jest/globals"; import { runServer } from "../../src/index"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // mock the StdioServerTransport jest.mock("@modelcontextprotocol/sdk/server/stdio"); From 938723252162d575e8b08368684d2e8089f2ff4c Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 12:11:36 +0200 Subject: [PATCH 2/9] move inits inside describe, fix timeout setting --- tests/integration/server.test.ts | 20 +++++------ .../integration/tools/mongodb/connect.test.ts | 33 +++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/tests/integration/server.test.ts b/tests/integration/server.test.ts index 90a01e7f..7b9d1b46 100644 --- a/tests/integration/server.test.ts +++ b/tests/integration/server.test.ts @@ -2,19 +2,19 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Server } from "../../src/server.js"; import { setupIntegrationTest } from "./helpers.js"; -let client: Client; -let server: Server; +describe("Server integration test", () => { + let client: Client; + let server: Server; -beforeEach(async () => { - ({ client, server } = await setupIntegrationTest()); -}); + beforeEach(async () => { + ({ client, server } = await setupIntegrationTest()); + }); -afterEach(async () => { - await client?.close(); - await server?.close(); -}); + afterEach(async () => { + await client?.close(); + await server?.close(); + }); -describe("Server integration test", () => { describe("list capabilities", () => { it("should return positive number of tools", async () => { const tools = await client.listTools(); diff --git a/tests/integration/tools/mongodb/connect.test.ts b/tests/integration/tools/mongodb/connect.test.ts index 484d6c66..5ebf7947 100644 --- a/tests/integration/tools/mongodb/connect.test.ts +++ b/tests/integration/tools/mongodb/connect.test.ts @@ -6,28 +6,27 @@ import runner from "mongodb-runner"; import defaultState from "../../../../src/state.js"; import config from "../../../../src/config.js"; -let client: Client; -let server: Server; +describe("Connect tool", () => { + let client: Client; + let server: Server; -let loadCredentialsMock: jest.SpyInstance | undefined; -let cluster: runner.MongoCluster; + let loadCredentialsMock: jest.SpyInstance | undefined; + let cluster: runner.MongoCluster; -beforeAll(async () => { - jest.setTimeout(30000); - cluster = await runMongoDB(); -}); + beforeAll(async () => { + cluster = await runMongoDB(); + }, 60_000); -afterEach(async () => { - loadCredentialsMock?.mockRestore(); - await client?.close(); - await server?.close(); -}); + afterEach(async () => { + loadCredentialsMock?.mockRestore(); + await client?.close(); + await server?.close(); + }); -afterAll(async () => { - await cluster.close(); -}); + afterAll(async () => { + await cluster.close(); + }); -describe("Connect tool", () => { describe("with default config", () => { beforeEach(async () => { loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(async () => true); From 6d89b3e038a40cba5f279f1b991eb1c456221edc Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 12:57:46 +0200 Subject: [PATCH 3/9] Install keyring deps on ubuntu --- .github/workflows/code_health.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/code_health.yaml b/.github/workflows/code_health.yaml index ff50e14b..252d3544 100644 --- a/.github/workflows/code_health.yaml +++ b/.github/workflows/code_health.yaml @@ -23,6 +23,11 @@ jobs: run-tests: runs-on: ubuntu-latest steps: + - name: Install keyring deps + run: | + sudo apt update -y + sudo apt install -y gnome-keyring libdbus-1-dev + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v4 - uses: actions/setup-node@v4 From a14ca33c3fc832aa0bcdaac5ae605151dd5664ae Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 13:17:17 +0200 Subject: [PATCH 4/9] Tweak state loading/saving --- .github/workflows/code_health.yaml | 72 ++++++++++--------- .github/workflows/publish.yaml | 50 ++++++------- .prettierrc.json | 7 ++ src/state.ts | 11 +-- tests/integration/helpers.ts | 15 ++++ .../integration/tools/mongodb/connect.test.ts | 12 ++-- 6 files changed, 95 insertions(+), 72 deletions(-) diff --git a/.github/workflows/code_health.yaml b/.github/workflows/code_health.yaml index 252d3544..998b1158 100644 --- a/.github/workflows/code_health.yaml +++ b/.github/workflows/code_health.yaml @@ -1,40 +1,44 @@ --- name: Code Health on: - push: - branches: - - main - pull_request: + push: + branches: + - main + pull_request: jobs: - check-style: - runs-on: ubuntu-latest - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - cache: "npm" - - name: Install dependencies - run: npm ci - - name: Run style check - run: npm run check + check-style: + runs-on: ubuntu-latest + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run style check + run: npm run check - run-tests: - runs-on: ubuntu-latest - steps: - - name: Install keyring deps - run: | - sudo apt update -y - sudo apt install -y gnome-keyring libdbus-1-dev + run-tests: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + - name: Install keyring deps on Ubuntu + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt update -y + sudo apt install -y gnome-keyring libdbus-1-dev - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - cache: "npm" - - name: Install dependencies - run: npm ci - - name: Run tests - run: npm test + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm test diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 02a99122..45fb0726 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,29 +1,29 @@ --- name: Publish on: - push: - tags: - - v* + push: + tags: + - v* jobs: - publish: - runs-on: ubuntu-latest - environment: Production - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - registry-url: "https://registry.npmjs.org" - cache: "npm" - - name: Build package - run: | - npm ci - npm run build - - name: Publish to NPM - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish Github release - run: | - gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes + publish: + runs-on: ubuntu-latest + environment: Production + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + registry-url: "https://registry.npmjs.org" + cache: "npm" + - name: Build package + run: | + npm ci + npm run build + - name: Publish to NPM + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Github release + run: | + gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes diff --git a/.prettierrc.json b/.prettierrc.json index b52f3251..076027c3 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -18,6 +18,13 @@ "tabWidth": 2, "printWidth": 80 } + }, + { + "files": "*.yaml", + "options": { + "tabWidth": 2, + "printWidth": 80 + } } ] } diff --git a/src/state.ts b/src/state.ts index f99f3cb6..bdd167b0 100644 --- a/src/state.ts +++ b/src/state.ts @@ -13,20 +13,21 @@ export class State { serviceProvider?: NodeDriverServiceProvider; public async persistCredentials(): Promise { - await this.entry.setPassword(JSON.stringify(this.credentials)); + try { + await this.entry.setPassword(JSON.stringify(this.credentials)); + } catch (err) { + logger.error(mongoLogId(1_000_008), "state", `Failed to save state: ${err}`); + } } - public async loadCredentials(): Promise { + public async loadCredentials(): Promise { try { const data = await this.entry.getPassword(); if (data) { this.credentials = JSON.parse(data); } - - return true; } catch (err: unknown) { logger.error(mongoLogId(1_000_007), "state", `Failed to load state: ${err}`); - return false; } } } diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index d45495d0..0c470bc7 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -4,11 +4,19 @@ import { Server } from "../../src/server.js"; import runner, { MongoCluster } from "mongodb-runner"; import path from "path"; import fs from "fs/promises"; +import defaultState from "../../src/state.js"; export async function setupIntegrationTest(): Promise<{ client: Client; server: Server; + teardown: () => Promise; }> { + // Mock the load/persist credentials method to avoid state loading/restore messing up with the tests + const loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(() => Promise.resolve()); + const saveCredentialsMock = jest + .spyOn(defaultState, "persistCredentials") + .mockImplementation(() => Promise.resolve()); + const clientTransport = new InMemoryTransport(); const serverTransport = new InMemoryTransport(); @@ -35,6 +43,13 @@ export async function setupIntegrationTest(): Promise<{ return { client, server, + teardown: async () => { + await client.close(); + await server.close(); + + loadCredentialsMock.mockRestore(); + saveCredentialsMock.mockRestore(); + }, }; } diff --git a/tests/integration/tools/mongodb/connect.test.ts b/tests/integration/tools/mongodb/connect.test.ts index 5ebf7947..f3aede07 100644 --- a/tests/integration/tools/mongodb/connect.test.ts +++ b/tests/integration/tools/mongodb/connect.test.ts @@ -9,8 +9,8 @@ import config from "../../../../src/config.js"; describe("Connect tool", () => { let client: Client; let server: Server; + let serverClientTeardown: () => Promise; - let loadCredentialsMock: jest.SpyInstance | undefined; let cluster: runner.MongoCluster; beforeAll(async () => { @@ -18,9 +18,7 @@ describe("Connect tool", () => { }, 60_000); afterEach(async () => { - loadCredentialsMock?.mockRestore(); - await client?.close(); - await server?.close(); + await serverClientTeardown?.(); }); afterAll(async () => { @@ -29,9 +27,7 @@ describe("Connect tool", () => { describe("with default config", () => { beforeEach(async () => { - loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(async () => true); - - ({ client, server } = await setupIntegrationTest()); + ({ client, server, teardown: serverClientTeardown } = await setupIntegrationTest()); }); it("should have correct metadata", async () => { @@ -92,7 +88,7 @@ describe("Connect tool", () => { beforeEach(async () => { config.connectionString = cluster.connectionString; - ({ client, server } = await setupIntegrationTest()); + ({ client, server, teardown: serverClientTeardown } = await setupIntegrationTest()); }); it("uses the connection string from config", async () => { From 88a2ecea073e7f430cd17b0762987175a12cd7be Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 13:18:43 +0200 Subject: [PATCH 5/9] Disable monitor on windows --- .github/workflows/code_health.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/code_health.yaml b/.github/workflows/code_health.yaml index 998b1158..5a99be15 100644 --- a/.github/workflows/code_health.yaml +++ b/.github/workflows/code_health.yaml @@ -24,9 +24,11 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: false runs-on: ${{ matrix.os }} steps: - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + if: matrix.os != 'windows-latest' - name: Install keyring deps on Ubuntu if: matrix.os == 'ubuntu-latest' run: | From 302b6f7d799a8bf1563a6bc60aa05d01cc589d36 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 13:22:22 +0200 Subject: [PATCH 6/9] fix lint --- tests/integration/tools/mongodb/connect.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/integration/tools/mongodb/connect.test.ts b/tests/integration/tools/mongodb/connect.test.ts index f3aede07..a13b5cb0 100644 --- a/tests/integration/tools/mongodb/connect.test.ts +++ b/tests/integration/tools/mongodb/connect.test.ts @@ -3,12 +3,10 @@ import { Server } from "../../../../src/server.js"; import { runMongoDB, setupIntegrationTest, validateToolResponse } from "../../helpers.js"; import runner from "mongodb-runner"; -import defaultState from "../../../../src/state.js"; import config from "../../../../src/config.js"; describe("Connect tool", () => { let client: Client; - let server: Server; let serverClientTeardown: () => Promise; let cluster: runner.MongoCluster; @@ -27,7 +25,7 @@ describe("Connect tool", () => { describe("with default config", () => { beforeEach(async () => { - ({ client, server, teardown: serverClientTeardown } = await setupIntegrationTest()); + ({ client, teardown: serverClientTeardown } = await setupIntegrationTest()); }); it("should have correct metadata", async () => { @@ -88,7 +86,7 @@ describe("Connect tool", () => { beforeEach(async () => { config.connectionString = cluster.connectionString; - ({ client, server, teardown: serverClientTeardown } = await setupIntegrationTest()); + ({ client, teardown: serverClientTeardown } = await setupIntegrationTest()); }); it("uses the connection string from config", async () => { From 60c0ba90fd8f89101cdd27f2393b45ed9eb0872f Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 13:25:32 +0200 Subject: [PATCH 7/9] fix lint again --- tests/integration/tools/mongodb/connect.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/tools/mongodb/connect.test.ts b/tests/integration/tools/mongodb/connect.test.ts index a13b5cb0..d304c266 100644 --- a/tests/integration/tools/mongodb/connect.test.ts +++ b/tests/integration/tools/mongodb/connect.test.ts @@ -1,5 +1,4 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { Server } from "../../../../src/server.js"; import { runMongoDB, setupIntegrationTest, validateToolResponse } from "../../helpers.js"; import runner from "mongodb-runner"; From e7109dd1998dd4b62985a217de39fd8531c40dba Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 13:28:25 +0200 Subject: [PATCH 8/9] Optionally mock the state store --- tests/integration/helpers.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index 0c470bc7..cc944458 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -6,16 +6,20 @@ import path from "path"; import fs from "fs/promises"; import defaultState from "../../src/state.js"; -export async function setupIntegrationTest(): Promise<{ +export async function setupIntegrationTest({ mockStateStore = true }: { mockStateStore: boolean }): Promise<{ client: Client; server: Server; teardown: () => Promise; }> { - // Mock the load/persist credentials method to avoid state loading/restore messing up with the tests - const loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(() => Promise.resolve()); - const saveCredentialsMock = jest - .spyOn(defaultState, "persistCredentials") - .mockImplementation(() => Promise.resolve()); + let loadCredentialsMock: jest.SpyInstance | undefined; + let saveCredentialsMock: jest.SpyInstance | undefined; + if (mockStateStore) { + // Mock the load/persist credentials method to avoid state loading/restore messing up with the tests + loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(() => Promise.resolve()); + saveCredentialsMock = jest + .spyOn(defaultState, "persistCredentials") + .mockImplementation(() => Promise.resolve()); + } const clientTransport = new InMemoryTransport(); const serverTransport = new InMemoryTransport(); @@ -47,8 +51,8 @@ export async function setupIntegrationTest(): Promise<{ await client.close(); await server.close(); - loadCredentialsMock.mockRestore(); - saveCredentialsMock.mockRestore(); + loadCredentialsMock?.mockRestore(); + saveCredentialsMock?.mockRestore(); }, }; } From 28706c24589c33d29342ca50d36690d4656b96c0 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 13:31:45 +0200 Subject: [PATCH 9/9] Add default --- tests/integration/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index cc944458..d20da21a 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -6,7 +6,7 @@ import path from "path"; import fs from "fs/promises"; import defaultState from "../../src/state.js"; -export async function setupIntegrationTest({ mockStateStore = true }: { mockStateStore: boolean }): Promise<{ +export async function setupIntegrationTest({ mockStateStore = true }: { mockStateStore?: boolean } = {}): Promise<{ client: Client; server: Server; teardown: () => Promise;