mirror of https://github.com/mongodb/mongo
307 lines
13 KiB
JavaScript
307 lines
13 KiB
JavaScript
/**
|
|
* Test the shell's --apiVersion and other options related to the MongoDB Stable API, and
|
|
* test passing API parameters to the Mongo() constructor.
|
|
*
|
|
* @tags: [
|
|
* requires_persistence,
|
|
* ]
|
|
*/
|
|
|
|
import {ReplSetTest} from "jstests/libs/replsettest.js";
|
|
|
|
const testCases = [
|
|
// [requireApiVersion server parameter, expect success, command, API parameters]
|
|
[false, true, {ping: 1}, {}],
|
|
[false, true, {ping: 1}, {version: '1'}],
|
|
[false, true, {count: 'collection'}, {version: '1'}],
|
|
[false, true, {getLog: 'global'}, {version: '1'}],
|
|
[false, true, {getLog: 'global'}, {version: '1', deprecationErrors: true}],
|
|
// getLog isn't in API Version 1, so it's banned with strict: true.
|
|
[false, false, {getLog: 'global'}, {version: '1', strict: true}],
|
|
[false, true, {ping: 1}, {version: '1', strict: true}],
|
|
[false, true, {testDeprecation: 1}, {version: '1', strict: true}],
|
|
[false, false, {testDeprecation: 1}, {version: '1', deprecationErrors: true}],
|
|
[false, false, {testDeprecation: 1}, {version: '1', strict: true, deprecationErrors: true}],
|
|
// tests with setParameter requireApiVersion: true.
|
|
[true, true, {count: 'collection'}, {version: '1'}],
|
|
[true, true, {count: 'collection'}, {version: '1', strict: true}],
|
|
[true, false, {ping: 1}, {}],
|
|
[true, true, {ping: 1}, {version: '1'}],
|
|
];
|
|
|
|
function runShellWithScript(port, requireApiVersion, expectSuccess, script, api) {
|
|
let shellArgs = [];
|
|
if (api.version) {
|
|
shellArgs.push('--apiVersion', api.version);
|
|
}
|
|
|
|
if (api.strict) {
|
|
shellArgs.push('--apiStrict');
|
|
}
|
|
|
|
if (api.deprecationErrors) {
|
|
shellArgs.push('--apiDeprecationErrors');
|
|
}
|
|
|
|
jsTestLog(`Run shell with script ${script} and args ${tojson(shellArgs)},` +
|
|
` requireApiVersion = ${requireApiVersion}, expectSuccess = ${expectSuccess}`);
|
|
|
|
const result = runMongoProgram.apply(
|
|
null, ['mongo', '--port', port, '--eval', script].concat(shellArgs || []));
|
|
|
|
if (expectSuccess) {
|
|
assert.eq(
|
|
result,
|
|
0,
|
|
`Error running shell with script ${tojson(script)} and args ${tojson(shellArgs)}`);
|
|
} else {
|
|
assert.neq(result,
|
|
0,
|
|
`Unexpected success running shell with` +
|
|
` script ${tojson(script)} and args ${tojson(shellArgs)}`);
|
|
}
|
|
}
|
|
|
|
function runShellWithCommand(port, requireApiVersion, expectSuccess, command, api) {
|
|
// Test runCommand and runReadCommand.
|
|
const scripts = [
|
|
`assert.commandWorked(db.getSiblingDB("admin").runCommand(${tojson(command)}))`,
|
|
`assert.commandWorked(db.getSiblingDB("admin").runReadCommand(${tojson(command)}))`,
|
|
];
|
|
|
|
for (const script of scripts) {
|
|
runShellWithScript(port, requireApiVersion, expectSuccess, script, api);
|
|
}
|
|
}
|
|
|
|
function newMongo(port, requireApiVersion, expectSuccess, command, api) {
|
|
jsTestLog(`Construct Mongo object with command ${tojson(command)} and args ${tojson(api)},` +
|
|
` requireApiVersion = ${requireApiVersion}, expectSuccess = ${expectSuccess}`);
|
|
if (expectSuccess) {
|
|
const m = new Mongo(
|
|
`mongodb://localhost:${port}`, undefined /* encryptedDBClientCallback */, {api: api});
|
|
const reply = m.adminCommand(command);
|
|
assert.commandWorked(reply, command);
|
|
} else {
|
|
let m;
|
|
try {
|
|
m = new Mongo(`mongodb://localhost:${port}`,
|
|
undefined /* encryptedDBClientCallback */,
|
|
{api: api});
|
|
} catch (e) {
|
|
// The constructor threw, but we expected failure.
|
|
print(e);
|
|
return;
|
|
}
|
|
const reply = m.adminCommand(command);
|
|
assert.commandFailed(reply, command);
|
|
}
|
|
}
|
|
|
|
const mongod = MongoRunner.runMongod({verbose: 2});
|
|
|
|
for (let [requireApiVersion, successExpected, command, api] of testCases) {
|
|
const m = new Mongo(`localhost:${mongod.port}`, undefined, {api: {version: '1'}});
|
|
assert.commandWorked(
|
|
m.getDB("admin").runCommand({setParameter: 1, requireApiVersion: requireApiVersion}));
|
|
|
|
runShellWithCommand(mongod.port, requireApiVersion, successExpected, command, api);
|
|
newMongo(mongod.port, requireApiVersion, successExpected, command, api);
|
|
}
|
|
|
|
let m = new Mongo(`localhost:${mongod.port}`, undefined, {api: {version: '1'}});
|
|
assert.commandWorked(m.getDB('admin').runCommand(
|
|
{insert: 'collection', documents: [{}, {}, {}, {}, {}, {}], apiVersion: '1'}));
|
|
|
|
for (let requireApiVersion of [false, true]) {
|
|
// Omit api = {}, that's tested elsewhere.
|
|
for (let api of [{version: '1'},
|
|
{version: '1', strict: true},
|
|
{version: '1', deprecationErrors: true},
|
|
{version: '1', strict: true, deprecationErrors: true},
|
|
]) {
|
|
assert.commandWorked(
|
|
m.getDB("admin").runCommand({setParameter: 1, requireApiVersion: requireApiVersion}));
|
|
|
|
/*
|
|
* Test getMore. Create a cursor with the right API version parameters. Use an explicit lsid
|
|
* to override implicit session creation.
|
|
*/
|
|
const lsid = UUID();
|
|
const versionedConn = new Mongo(`localhost:${mongod.port}`, undefined, {api: api});
|
|
const findReply = assert.commandWorked(versionedConn.getDB('admin').runCommand(
|
|
{find: 'collection', batchSize: 1, lsid: {id: lsid}}));
|
|
const getMoreCmd = {
|
|
getMore: findReply.cursor.id,
|
|
collection: 'collection',
|
|
lsid: {id: lsid},
|
|
batchSize: 1
|
|
};
|
|
|
|
runShellWithCommand(
|
|
mongod.port, requireApiVersion, true /* expectSuccess */, getMoreCmd, api);
|
|
newMongo(mongod.port, requireApiVersion, true /* expectSuccess */, getMoreCmd, api);
|
|
|
|
/*
|
|
* Test unacknowledged writes (OP_MSG with moreToCome).
|
|
*/
|
|
versionedConn.getDB('admin')['collection2'].insertMany([{}, {}]);
|
|
const deleteScript = "db.getSiblingDB('admin').collection2.deleteOne({}, {w: 0});";
|
|
runShellWithScript(
|
|
mongod.port, requireApiVersion, true /* expectSuccess */, deleteScript, api);
|
|
// The delete succeeds, collection2 had 2 records, it soon has 1. Use assert.soon since the
|
|
// write is unacknowledged. Use countDocuments which is apiStrict-compatible.
|
|
assert.soon(() => {
|
|
return 1 === versionedConn.getDB('admin')['collection2'].countDocuments({});
|
|
});
|
|
const deleteCmd = {
|
|
delete: 'collection2',
|
|
deletes: [{q: {}, limit: 1}],
|
|
writeConcern: {w: 0}
|
|
};
|
|
newMongo(mongod.port, requireApiVersion, true /* expectSuccess */, deleteCmd, api);
|
|
// The delete succeeds, collection2 soon has 0 records.
|
|
assert.soon(() => {
|
|
return 0 === versionedConn.getDB('admin')['collection2'].countDocuments({});
|
|
});
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Shell-specific tests.
|
|
*/
|
|
|
|
// Version 2 is not supported.
|
|
runShellWithCommand(mongod.port, false, false, {ping: 1}, {version: '2'});
|
|
// apiVersion is required if strict or deprecationErrors is included
|
|
runShellWithCommand(mongod.port, false, false, {ping: 1}, {strict: true});
|
|
runShellWithCommand(mongod.port, false, false, {ping: 1}, {deprecationErrors: true});
|
|
runShellWithCommand(mongod.port, false, false, {ping: 1}, {strict: true, deprecationErrors: true});
|
|
|
|
if (m.adminCommand('buildinfo').modules.indexOf('enterprise') > -1) {
|
|
/*
|
|
* Test that we can call buildinfo while assembling the shell prompt, in order to determine that
|
|
* this is MongoDB Enterprise, although buildinfo is not in API Version 1 and the shell is
|
|
* running with --apiStrict.
|
|
*/
|
|
const testPrompt = "assert(RegExp('MongoDB Enterprise').test(defaultPrompt()))";
|
|
const result = runMongoProgram(
|
|
'mongo', '--port', mongod.port, '--apiVersion', '1', '--apiStrict', '--eval', testPrompt);
|
|
assert.eq(result, 0, `Error running shell with script '${testPrompt}'`);
|
|
}
|
|
|
|
// Test that shell helpers which return a cursor do not accept API versioning parameters. This is to
|
|
// avoid the potential pitfall of sending API parameters on the initial command but not on
|
|
// subsequent getMore's.
|
|
const coll = m.getDB("test").collection;
|
|
assert.throws(() => coll.aggregate([], {apiVersion: 1}));
|
|
assert.throws(() => coll.aggregate([], {apiStrict: true}));
|
|
assert.throws(() => coll.aggregate([], {apiDeprecationErrors: true}));
|
|
assert.throws(() => coll.find({}, {}, 1, 0, 1, {apiVersion: 1}));
|
|
assert.throws(() => coll.find({}, {}, 1, 0, 1, {apiStrict: true}));
|
|
assert.throws(() => coll.find({}, {}, 1, 0, 1, {apiDeprecationErrors: true}));
|
|
assert.throws(() => coll.watch({}, {apiVersion: 1}));
|
|
assert.throws(() => coll.watch({}, {apiStrict: true}));
|
|
assert.throws(() => coll.watch({}, {apiDeprecationErrors: true}));
|
|
assert.throws(() => coll.getDB().watch({}, {apiVersion: 1}));
|
|
assert.throws(() => coll.getDB().watch({}, {apiStrict: true}));
|
|
assert.throws(() => coll.getDB().watch({}, {apiDeprecationErrors: true}));
|
|
|
|
/*
|
|
* Mongo-specific tests.
|
|
*/
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, "not an object");
|
|
}, [], "Mongo() constructor should check that options argument is an object");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: "not an object"});
|
|
}, [], "Mongo() constructor should check that 'api' is an object");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {version: 1}});
|
|
}, [], "Mongo() constructor should reject API version 1 (as a number)");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {version: '2'}});
|
|
}, [], "Mongo() constructor should reject API version 2");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {version: '1', strict: 1}});
|
|
}, [], "Mongo() constructor should reject strict: 1");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {version: '1', strict: 'asdf'}});
|
|
}, [], "Mongo() constructor should reject strict: 'asdf");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {version: '1', deprecationErrors: 1}});
|
|
}, [], "Mongo() constructor should reject deprecationErrors: 1");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {version: '1', deprecationErrors: 'asdf'}});
|
|
}, [], "Mongo() constructor should reject deprecationErrors: 'asdf'");
|
|
|
|
// apiVersion is required if strict or deprecationErrors is included
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {strict: true}});
|
|
}, [], "Mongo() constructor should reject 'strict' without 'version'");
|
|
|
|
assert.throws(() => {
|
|
new Mongo(mongod.port, null, {api: {deprecationErrors: true}});
|
|
}, [], "Mongo() constructor should reject 'deprecationErrors' without 'version'");
|
|
|
|
MongoRunner.stopMongod(mongod);
|
|
|
|
const rst = new ReplSetTest({nodes: 1});
|
|
rst.startSet();
|
|
rst.initiate();
|
|
const primaryPort = rst.getPrimary().port;
|
|
|
|
/*
|
|
* Test that we can call replSetGetStatus while assembling the shell prompt, although
|
|
* replSetGetStatus is not in API Version 1 and the shell is running with --apiStrict.
|
|
*/
|
|
const testPrompt = "assert(RegExp('PRIMARY').test(defaultPrompt()))";
|
|
const result = runMongoProgram(
|
|
'mongo', '--port', primaryPort, '--apiVersion', '1', '--apiStrict', '--eval', testPrompt);
|
|
assert.eq(result, 0, `Error running shell with script '${testPrompt}'`);
|
|
|
|
/*
|
|
* Test transaction-continuing commands with API parameters.
|
|
*/
|
|
m = new Mongo(`localhost:${primaryPort}`, undefined, {api: {version: '1'}});
|
|
for (let requireApiVersion of [false, true]) {
|
|
// Omit api = {}, that's tested elsewhere.
|
|
for (let api of [{version: '1'},
|
|
{version: '1', strict: true},
|
|
{version: '1', deprecationErrors: true},
|
|
{version: '1', strict: true, deprecationErrors: true},
|
|
]) {
|
|
assert.commandWorked(
|
|
m.getDB("admin").runCommand({setParameter: 1, requireApiVersion: requireApiVersion}));
|
|
|
|
// Start a transaction with the right API version parameters. Use an explicit lsid to
|
|
// override implicit session creation.
|
|
const lsid = UUID();
|
|
const versionedConn = new Mongo(`localhost:${primaryPort}`, undefined, {api: api});
|
|
assert.commandWorked(versionedConn.getDB('admin').runCommand({
|
|
find: 'collection',
|
|
lsid: {id: lsid},
|
|
txnNumber: NumberLong(1),
|
|
autocommit: false,
|
|
startTransaction: true
|
|
}));
|
|
|
|
const continueCmd =
|
|
{find: 'collection', lsid: {id: lsid}, txnNumber: NumberLong(1), autocommit: false};
|
|
|
|
runShellWithCommand(
|
|
primaryPort, requireApiVersion, true /* expectSuccess */, continueCmd, api);
|
|
newMongo(primaryPort, requireApiVersion, true /* expectSuccess */, continueCmd, api);
|
|
}
|
|
}
|
|
|
|
assert.commandWorked(m.getDB('admin').runCommand({setParameter: 1, requireApiVersion: false}));
|
|
|
|
rst.stopSet(); |