diff --git a/test/mocha_mongodb.json b/test/mocha_mongodb.json index d029ca46919..e60ad470ca3 100644 --- a/test/mocha_mongodb.json +++ b/test/mocha_mongodb.json @@ -4,7 +4,7 @@ "source-map-support/register", "ts-node/register", "test/tools/runner/chai-addons.js", - "test/tools/runner/hooks/configuration.js", + "test/tools/runner/hooks/configuration.ts", "test/tools/runner/hooks/unhandled_checker.ts", "test/tools/runner/hooks/leak_checker.ts", "test/tools/runner/hooks/legacy_crud_shims.ts" diff --git a/test/tools/runner/config.ts b/test/tools/runner/config.ts index ab2a4d519e4..047baa1d694 100644 --- a/test/tools/runner/config.ts +++ b/test/tools/runner/config.ts @@ -78,6 +78,7 @@ export class TestConfiguration { }; serverApi: ServerApi; activeResources: number; + serverlessCredentials: { username: string | undefined; password: string | undefined }; constructor(private uri: string, private context: Record) { const url = new ConnectionString(uri); diff --git a/test/tools/runner/filters/api_version_filter.js b/test/tools/runner/filters/api_version_filter.ts similarity index 80% rename from test/tools/runner/filters/api_version_filter.js rename to test/tools/runner/filters/api_version_filter.ts index 83c00b20b70..dcc8b345916 100755 --- a/test/tools/runner/filters/api_version_filter.js +++ b/test/tools/runner/filters/api_version_filter.ts @@ -1,22 +1,26 @@ -'use strict'; +import { Filter } from './filter'; /** * Filter for the MongoDB API Version required for the test * - * example: + * @example + * ```js * metadata: { * requires: { * apiVersion: '1' * } * } + * ``` */ -class ApiVersionFilter { +export class ApiVersionFilter extends Filter { + apiVersion: string | undefined; constructor() { + super(); // Get environmental variables that are known this.apiVersion = process.env.MONGODB_API_VERSION; } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { if (!test.metadata) return true; if (!test.metadata.requires) return true; const apiVersion = test.metadata.requires.apiVersion; @@ -33,5 +37,3 @@ class ApiVersionFilter { return apiVersion === this.apiVersion; } } - -module.exports = ApiVersionFilter; diff --git a/test/tools/runner/filters/auth_filter.js b/test/tools/runner/filters/auth_filter.ts similarity index 77% rename from test/tools/runner/filters/auth_filter.js rename to test/tools/runner/filters/auth_filter.ts index c8122c6d4e6..4fe3cb26a09 100644 --- a/test/tools/runner/filters/auth_filter.js +++ b/test/tools/runner/filters/auth_filter.ts @@ -1,21 +1,25 @@ -'use strict'; +import { Filter } from './filter'; /** * Filter for authorization enabled or disabled on the server * - * example: + * @example + * ```js * metadata: { * requires: { * auth: 'enabled' | 'disabled' * } * } + * ``` */ -class AuthFilter { +export class AuthFilter extends Filter { + isAuthEnabled: boolean; constructor() { + super(); this.isAuthEnabled = process.env.AUTH === 'auth'; } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { if (!test.metadata) return true; if (!test.metadata.requires) return true; if (!test.metadata.requires.auth) return true; @@ -33,5 +37,3 @@ class AuthFilter { ); } } - -module.exports = AuthFilter; diff --git a/test/tools/runner/filters/client_encryption_filter.js b/test/tools/runner/filters/client_encryption_filter.ts similarity index 65% rename from test/tools/runner/filters/client_encryption_filter.js rename to test/tools/runner/filters/client_encryption_filter.ts index 2cc20262e6e..a614f6f8e35 100644 --- a/test/tools/runner/filters/client_encryption_filter.js +++ b/test/tools/runner/filters/client_encryption_filter.ts @@ -1,22 +1,28 @@ -'use strict'; +import { readFile } from 'fs/promises'; +import { dirname, resolve } from 'path'; +import * as process from 'process'; -const { readFileSync } = require('fs'); -const { resolve } = require('path'); -const process = require('process'); +import { type MongoClient } from '../../../mongodb'; +import { Filter } from './filter'; /** * Filter for whether or not a test needs / doesn't need Client Side Encryption * - * example: + * @example + * ```js * metadata: { * requires: { * clientSideEncryption: true|false * } * } + * ``` */ -class ClientSideEncryptionFilter { - initializeFilter(client, context, callback) { +export class ClientSideEncryptionFilter extends Filter { + enabled: boolean; + static version = null; + + async initializeFilter(client: MongoClient, context: Record) { const CSFLE_KMS_PROVIDERS = process.env.CSFLE_KMS_PROVIDERS; let mongodbClientEncryption; try { @@ -27,11 +33,12 @@ class ClientSideEncryptionFilter { } } - const { version } = JSON.parse( - readFileSync( - resolve(__dirname, '../../../../node_modules/mongodb-client-encryption', 'package.json') + ClientSideEncryptionFilter.version ??= JSON.parse( + await readFile( + resolve(dirname(require.resolve('mongodb-client-encryption')), '..', 'package.json'), + 'utf8' ) - ); + ).version; this.enabled = !!(CSFLE_KMS_PROVIDERS && mongodbClientEncryption); @@ -40,13 +47,11 @@ class ClientSideEncryptionFilter { enabled: this.enabled, mongodbClientEncryption, CSFLE_KMS_PROVIDERS, - version + version: ClientSideEncryptionFilter.version }; - - callback(); } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { const clientSideEncryption = test.metadata && test.metadata.requires && test.metadata.requires.clientSideEncryption; @@ -66,5 +71,3 @@ class ClientSideEncryptionFilter { return this.enabled; } } - -module.exports = ClientSideEncryptionFilter; diff --git a/test/tools/runner/filters/filter.ts b/test/tools/runner/filters/filter.ts new file mode 100644 index 00000000000..b03ad83d5e9 --- /dev/null +++ b/test/tools/runner/filters/filter.ts @@ -0,0 +1,11 @@ +import { type Test } from 'mocha'; + +import { type MongoClient } from '../../../mongodb'; + +export abstract class Filter { + async initializeFilter(_client: MongoClient, _context: Record): Promise { + return; + } + + abstract filter(test: Test): string | boolean; +} diff --git a/test/tools/runner/filters/generic_predicate_filter.js b/test/tools/runner/filters/generic_predicate_filter.ts similarity index 65% rename from test/tools/runner/filters/generic_predicate_filter.js rename to test/tools/runner/filters/generic_predicate_filter.ts index 2682acc0eba..85d478f35f7 100644 --- a/test/tools/runner/filters/generic_predicate_filter.js +++ b/test/tools/runner/filters/generic_predicate_filter.ts @@ -1,26 +1,26 @@ -'use strict'; - /** * Generic filter than can run predicates. * * Predicates cannot be async. The test is skipped if the predicate returns * a string. The string returned should be a skip reason. * - * example: + * @example + * ```js * metadata: { * requires: { * predicate: (test: Mocha.Test) => true | string * } * } + * ``` */ -class GenericPredicateFilter { - filter(test) { - /** @type{ ((test?: Mocha.Test) => string | true) | undefined } */ - const predicate = test?.metadata?.requires?.predicate; +import { type Test } from 'mocha'; + +import { Filter } from './filter'; +export class GenericPredicateFilter extends Filter { + filter(test: Test & { metadata?: MongoDBMetadataUI }) { + const predicate = test?.metadata?.requires?.predicate; return predicate?.(test) ?? true; } } - -module.exports = GenericPredicateFilter; diff --git a/test/tools/runner/filters/idms_mock_server_filter.js b/test/tools/runner/filters/idms_mock_server_filter.ts similarity index 73% rename from test/tools/runner/filters/idms_mock_server_filter.js rename to test/tools/runner/filters/idms_mock_server_filter.ts index 04998541897..40d34d93b95 100644 --- a/test/tools/runner/filters/idms_mock_server_filter.js +++ b/test/tools/runner/filters/idms_mock_server_filter.ts @@ -1,6 +1,6 @@ -'use strict'; +import { get } from 'http'; -const { get } = require('http'); +import { Filter } from './filter'; async function isMockServerSetup() { const url = (() => { @@ -11,7 +11,7 @@ async function isMockServerSetup() { url.searchParams.append('resource', 'https://vault.azure.net'); return url; })(); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { get(url, res => { if (res.statusCode === 200) { return resolve(); @@ -26,24 +26,28 @@ async function isMockServerSetup() { /** * Filter for tests that require the mock idms server to be running. * - * example: + * @example + * ```js * metadata: { * requires: { * idmsMockServer: true * } * } + * ``` */ -class IDMSMockServerFilter { - initializeFilter(client, context, callback) { - isMockServerSetup() - .then( - () => (this.isRunning = true), - () => (this.isRunning = false) - ) - .then(() => callback()); +export class IDMSMockServerFilter extends Filter { + isRunning: boolean; + + async initializeFilter() { + try { + await isMockServerSetup(); + this.isRunning = true; + } catch (error) { + this.isRunning = false; + } } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { if (!test.metadata) return true; if (!test.metadata.requires) return true; if (!test.metadata.requires.idmsMockServer) return true; @@ -58,5 +62,3 @@ class IDMSMockServerFilter { return this.isRunning; } } - -module.exports = IDMSMockServerFilter; diff --git a/test/tools/runner/filters/mongodb_topology_filter.js b/test/tools/runner/filters/mongodb_topology_filter.ts similarity index 80% rename from test/tools/runner/filters/mongodb_topology_filter.js rename to test/tools/runner/filters/mongodb_topology_filter.ts index 8f369b1e477..c4e7035ab22 100755 --- a/test/tools/runner/filters/mongodb_topology_filter.js +++ b/test/tools/runner/filters/mongodb_topology_filter.ts @@ -1,25 +1,29 @@ -'use strict'; -const { TopologyType } = require('../../../mongodb'); +import { type MongoClient, TopologyType } from '../../../mongodb'; +import { Filter } from './filter'; /** * Filter for the MongoDB topology required for the test * - * example: + * @example + * ```js * metadata: { * requires: { * topology: 'single' | 'replicaset' | 'sharded' * } * } + * ``` */ -class MongoDBTopologyFilter { - initializeFilter(client, context, callback) { - let type = client.topology.description.type; +export class MongoDBTopologyFilter extends Filter { + runtimeTopology: string; + + async initializeFilter(client: MongoClient, context: Record) { + const type = client.topology?.description.type; + if (type == null) throw new Error('unexpected nullish type' + client.topology?.description); context.topologyType = type; this.runtimeTopology = topologyTypeToString(type); - callback(); } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { if (!test.metadata) return true; if (!test.metadata.requires) return true; if (!test.metadata.requires.topology) return true; @@ -72,5 +76,3 @@ function topologyTypeToString(topologyType) { return 'single'; } - -module.exports = MongoDBTopologyFilter; diff --git a/test/tools/runner/filters/mongodb_version_filter.js b/test/tools/runner/filters/mongodb_version_filter.js deleted file mode 100755 index eff44d56a96..00000000000 --- a/test/tools/runner/filters/mongodb_version_filter.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -const semver = require('semver'); - -/** - * Filter for the MongoDB version required for the test - * - * example: - * metadata: { - * requires: { - * mongodb: 'mongodbSemverVersion' - * } - * } - */ -class MongoDBVersionFilter { - constructor(options) { - this.options = options || {}; - this.version = null; - } - - initializeFilter(client, context, callback) { - if (this.options.skip) { - callback(); - return; - } - - client.db('admin').command({ buildInfo: true }, (err, result) => { - if (err) { - callback(err); - return; - } - context.version = this.version = result.versionArray.slice(0, 3).join('.'); - context.buildInfo = result; - callback(); - }); - } - - filter(test) { - if (this.options.skip) return true; - if (!test.metadata) return true; - if (!test.metadata.requires) return true; - if (!test.metadata.requires.mongodb) return true; - return semver.satisfies(this.version, test.metadata.requires.mongodb); - } -} - -module.exports = MongoDBVersionFilter; diff --git a/test/tools/runner/filters/mongodb_version_filter.ts b/test/tools/runner/filters/mongodb_version_filter.ts new file mode 100755 index 00000000000..235a1af8fd6 --- /dev/null +++ b/test/tools/runner/filters/mongodb_version_filter.ts @@ -0,0 +1,39 @@ +import * as semver from 'semver'; + +import { type MongoClient } from '../../../mongodb'; +import { Filter } from './filter'; + +/** + * Filter for the MongoDB version required for the test + * + * @example + * ```js + * metadata: { + * requires: { + * mongodb: 'mongodbSemverVersion' + * } + * } + * ``` + */ +export class MongoDBVersionFilter extends Filter { + version: string | null; + + constructor() { + super(); + this.version = null; + } + + async initializeFilter(client: MongoClient, context: Record) { + const result = await client.db('admin').command({ buildInfo: true }); + context.version = this.version = result.versionArray.slice(0, 3).join('.'); + context.buildInfo = result; + } + + filter(test: { metadata?: MongoDBMetadataUI }) { + if (!test.metadata) return true; + if (!test.metadata.requires) return true; + if (!test.metadata.requires.mongodb) return true; + if (typeof this.version !== 'string') throw new Error('expected version string!'); + return semver.satisfies(this.version, test.metadata.requires.mongodb); + } +} diff --git a/test/tools/runner/filters/node_version_filter.js b/test/tools/runner/filters/node_version_filter.ts similarity index 60% rename from test/tools/runner/filters/node_version_filter.js rename to test/tools/runner/filters/node_version_filter.ts index 0c16139f717..2cc70ea5c13 100644 --- a/test/tools/runner/filters/node_version_filter.js +++ b/test/tools/runner/filters/node_version_filter.ts @@ -1,19 +1,21 @@ -'use strict'; +import { satisfies } from 'semver'; -const { satisfies } = require('semver'); +import { Filter } from './filter'; /** * Filter for specific nodejs versions * - * example: + * @example + * ```js * metadata: { * requires: { * nodejs: '>=14' * } * } + * ``` */ -class NodeVersionFilter { - filter(test) { +export class NodeVersionFilter extends Filter { + filter(test: { metadata?: MongoDBMetadataUI }) { const nodeVersionRange = test?.metadata?.requires?.nodejs; if (!nodeVersionRange) { return true; @@ -22,5 +24,3 @@ class NodeVersionFilter { return satisfies(process.version, nodeVersionRange); } } - -module.exports = NodeVersionFilter; diff --git a/test/tools/runner/filters/os_filter.js b/test/tools/runner/filters/os_filter.ts similarity index 76% rename from test/tools/runner/filters/os_filter.js rename to test/tools/runner/filters/os_filter.ts index d94c2eb029a..50e6201ed1e 100755 --- a/test/tools/runner/filters/os_filter.js +++ b/test/tools/runner/filters/os_filter.ts @@ -1,22 +1,26 @@ -'use strict'; +import { Filter } from './filter'; /** * Filter for the OS required for the test * - * example: + * @example + * ```js * metadata: { * requires: { * os: 'osName' * } * } + * ``` */ -class OSFilter { +export class OSFilter extends Filter { + platform: string; constructor() { + super(); // Get environmental variables that are known this.platform = process.platform; } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { if (!test.metadata) return true; if (!test.metadata.requires) return true; if (!test.metadata.requires.os) return true; @@ -29,5 +33,3 @@ class OSFilter { return false; } } - -module.exports = OSFilter; diff --git a/test/tools/runner/filters/serverless_filter.js b/test/tools/runner/filters/serverless_filter.ts similarity index 62% rename from test/tools/runner/filters/serverless_filter.js rename to test/tools/runner/filters/serverless_filter.ts index 72288c8f576..862d623484f 100755 --- a/test/tools/runner/filters/serverless_filter.js +++ b/test/tools/runner/filters/serverless_filter.ts @@ -1,37 +1,39 @@ -'use strict'; -const { shouldRunServerlessTest } = require('../../utils'); +import { type MongoClient } from '../../../mongodb'; +import { shouldRunServerlessTest } from '../../utils'; +import { Filter } from './filter'; /** * Filter to allow to tests to run on serverless * - * example: + * @example + * ```js * metadata: { * requires: { * serverless: 'forbid' * } * } + * ``` */ -class ServerlessFilter { +export class ServerlessFilter extends Filter { + serverless: boolean; constructor() { + super(); // Get environmental variables that are known this.serverless = !!process.env.SERVERLESS; } - initializeFilter(client, context, callback) { + async initializeFilter(client: MongoClient, context: Record) { if (this.serverless) { context.serverlessCredentials = { username: process.env.SERVERLESS_ATLAS_USER, password: process.env.SERVERLESS_ATLAS_PASSWORD }; } - callback(); } - filter(test) { + filter(test: { metadata?: MongoDBMetadataUI }) { if (!test.metadata) return true; if (!test.metadata.requires) return true; return shouldRunServerlessTest(test.metadata.requires.serverless, this.serverless); } } - -module.exports = ServerlessFilter; diff --git a/test/tools/runner/hooks/configuration.js b/test/tools/runner/hooks/configuration.ts similarity index 79% rename from test/tools/runner/hooks/configuration.js rename to test/tools/runner/hooks/configuration.ts index e947a6f069d..8c3f7be29dc 100644 --- a/test/tools/runner/hooks/configuration.js +++ b/test/tools/runner/hooks/configuration.ts @@ -1,18 +1,30 @@ -'use strict'; +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable import/first */ +// eslint-disable-next-line @typescript-eslint/no-var-requires require('source-map-support').install({ hookRequire: true }); -const path = require('path'); -const fs = require('fs'); -const { MongoClient } = require('../../../mongodb'); -const { AstrolabeTestConfiguration, TestConfiguration } = require('../config'); -const { getEnvironmentalOptions } = require('../../utils'); -const mock = require('../../mongodb-mock/index'); -const { inspect } = require('util'); -const { setDefaultResultOrder } = require('dns'); -const { coerce, gte } = require('semver'); +import { MongoClient } from '../../../mongodb'; +import { AstrolabeTestConfiguration, TestConfiguration } from '../config'; +import { getEnvironmentalOptions } from '../../utils'; +import * as mock from '../../mongodb-mock/index'; +import { inspect } from 'util'; +import { setDefaultResultOrder } from 'dns'; +import { coerce, gte } from 'semver'; + +import { ApiVersionFilter } from '../filters/api_version_filter'; +import { AuthFilter } from '../filters/auth_filter'; +import { ClientSideEncryptionFilter } from '../filters/client_encryption_filter'; +import { GenericPredicateFilter } from '../filters/generic_predicate_filter'; +import { IDMSMockServerFilter } from '../filters/idms_mock_server_filter'; +import { MongoDBTopologyFilter } from '../filters/mongodb_topology_filter'; +import { MongoDBVersionFilter } from '../filters/mongodb_version_filter'; +import { NodeVersionFilter } from '../filters/node_version_filter'; +import { OSFilter } from '../filters/os_filter'; +import { ServerlessFilter } from '../filters/serverless_filter'; +import { type Filter } from '../filters/filter'; // Default our tests to have auth enabled // A better solution will be tackled in NODE-3714 @@ -34,34 +46,30 @@ const SINGLE_MONGOS_LB_URI = process.env.SINGLE_MONGOS_LB_URI; // Load balancer fronting 2 mongoses. const MULTI_MONGOS_LB_URI = process.env.MULTI_MONGOS_LB_URI; const loadBalanced = SINGLE_MONGOS_LB_URI && MULTI_MONGOS_LB_URI; -const filters = []; +const filters: Filter[] = []; let initializedFilters = false; -async function initializeFilters(client) { +async function initializeFilters(client): Promise> { if (initializedFilters) { - return; + return {}; } initializedFilters = true; const context = {}; - const filterFiles = fs - .readdirSync(path.join(__dirname, '../filters')) - .filter(x => x.indexOf('js') !== -1); - - for (const filterName of filterFiles) { - const FilterModule = require(path.join(__dirname, '../filters', filterName)); - const filter = new FilterModule(); - - console.assert(typeof filter === 'object'); - console.assert(filter.filter && typeof filter.filter === 'function'); - + for (const filter of [ + new ApiVersionFilter(), + new AuthFilter(), + new ClientSideEncryptionFilter(), + new GenericPredicateFilter(), + new IDMSMockServerFilter(), + new MongoDBTopologyFilter(), + new MongoDBVersionFilter(), + new NodeVersionFilter(), + new OSFilter(), + new ServerlessFilter() + ]) { filters.push(filter); - - if (typeof filter.initializeFilter === 'function') { - await new Promise((resolve, reject) => - filter.initializeFilter(client, context, e => (e ? reject(e) : resolve())) - ); - } + await filter.initializeFilter(client, context); } return context;