Skip to content

Commit

Permalink
fix(NODE-3290): versioned api validation and tests (#2869)
Browse files Browse the repository at this point in the history
  • Loading branch information
emadum committed Jul 14, 2021
1 parent 91a2fc9 commit 238a4b0
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 12 deletions.
3 changes: 3 additions & 0 deletions index.js
Expand Up @@ -17,6 +17,9 @@ connect.MongoWriteConcernError = core.MongoWriteConcernError;
connect.MongoBulkWriteError = require('./lib/bulk/common').BulkWriteError;
connect.BulkWriteError = connect.MongoBulkWriteError;

// Expose server versions
connect.ServerApiVersion = core.ServerApiVersion;

// Actual driver classes exported
connect.Admin = require('./lib/admin');
connect.MongoClient = require('./lib/mongo_client');
Expand Down
7 changes: 7 additions & 0 deletions lib/core/index.js
Expand Up @@ -14,7 +14,14 @@ try {
}
} catch (err) {} // eslint-disable-line

/** An enumeration of valid server API versions */
const ServerApiVersion = Object.freeze({
v1: '1'
});

module.exports = {
// Versioned API
ServerApiVersion,
// Errors
MongoError: require('./error').MongoError,
MongoNetworkError: require('./error').MongoNetworkError,
Expand Down
29 changes: 26 additions & 3 deletions lib/mongo_client.js
Expand Up @@ -5,6 +5,7 @@ const Db = require('./db');
const EventEmitter = require('events').EventEmitter;
const inherits = require('util').inherits;
const MongoError = require('./core').MongoError;
const ServerApiVersion = require('./core').ServerApiVersion;
const deprecate = require('util').deprecate;
const WriteConcern = require('./write_concern');
const MongoDBNamespace = require('./utils').MongoDBNamespace;
Expand Down Expand Up @@ -192,16 +193,38 @@ const validOptions = require('./operations/connect').validOptions;
* @param {MongoClientOptions} [options] Optional settings
*/
function MongoClient(url, options) {
options = options || {};
if (!(this instanceof MongoClient)) return new MongoClient(url, options);
// Set up event emitter
EventEmitter.call(this);

if (options && options.autoEncryption) require('./encrypter'); // Does CSFLE lib check
if (options.autoEncryption) require('./encrypter'); // Does CSFLE lib check

if (options.serverApi) {
const serverApiToValidate =
typeof options.serverApi === 'string' ? { version: options.serverApi } : options.serverApi;
const versionToValidate = serverApiToValidate && serverApiToValidate.version;
if (!versionToValidate) {
throw new MongoError(
`Invalid \`serverApi\` property; must specify a version from the following enum: ["${Object.values(
ServerApiVersion
).join('", "')}"]`
);
}
if (!Object.values(ServerApiVersion).some(v => v === versionToValidate)) {
throw new MongoError(
`Invalid server API version=${versionToValidate}; must be in the following enum: ["${Object.values(
ServerApiVersion
).join('", "')}"]`
);
}
options.serverApi = serverApiToValidate;
}

// The internal state
this.s = {
url: url,
options: options || {},
url,
options,
promiseLibrary: (options && options.promiseLibrary) || Promise,
dbCache: new Map(),
sessions: new Set(),
Expand Down
71 changes: 67 additions & 4 deletions test/functional/versioned-api.test.js
Expand Up @@ -3,17 +3,80 @@
const expect = require('chai').expect;
const loadSpecTests = require('../spec/index').loadSpecTests;
const runUnifiedTest = require('./unified-spec-runner/runner').runUnifiedTest;
const ServerApiVersion = require('../../lib/core').ServerApiVersion;

describe('Versioned API', function() {
it('should throw an error if serverApi version is provided via the uri with new parser', {
metadata: { topology: 'single' },
test: function(done) {
describe('client option validation', function() {
it('is supported as a client option when it is a valid ServerApiVersion string', function() {
const validVersions = Object.values(ServerApiVersion);
expect(validVersions.length).to.be.at.least(1);
for (const version of validVersions) {
const client = this.configuration.newClient('mongodb://localhost/', {
serverApi: version
});
expect(client.s.options)
.to.have.property('serverApi')
.deep.equal({ version });
}
});

it('is supported as a client option when it is an object with a valid version property', function() {
const validVersions = Object.values(ServerApiVersion);
expect(validVersions.length).to.be.at.least(1);
for (const version of validVersions) {
const client = this.configuration.newClient('mongodb://localhost/', {
serverApi: { version }
});
expect(client.s.options)
.to.have.property('serverApi')
.deep.equal({ version });
}
});

it('is not supported as a client option when it is an invalid string', function() {
expect(() =>
this.configuration.newClient('mongodb://localhost/', {
serverApi: 'bad'
})
).to.throw(/^Invalid server API version=bad;/);
});

it('is not supported as a client option when it is a number', function() {
expect(() =>
this.configuration.newClient('mongodb://localhost/', {
serverApi: 1
})
).to.throw(/^Invalid `serverApi` property;/);
});

it('is not supported as a client option when it is an object without a specified version', function() {
expect(() =>
this.configuration.newClient('mongodb://localhost/', {
serverApi: {}
})
).to.throw(/^Invalid `serverApi` property;/);
});

it('is not supported as a client option when it is an object with an invalid specified version', function() {
expect(() =>
this.configuration.newClient('mongodb://localhost/', {
serverApi: { version: 1 }
})
).to.throw(/^Invalid server API version=1;/);
expect(() =>
this.configuration.newClient('mongodb://localhost/', {
serverApi: { version: 'bad' }
})
).to.throw(/^Invalid server API version=bad;/);
});

it('is not supported as a URI option even when it is a valid ServerApiVersion string', function(done) {
const client = this.configuration.newClient({ serverApi: '1' }, { useNewUrlParser: true });
client.connect(err => {
expect(err).to.match(/URI cannot contain `serverApi`, it can only be passed to the client/);
client.close(done);
});
}
});
});

for (const versionedApiTest of loadSpecTests('versioned-api')) {
Expand Down
24 changes: 19 additions & 5 deletions test/tools/cluster_setup.sh
Expand Up @@ -2,17 +2,31 @@

if [ "$#" -ne 1 ]; then
echo "usage: cluster_setup <server|replica_set|sharded_cluster>"
echo "override <DATA_DIR | SINGLE_DIR | REPLICASET_DIR | SHARDED_DIR> env variables to change dbPath"
exit
fi

DATA_DIR=${DATA_DIR:-data}
SINGLE_DIR=${SINGLE_DIR:-$DATA_DIR/server}
REPLICASET_DIR=${REPLICASET_DIR:-$DATA_DIR/replica_set}
SHARDED_DIR=${SHARDED_DIR:-$DATA_DIR/sharded_cluster}

if [[ ! -z "$MONGODB_API_VERSION" ]]; then
echo "Requiring versioned API $MONGODB_API_VERSION"
REQUIRE_API="--setParameter requireApiVersion=$MONGODB_API_VERSION"
fi

if [[ $1 == "replica_set" ]]; then
mlaunch init --replicaset --nodes 3 --arbiter --name rs --port 31000 --enableMajorityReadConcern --setParameter enableTestCommands=1
echo "mongodb://localhost:31000/?replicaSet=rs"
mkdir -p $REPLICASET_DIR
mlaunch init --dir $REPLICASET_DIR --replicaset --nodes 3 --arbiter --name rs --port 31000 --enableMajorityReadConcern --setParameter enableTestCommands=1
echo "mongodb://localhost:31000,localhost:31001,localhost:31002/?replicaSet=rs"
elif [[ $1 == "sharded_cluster" ]]; then
mlaunch init --replicaset --nodes 3 --arbiter --name rs --port 51000 --enableMajorityReadConcern --setParameter enableTestCommands=1 --sharded 1 --mongos 2
echo "mongodb://localhost:51000,localhost:51001/"
mkdir -p $SHARDED_DIR
mlaunch init --dir $SHARDED_DIR --replicaset --nodes 3 --arbiter --name rs --port 51000 --enableMajorityReadConcern --setParameter enableTestCommands=1 --sharded 1 --mongos 2
echo "mongodb://localhost:51000,localhost:51001"
elif [[ $1 == "server" ]]; then
mlaunch init --single --setParameter enableTestCommands=1
mkdir -p $SINGLE_DIR
mlaunch init --dir $SINGLE_DIR --single --setParameter enableTestCommands=1 $REQUIRE_API
echo "mongodb://localhost:27017"
else
echo "unsupported topology: $1"
Expand Down

0 comments on commit 238a4b0

Please sign in to comment.