diff --git a/package-lock.json b/package-lock.json index c60df85143..6585d4f1d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "husky": "^9.0.11", "mocha": "^10.2.0", "mongodb": "^6.19.0", - "mongodb-runner": "^5.7.1", + "mongodb-runner": "^6.0.0", "node-gyp": "^9.0.0 || ^10.2.0", "nyc": "^15.1.0", "pkg-up": "^3.1.0", @@ -7282,9 +7282,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -25434,10 +25434,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "license": "MIT" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minimist-options": { "version": "4.1.0", @@ -25875,20 +25878,43 @@ } }, "node_modules/mongodb-download-url": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/mongodb-download-url/-/mongodb-download-url-1.5.5.tgz", - "integrity": "sha512-8HLqKVVuKQBinKRZbDu0YSzwLfD/Wb//vOIm3CMk0/2AzZzp0pg+8E+DAkx7VLEdoyuPVWLU5v/doODjXlPYSA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mongodb-download-url/-/mongodb-download-url-1.7.0.tgz", + "integrity": "sha512-Dj9l3/MzvgAO+no7zaBIbQL0Ilsb0jo5Y8WijkB/sCnagoh3KgnN0Vxu5awglK/75QrEObduf9wa+U0fjxvYMw==", "license": "Apache-2.0", "dependencies": { - "debug": "^4.1.1", - "minimist": "^1.2.3", - "node-fetch": "^2.6.1", - "semver": "^7.1.1" + "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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mongodb-download-url/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/mongodb-log-writer": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-2.4.3.tgz", @@ -25909,15 +25935,15 @@ "optional": true }, "node_modules/mongodb-runner": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.7.1.tgz", - "integrity": "sha512-/MBEP2DcMpNbpSsXqG+lgFqYehCd2qasdWIfKuv4jGKwLoDPv/mWoQYAQDFAC2xaxjb576Y2LwUAeYeB1KPZdg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-6.0.0.tgz", + "integrity": "sha512-ijhBVCcTWRlauxp4UdIuktfPjEqlt8yOo0u7XyE99HdaITzL0BjS4x/dOyNzddewC6gw5wxH1uYx+Uo7GfeUnw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@mongodb-js/mongodb-downloader": "^0.3.7", - "@mongodb-js/saslprep": "^1.1.9", - "debug": "^4.3.4", + "@mongodb-js/mongodb-downloader": "^1.0.0", + "@mongodb-js/saslprep": "^1.3.2", + "debug": "^4.4.0", "mongodb": "^6.9.0", "mongodb-connection-string-url": "^3.0.0", "yargs": "^17.7.2" @@ -25926,6 +25952,21 @@ "mongodb-runner": "bin/runner.js" } }, + "node_modules/mongodb-runner/node_modules/@mongodb-js/mongodb-downloader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-1.0.0.tgz", + "integrity": "sha512-xz4zr/5RWfAaHp9Kf7XPaLNxKaisl2NJGFEDQRNjB/ru79LdpLQbbSjQazTOl6JLcBvAjNNvauTeTwmgEvSUlA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "decompress": "^4.2.1", + "mongodb-download-url": "^1.7.0", + "node-fetch": "^2.7.0", + "proper-lockfile": "^4.1.2", + "tar": "^6.1.15" + } + }, "node_modules/mongodb-runner/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -25941,6 +25982,31 @@ "node": ">=12" } }, + "node_modules/mongodb-runner/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mongodb-runner/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/mongodb-runner/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -29102,6 +29168,18 @@ "node": ">= 8" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", diff --git a/package.json b/package.json index ee94a7f924..a7b666ea7d 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "husky": "^9.0.11", "mocha": "^10.2.0", "mongodb": "^6.19.0", - "mongodb-runner": "^5.7.1", + "mongodb-runner": "^6.0.0", "node-gyp": "^9.0.0 || ^10.2.0", "nyc": "^15.1.0", "pkg-up": "^3.1.0", diff --git a/packages/build/src/download-center/constants.ts b/packages/build/src/download-center/constants.ts index cbe8cd328f..fc4c2f64dd 100644 --- a/packages/build/src/download-center/constants.ts +++ b/packages/build/src/download-center/constants.ts @@ -1,4 +1,4 @@ -const fallback = require('./fallback.json'); +import fallback from './fallback.json'; /** * The S3 bucket for download center configurations. diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts index 3ca60fb33d..d2208f8632 100644 --- a/packages/build/src/index.ts +++ b/packages/build/src/index.ts @@ -9,6 +9,7 @@ import type { Config, PackageVariant } from './config'; import { updateJsonFeedCTA } from './download-center'; import Ajv from 'ajv'; +export { downloadCryptLibrary } from './packaging/download-crypt-library'; export { getArtifactUrl, downloadMongoDb }; const validCommands: (ReleaseCommand | 'trigger-release' | 'update-cta')[] = [ diff --git a/packages/build/tsconfig.json b/packages/build/tsconfig.json index 22cb4c367c..dc5d8a2795 100644 --- a/packages/build/tsconfig.json +++ b/packages/build/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "./lib", "allowJs": true, - "removeComments": false + "removeComments": false, + "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["./src/**/*.spec.*"] diff --git a/testing/.eslintrc.js b/testing/.eslintrc.js new file mode 100644 index 0000000000..0ce9c7915e --- /dev/null +++ b/testing/.eslintrc.js @@ -0,0 +1,10 @@ +const { fixCygwinPath } = require('@mongodb-js/eslint-config-mongosh/utils'); + +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-mongosh'], + parserOptions: { + tsconfigRootDir: fixCygwinPath(__dirname), + project: ['./tsconfig.json'], + }, +}; diff --git a/testing/.prettierrc.json b/testing/.prettierrc.json new file mode 100644 index 0000000000..dfae21d047 --- /dev/null +++ b/testing/.prettierrc.json @@ -0,0 +1 @@ +"@mongodb-js/prettier-config-devtools" diff --git a/testing/certificates/README.md b/testing/certificates/README.md index 83ac3804ee..dee8b982d3 100644 --- a/testing/certificates/README.md +++ b/testing/certificates/README.md @@ -5,6 +5,7 @@ This directory contains all certificates and keys used in testing. To recreate the certificates follow the steps outlined below. ## Setup CA + 1. Create a new key to use for the CA: ``` openssl genrsa -out ca.key 4096 @@ -13,10 +14,11 @@ To recreate the certificates follow the steps outlined below. ``` openssl req -new -x509 -key ca.key -out ca.crt -days 99999 ``` - * Organization Name: `MongoDB` - * Organizational Unit Name: `DevTools` - * Common Name: `DevTools CA` + - Organization Name: `MongoDB` + - Organizational Unit Name: `DevTools` + - Common Name: `DevTools CA` 3. To sign and revoke certificates, an openssl config files is required. Create `ca.cnf` with the following content: + ``` [ca] default_ca=CA_default @@ -43,12 +45,14 @@ To recreate the certificates follow the steps outlined below. commonName=supplied emailAddress=optional ``` + 4. Ensure the `ca.db` file exists: ``` touch ca.db ``` ## Setup Server Certificate + 1. Create a new key to use for the server: ``` openssl genrsa -out server.key 4096 @@ -57,9 +61,9 @@ To recreate the certificates follow the steps outlined below. ``` openssl req -new -key server.key -out server.csr -days 99999 ``` - * Organization Name: `MongoDB` - * Organizational Unit Name: `DevTools` - * Common Name: `localhost` + - Organization Name: `MongoDB` + - Organizational Unit Name: `DevTools` + - Common Name: `localhost` 3. Sign the CSR to generate server certificate: ``` openssl ca -create_serial -config ca.cnf -in server.csr -out server.pem -days 99999 @@ -71,6 +75,7 @@ To recreate the certificates follow the steps outlined below. ``` ## Setup Server Certificate with invalid hostname + 1. Create a new key to use for the server: ``` openssl genrsa -out server-invalidhost.key 4096 @@ -79,9 +84,9 @@ To recreate the certificates follow the steps outlined below. ``` openssl req -new -key server-invalidhost.key -out server-invalidhost.csr -days 99999 ``` - * Organization Name: `MongoDB` - * Organizational Unit Name: `DevTools` - * Common Name: `invalidhost` + - Organization Name: `MongoDB` + - Organizational Unit Name: `DevTools` + - Common Name: `invalidhost` 3. Sign the CSR to generate server certificate: ``` openssl ca -create_serial -config ca.cnf -in server-invalidhost.csr -out server-invalidhost.pem -days 99999 @@ -93,6 +98,7 @@ To recreate the certificates follow the steps outlined below. ``` ## Setup "Non-CA" for testing invalid CA cert + 1. Create a new key to use for the Non CA: ``` openssl genrsa -out non-ca.key 4096 @@ -101,11 +107,12 @@ To recreate the certificates follow the steps outlined below. ``` openssl req -new -x509 -key non-ca.key -out non-ca.crt -days 99999 ``` - * Organization Name: `MongoDB` - * Organizational Unit Name: `DevTools` - * Common Name: `NOT DevTools CA` + - Organization Name: `MongoDB` + - Organizational Unit Name: `DevTools` + - Common Name: `NOT DevTools CA` ## Revoke Server Certificate and generate CRL + 1. Revoke the server's certificate: ``` openssl ca -config ca.cnf -revoke server.pem @@ -116,6 +123,7 @@ To recreate the certificates follow the steps outlined below. ``` ## Create Client Certificate from CA + 1. Create a new key to use for the client: ``` openssl genrsa -out client.key 4096 @@ -124,10 +132,10 @@ To recreate the certificates follow the steps outlined below. ``` openssl req -new -key client.key -out client.csr -days 99999 ``` - * Organization Name: `MongoDB` - * Organizational Unit Name: `DevTools Testers` - * Common Name: `Wonderwoman` - * E-Mail: `tester@example.com` + - Organization Name: `MongoDB` + - Organizational Unit Name: `DevTools Testers` + - Common Name: `Wonderwoman` + - E-Mail: `tester@example.com` 3. Sign the CSR to generate server certificate: ``` openssl ca -create_serial -config ca.cnf -in client.csr -out client.pem -days 99999 @@ -146,9 +154,10 @@ To recreate the certificates follow the steps outlined below. ``` openssl pkcs12 -inkey client.bundle.pem -in client.bundle.pem -export -out client.bundle.pfx ``` - * Password: `passw0rd` + - Password: `passw0rd` ## Create Client Certificate not from CA + 1. Create a new key to use for the Non CA: ``` openssl genrsa -out invalid-client.key 4096 @@ -157,10 +166,10 @@ To recreate the certificates follow the steps outlined below. ``` openssl req -new -x509 -key invalid-client.key -out invalid-client.crt -days 99999 ``` - * Organization Name: `MongoDB` - * Organizational Unit Name: `DevTools Testers` - * Common Name: `Wonderwoman` - * E-Mail: `tester@example.com` + - Organization Name: `MongoDB` + - Organizational Unit Name: `DevTools Testers` + - Common Name: `Wonderwoman` + - E-Mail: `tester@example.com` 3. Create a bundle with client key and certificate to use for connecting: ``` cat invalid-client.crt invalid-client.key > invalid-client.bundle.pem diff --git a/testing/disable-dns-srv.js b/testing/disable-dns-srv.js index 7f75a0cef1..309d466066 100644 --- a/testing/disable-dns-srv.js +++ b/testing/disable-dns-srv.js @@ -1,14 +1,18 @@ 'use strict'; const dns = require('dns'); -console.log('!!! Disabling SRV and TXT DNS queries through the Node.js API !!!'); +// eslint-disable-next-line no-console +console.log( + '!!! Disabling SRV and TXT DNS queries through the Node.js API !!!' +); const origResolve = dns.resolve; const origPromiseResolve = dns.promises.resolve; -const err = Object.assign(new Error('SRV and TXT not available'), { code: 'ENODATA' }); +const err = Object.assign(new Error('SRV and TXT not available'), { + code: 'ENODATA', +}); dns.resolve = (hostname, type, cb) => { - if (type === 'SRV' || type === 'TXT') - return process.nextTick(cb, err); + if (type === 'SRV' || type === 'TXT') return process.nextTick(cb, err); return origResolve(hostname, type, cb); }; dns.resolveSrv = (hostname, cb) => { @@ -17,9 +21,8 @@ dns.resolveSrv = (hostname, cb) => { dns.resolveTxt = (hostname, cb) => { return process.nextTick(cb, err); }; -dns.promises.resolve = async(hostname, type) => { - if (type === 'SRV' || type === 'TXT') - throw err; +dns.promises.resolve = async (hostname, type) => { + if (type === 'SRV' || type === 'TXT') throw err; await origPromiseResolve; }; dns.promises.resolveSrv = () => Promise.reject(err); diff --git a/testing/eventually.ts b/testing/eventually.ts index c3e1f05a93..e2ab3bdfcb 100644 --- a/testing/eventually.ts +++ b/testing/eventually.ts @@ -1,9 +1,16 @@ -export async function eventually(fn: Function, opts: { initialInterval?: number; timeout?: number, backoffFactor?: number } = {}): Promise { +export async function eventually( + fn: Function, + opts: { + initialInterval?: number; + timeout?: number; + backoffFactor?: number; + } = {} +): Promise { const options = { initialInterval: 100, timeout: 10000, backoffFactor: 1, // no backoff - ...opts + ...opts, }; let attempts = calculateAttempts(options); @@ -21,7 +28,7 @@ export async function eventually(fn: Function, opts: { initialInterval?: number; err = e; } - await new Promise(resolve => setTimeout(resolve, currentInterval)); + await new Promise((resolve) => setTimeout(resolve, currentInterval)); currentInterval *= options.backoffFactor; } @@ -29,12 +36,16 @@ export async function eventually(fn: Function, opts: { initialInterval?: number; Object.assign(err, { timedOut: true, timeout: options.timeout, - message: `[Timed out ${options.timeout}ms] ${err.message}` + message: `[Timed out ${options.timeout}ms] ${err.message}`, }); throw err; } -function calculateAttempts(options: { initialInterval: number; timeout: number; backoffFactor: number; }): number { +function calculateAttempts(options: { + initialInterval: number; + timeout: number; + backoffFactor: number; +}): number { let totalInterval = 0; let attempts = 0; let interval = options.initialInterval; @@ -46,4 +57,3 @@ function calculateAttempts(options: { initialInterval: number; timeout: number; } return attempts; } - diff --git a/testing/fake-kms.ts b/testing/fake-kms.ts index 669b9df552..ee0e44dee0 100644 --- a/testing/fake-kms.ts +++ b/testing/fake-kms.ts @@ -4,15 +4,17 @@ import http from 'http'; // Exact values specified by RFC6749 ;) const oauthToken = { access_token: '2YotnFZFEjr1zCsicMWpAA', expires_in: 3600 }; -type RequestData = { url: string, body: string }; +type RequestData = { url: string; body: string }; type HandlerFunction = (data: RequestData) => any; -type HandlerList = { host: RegExp, handler: HandlerFunction }[]; +type HandlerList = { host: RegExp; handler: HandlerFunction }[]; type Duplex = NodeJS.ReadableStream & NodeJS.WritableStream; // Return a Duplex stream that behaves like an HTTP stream, with the 'server' // being provided by the handler function in this case (which is expected // to return JSON). -export function makeFakeHTTPConnection(handlerList: HandlerList): Duplex & { requests: http.IncomingMessage[] } { +export function makeFakeHTTPConnection( + handlerList: HandlerList +): Duplex & { requests: http.IncomingMessage[] } { const { socket1, socket2 } = new DuplexPair(); const server = makeFakeHTTPServer(handlerList); server.emit('connection', socket2); @@ -35,7 +37,7 @@ export function makeFakeHTTPServer(handlerList: HandlerList): FakeHTTPServer { } if (!foundHandler) { res.writeHead(404, { - 'Content-Type': 'text/plain' + 'Content-Type': 'text/plain', }); res.end(`Host ${host} not found`); return; @@ -43,10 +45,12 @@ export function makeFakeHTTPServer(handlerList: HandlerList): FakeHTTPServer { const handler = foundHandler; // Makes TS happy let body = ''; - req.setEncoding('utf8').on('data', chunk => { body += chunk; }); + req.setEncoding('utf8').on('data', (chunk) => { + body += chunk; + }); req.on('end', () => { res.writeHead(200, { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }); res.end(JSON.stringify(handler({ url: req.url ?? '', body }))); }); @@ -57,25 +61,28 @@ export function makeFakeHTTPServer(handlerList: HandlerList): FakeHTTPServer { export const fakeAWSHandlers: HandlerList = [ { host: /\.amazonaws\.com$/, handler: awsHandler }, { host: /\.microsoftonline.com$|\.azure.net$/, handler: azureHandler }, - { host: /\.googleapis.com$/, handler: gcpHandler } + { host: /\.googleapis.com$/, handler: gcpHandler }, ]; function awsHandler({ body }: RequestData): any { const request = JSON.parse(body); - let response; if (request.KeyId && request.Plaintext) { // Famously "unbreakable" base64 encryption ;) We use this to forward // both KeyId and Plaintext so that they are available for generating // the decryption response, which also provides the KeyId and Plaintext // based on the CiphertextBlob alone. - const CiphertextBlob = Buffer.from(request.KeyId + '\0' + request.Plaintext).toString('base64') + const CiphertextBlob = Buffer.from( + request.KeyId + '\0' + request.Plaintext + ).toString('base64'); return { CiphertextBlob, EncryptionAlgorithm: 'SYMMETRIC_DEFAULT', - KeyId: request.KeyId + KeyId: request.KeyId, }; } else { - let [ KeyId, Plaintext ] = Buffer.from(request.CiphertextBlob, 'base64').toString().split('\0'); + let [KeyId, Plaintext] = Buffer.from(request.CiphertextBlob, 'base64') + .toString() + .split('\0'); // Do not return invalid base64 https://jira.mongodb.org/browse/MONGOCRYPT-525 if (Buffer.from(KeyId, 'base64').toString('base64') !== KeyId) { KeyId = 'invalid0'; @@ -86,7 +93,7 @@ function awsHandler({ body }: RequestData): any { return { Plaintext, EncryptionAlgorithm: 'SYMMETRIC_DEFAULT', - KeyId + KeyId, }; } } diff --git a/testing/integration-testing-hooks.ts b/testing/integration-testing-hooks.ts index 8d3ee3fd21..7f229cdf18 100644 --- a/testing/integration-testing-hooks.ts +++ b/testing/integration-testing-hooks.ts @@ -1,23 +1,17 @@ +/* eslint-disable mocha/no-exports */ import child_process from 'child_process'; import { promises as fs } from 'fs'; -import { MongoClient, MongoClientOptions } from 'mongodb'; +import { MongoClient, type MongoClientOptions } from 'mongodb'; import path from 'path'; import semver from 'semver'; import { promisify } from 'util'; import which from 'which'; +import { MongoCluster, type MongoClusterOptions } from 'mongodb-runner'; import { ConnectionString } from 'mongodb-connection-string-url'; -import { MongoCluster, MongoClusterOptions } from 'mongodb-runner'; -import { downloadCryptLibrary } from '../packages/build/src/packaging/download-crypt-library'; +import { downloadCryptLibrary } from '@mongosh/build'; const execFile = promisify(child_process.execFile); -const isCI = !!process.env.IS_CI; -function ciLog(...args: any[]) { - if (isCI) { - console.error(...args); - } -} - // Return the path to the temporary directory and ensure that it exists. async function getTmpdir(): Promise { const tmpdir = path.resolve(__dirname, '..', 'tmp'); @@ -26,6 +20,7 @@ async function getTmpdir(): Promise { } // Represents one running test server instance. +// eslint-disable-next-line mocha/no-exports export class MongodSetup { _connectionString: Promise; _setConnectionString: (connectionString: string) => void; @@ -34,8 +29,10 @@ export class MongodSetup { _bindir = ''; constructor(connectionString?: string) { - this._setConnectionString = (connectionString: string) => {}; // Make TypeScript happy. - this._connectionString = new Promise(resolve => { + this._setConnectionString = () => { + // no-op to make TypeScript happy. + }; + this._connectionString = new Promise((resolve) => { this._setConnectionString = resolve; }); @@ -45,15 +42,21 @@ export class MongodSetup { } async start(): Promise { - throw new Error('Server not managed'); + await Promise.reject(new Error('Server not managed')); } async stop(): Promise { - throw new Error('Server not managed'); + await Promise.reject(new Error('Server not managed')); } - async connectionString(searchParams: Partial> = {}, uriOptions: Partial = {}): Promise { - if (Object.keys(searchParams).length + Object.keys(uriOptions).length === 0) { + async connectionString( + searchParams: Partial> = {}, + uriOptions: Partial = {} + ): Promise { + if ( + Object.keys(searchParams).length + Object.keys(uriOptions).length === + 0 + ) { return this._connectionString; } @@ -82,7 +85,7 @@ export class MongodSetup { return this._serverVersion; } - const { version } = await this.withClient(async client => { + const { version } = await this.withClient(async (client) => { return await client.db('db1').admin().serverStatus(); }); this._serverVersion = version; @@ -94,7 +97,7 @@ export class MongodSetup { return this._isCommunityServer; } - const { modules } = await this.withClient(async client => { + const { modules } = await this.withClient(async (client) => { return await client.db('db1').admin().command({ buildInfo: 1 }); }); const isCommunityServer = !modules.includes('enterprise'); @@ -109,7 +112,7 @@ export class MongodSetup { return await fn(client); } finally { if (client) { - client.close(); + await client.close(); } } } @@ -123,8 +126,15 @@ export class MongodSetup { export class MongoRunnerSetup extends MongodSetup { private static _usedDirPrefix: Record = {}; - private static _buildDirPath(id: string, version?: string, topology?: string) { - const prefix = [id, version, topology].filter(Boolean).join('-').replace(/[^a-zA-Z0-9_.-]/g, ''); + private static _buildDirPath( + id: string, + version?: string, + topology?: string + ) { + const prefix = [id, version, topology] + .filter(Boolean) + .join('-') + .replace(/[^a-zA-Z0-9_.-]/g, ''); this._usedDirPrefix[prefix] ??= 0; @@ -148,15 +158,20 @@ export class MongoRunnerSetup extends MongodSetup { if (this._cluster) return; const tmpDir = await getTmpdir(); const version = process.env.MONGOSH_SERVER_TEST_VERSION; - const dirPath = MongoRunnerSetup._buildDirPath(this._id, version, this._opts.topology); + const dirPath = MongoRunnerSetup._buildDirPath( + this._id, + version, + this._opts.topology + ); this._cluster = await MongoCluster.start({ topology: 'standalone', tmpDir: path.join(tmpDir, 'mongodb-runner', 'dbs', dirPath), logDir: path.join(tmpDir, 'mongodb-runner', 'logs', dirPath), + downloadDir: path.join(tmpDir, 'mongodb-runner'), version: version, - ...this._opts - }) + ...this._opts, + }); this._setConnectionString(this._cluster.connectionString); } @@ -170,13 +185,21 @@ export class MongoRunnerSetup extends MongodSetup { async function getInstalledMongodVersion(): Promise { await Promise.all([which('mongod'), which('mongos')]); const { stdout } = await execFile('mongod', ['--version']); - const { version } = stdout.match(/^db version (?.+)$/m)!.groups as any; + const { version } = stdout.match(/^db version (?.+)$/m)! + .groups as any; return version; } -export async function downloadCurrentCryptSharedLibrary(versionSpec?: string): Promise { +export async function downloadCurrentCryptSharedLibrary( + versionSpec?: string +): Promise { if (process.platform === 'linux') { - return (await downloadCryptLibrary(`linux-${process.arch.replace('ppc64', 'ppc64le')}` as any, versionSpec)).cryptLibrary; + return ( + await downloadCryptLibrary( + `linux-${process.arch.replace('ppc64', 'ppc64le')}` as any, + versionSpec + ) + ).cryptLibrary; } return (await downloadCryptLibrary('host', versionSpec)).cryptLibrary; } @@ -188,15 +211,18 @@ export async function downloadCurrentCryptSharedLibrary(versionSpec?: string): P * @export * @returns {MongodSetup} - Object with information about the started server. */ -let sharedSetup : MongodSetup | null = null; -export function startTestServer(id: string, args: Partial = {}): MongodSetup { +let sharedSetup: MongodSetup | null = null; +export function startTestServer( + id: string, + args: Partial = {} +): MongodSetup { const server = new MongoRunnerSetup(id, args); - before(async function() { - this.timeout(120_000); // Include potential mongod download time. + before(async function () { + this.timeout(120_000); // Include potential mongod download time. await server.start(); }); - after(async function() { + after(async function () { this.timeout(30_000); await server.stop(); }); @@ -204,7 +230,6 @@ export function startTestServer(id: string, args: Partial = return server; } - /** * Starts or reuse an existing shared local server managed by this process. * @@ -223,8 +248,8 @@ export function startSharedTestServer(): MongodSetup { const server = sharedSetup ?? (sharedSetup = new MongoRunnerSetup('shared')); - before(async function() { - this.timeout(120_000); // Include potential mongod download time. + before(async function () { + this.timeout(120_000); // Include potential mongod download time. await server.start(); }); @@ -233,7 +258,7 @@ export function startSharedTestServer(): MongodSetup { return server; } -global.after?.(async function() { +global.after?.(async function () { if (sharedSetup !== null) { this.timeout(30_000); await sharedSetup.stop(); @@ -242,15 +267,18 @@ global.after?.(async function() { // The same as startTestServer(), except that this starts multiple servers // in parallel in the same before() call. -export function startTestCluster(id: string, ...argLists: Partial[]): MongodSetup[] { - const servers = argLists.map(args => new MongoRunnerSetup(id, args)); +export function startTestCluster( + id: string, + ...argLists: Partial[] +): MongodSetup[] { + const servers = argLists.map((args) => new MongoRunnerSetup(id, args)); - before(async function() { + before(async function () { this.timeout(90_000 + 30_000 * servers.length); await Promise.all(servers.map((server: MongodSetup) => server.start())); }); - after(async function() { + after(async function () { this.timeout(30_000 * servers.length); await Promise.all(servers.map((server: MongodSetup) => server.stop())); }); @@ -258,8 +286,16 @@ export function startTestCluster(id: string, ...argLists: Partial /[0-9]+/.test(num) ? num : '0') + testServerVersion = testServerVersion + .split('-')[0] + .split('.') + .map((num) => (/[0-9]+/.test(num) ? num : '0')) .join('.'); } skipIfVersion(this, testServerVersion, semverCondition); diff --git a/testing/package.json b/testing/package.json new file mode 100644 index 0000000000..bf20e826b5 --- /dev/null +++ b/testing/package.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "lint": "prettier --check .", + "reformat": "prettier --write ." + } +} diff --git a/testing/tsconfig.json b/testing/tsconfig.json new file mode 100644 index 0000000000..c76dbd55aa --- /dev/null +++ b/testing/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@mongodb-js/tsconfig-mongosh/tsconfig.common.json", + "compilerOptions": { + "allowJs": true, + "outDir": "./dist" + }, + "include": ["**/*"], + "exclude": ["certificates"] +}