mirror of https://github.com/mongodb/mongo
361 lines
13 KiB
JavaScript
361 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();
|