Skip to content

Commit 238a4b0

Browse files
authored
fix(NODE-3290): versioned api validation and tests (#2869)
1 parent 91a2fc9 commit 238a4b0

File tree

5 files changed

+122
-12
lines changed

5 files changed

+122
-12
lines changed

index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ connect.MongoWriteConcernError = core.MongoWriteConcernError;
1717
connect.MongoBulkWriteError = require('./lib/bulk/common').BulkWriteError;
1818
connect.BulkWriteError = connect.MongoBulkWriteError;
1919

20+
// Expose server versions
21+
connect.ServerApiVersion = core.ServerApiVersion;
22+
2023
// Actual driver classes exported
2124
connect.Admin = require('./lib/admin');
2225
connect.MongoClient = require('./lib/mongo_client');

lib/core/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ try {
1414
}
1515
} catch (err) {} // eslint-disable-line
1616

17+
/** An enumeration of valid server API versions */
18+
const ServerApiVersion = Object.freeze({
19+
v1: '1'
20+
});
21+
1722
module.exports = {
23+
// Versioned API
24+
ServerApiVersion,
1825
// Errors
1926
MongoError: require('./error').MongoError,
2027
MongoNetworkError: require('./error').MongoNetworkError,

lib/mongo_client.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const Db = require('./db');
55
const EventEmitter = require('events').EventEmitter;
66
const inherits = require('util').inherits;
77
const MongoError = require('./core').MongoError;
8+
const ServerApiVersion = require('./core').ServerApiVersion;
89
const deprecate = require('util').deprecate;
910
const WriteConcern = require('./write_concern');
1011
const MongoDBNamespace = require('./utils').MongoDBNamespace;
@@ -192,16 +193,38 @@ const validOptions = require('./operations/connect').validOptions;
192193
* @param {MongoClientOptions} [options] Optional settings
193194
*/
194195
function MongoClient(url, options) {
196+
options = options || {};
195197
if (!(this instanceof MongoClient)) return new MongoClient(url, options);
196198
// Set up event emitter
197199
EventEmitter.call(this);
198200

199-
if (options && options.autoEncryption) require('./encrypter'); // Does CSFLE lib check
201+
if (options.autoEncryption) require('./encrypter'); // Does CSFLE lib check
202+
203+
if (options.serverApi) {
204+
const serverApiToValidate =
205+
typeof options.serverApi === 'string' ? { version: options.serverApi } : options.serverApi;
206+
const versionToValidate = serverApiToValidate && serverApiToValidate.version;
207+
if (!versionToValidate) {
208+
throw new MongoError(
209+
`Invalid \`serverApi\` property; must specify a version from the following enum: ["${Object.values(
210+
ServerApiVersion
211+
).join('", "')}"]`
212+
);
213+
}
214+
if (!Object.values(ServerApiVersion).some(v => v === versionToValidate)) {
215+
throw new MongoError(
216+
`Invalid server API version=${versionToValidate}; must be in the following enum: ["${Object.values(
217+
ServerApiVersion
218+
).join('", "')}"]`
219+
);
220+
}
221+
options.serverApi = serverApiToValidate;
222+
}
200223

201224
// The internal state
202225
this.s = {
203-
url: url,
204-
options: options || {},
226+
url,
227+
options,
205228
promiseLibrary: (options && options.promiseLibrary) || Promise,
206229
dbCache: new Map(),
207230
sessions: new Set(),

test/functional/versioned-api.test.js

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,80 @@
33
const expect = require('chai').expect;
44
const loadSpecTests = require('../spec/index').loadSpecTests;
55
const runUnifiedTest = require('./unified-spec-runner/runner').runUnifiedTest;
6+
const ServerApiVersion = require('../../lib/core').ServerApiVersion;
67

78
describe('Versioned API', function() {
8-
it('should throw an error if serverApi version is provided via the uri with new parser', {
9-
metadata: { topology: 'single' },
10-
test: function(done) {
9+
describe('client option validation', function() {
10+
it('is supported as a client option when it is a valid ServerApiVersion string', function() {
11+
const validVersions = Object.values(ServerApiVersion);
12+
expect(validVersions.length).to.be.at.least(1);
13+
for (const version of validVersions) {
14+
const client = this.configuration.newClient('mongodb://localhost/', {
15+
serverApi: version
16+
});
17+
expect(client.s.options)
18+
.to.have.property('serverApi')
19+
.deep.equal({ version });
20+
}
21+
});
22+
23+
it('is supported as a client option when it is an object with a valid version property', function() {
24+
const validVersions = Object.values(ServerApiVersion);
25+
expect(validVersions.length).to.be.at.least(1);
26+
for (const version of validVersions) {
27+
const client = this.configuration.newClient('mongodb://localhost/', {
28+
serverApi: { version }
29+
});
30+
expect(client.s.options)
31+
.to.have.property('serverApi')
32+
.deep.equal({ version });
33+
}
34+
});
35+
36+
it('is not supported as a client option when it is an invalid string', function() {
37+
expect(() =>
38+
this.configuration.newClient('mongodb://localhost/', {
39+
serverApi: 'bad'
40+
})
41+
).to.throw(/^Invalid server API version=bad;/);
42+
});
43+
44+
it('is not supported as a client option when it is a number', function() {
45+
expect(() =>
46+
this.configuration.newClient('mongodb://localhost/', {
47+
serverApi: 1
48+
})
49+
).to.throw(/^Invalid `serverApi` property;/);
50+
});
51+
52+
it('is not supported as a client option when it is an object without a specified version', function() {
53+
expect(() =>
54+
this.configuration.newClient('mongodb://localhost/', {
55+
serverApi: {}
56+
})
57+
).to.throw(/^Invalid `serverApi` property;/);
58+
});
59+
60+
it('is not supported as a client option when it is an object with an invalid specified version', function() {
61+
expect(() =>
62+
this.configuration.newClient('mongodb://localhost/', {
63+
serverApi: { version: 1 }
64+
})
65+
).to.throw(/^Invalid server API version=1;/);
66+
expect(() =>
67+
this.configuration.newClient('mongodb://localhost/', {
68+
serverApi: { version: 'bad' }
69+
})
70+
).to.throw(/^Invalid server API version=bad;/);
71+
});
72+
73+
it('is not supported as a URI option even when it is a valid ServerApiVersion string', function(done) {
1174
const client = this.configuration.newClient({ serverApi: '1' }, { useNewUrlParser: true });
1275
client.connect(err => {
1376
expect(err).to.match(/URI cannot contain `serverApi`, it can only be passed to the client/);
1477
client.close(done);
1578
});
16-
}
79+
});
1780
});
1881

1982
for (const versionedApiTest of loadSpecTests('versioned-api')) {

test/tools/cluster_setup.sh

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,31 @@
22

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

9+
DATA_DIR=${DATA_DIR:-data}
10+
SINGLE_DIR=${SINGLE_DIR:-$DATA_DIR/server}
11+
REPLICASET_DIR=${REPLICASET_DIR:-$DATA_DIR/replica_set}
12+
SHARDED_DIR=${SHARDED_DIR:-$DATA_DIR/sharded_cluster}
13+
14+
if [[ ! -z "$MONGODB_API_VERSION" ]]; then
15+
echo "Requiring versioned API $MONGODB_API_VERSION"
16+
REQUIRE_API="--setParameter requireApiVersion=$MONGODB_API_VERSION"
17+
fi
18+
819
if [[ $1 == "replica_set" ]]; then
9-
mlaunch init --replicaset --nodes 3 --arbiter --name rs --port 31000 --enableMajorityReadConcern --setParameter enableTestCommands=1
10-
echo "mongodb://localhost:31000/?replicaSet=rs"
20+
mkdir -p $REPLICASET_DIR
21+
mlaunch init --dir $REPLICASET_DIR --replicaset --nodes 3 --arbiter --name rs --port 31000 --enableMajorityReadConcern --setParameter enableTestCommands=1
22+
echo "mongodb://localhost:31000,localhost:31001,localhost:31002/?replicaSet=rs"
1123
elif [[ $1 == "sharded_cluster" ]]; then
12-
mlaunch init --replicaset --nodes 3 --arbiter --name rs --port 51000 --enableMajorityReadConcern --setParameter enableTestCommands=1 --sharded 1 --mongos 2
13-
echo "mongodb://localhost:51000,localhost:51001/"
24+
mkdir -p $SHARDED_DIR
25+
mlaunch init --dir $SHARDED_DIR --replicaset --nodes 3 --arbiter --name rs --port 51000 --enableMajorityReadConcern --setParameter enableTestCommands=1 --sharded 1 --mongos 2
26+
echo "mongodb://localhost:51000,localhost:51001"
1427
elif [[ $1 == "server" ]]; then
15-
mlaunch init --single --setParameter enableTestCommands=1
28+
mkdir -p $SINGLE_DIR
29+
mlaunch init --dir $SINGLE_DIR --single --setParameter enableTestCommands=1 $REQUIRE_API
1630
echo "mongodb://localhost:27017"
1731
else
1832
echo "unsupported topology: $1"

0 commit comments

Comments
 (0)