/** * Copyright (C) 2016 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/client/dbclientinterface.h" #include "mongo/db/ops/write_ops_parsers.h" #include "mongo/unittest/unittest.h" namespace mongo { TEST(CommandWriteOpsParsers, CommonFields_WriteConcern) { auto writeConcern = BSON("w" << 2); auto cmd = BSON("insert" << "bar" << "documents" << BSON_ARRAY(BSONObj()) << "writeConcern" << writeConcern); auto op = parseInsertCommand("foo", cmd); ASSERT(bool(op.writeConcern)); ASSERT_EQ(*op.writeConcern, writeConcern); } TEST(CommandWriteOpsParsers, CommonFields_BypassDocumentValidation) { for (bool bypassDocumentValidation : {true, false}) { auto cmd = BSON("insert" << "bar" << "documents" << BSON_ARRAY(BSONObj()) << "bypassDocumentValidation" << bypassDocumentValidation); auto op = parseInsertCommand("foo", cmd); ASSERT_EQ(op.bypassDocumentValidation, bypassDocumentValidation); } } TEST(CommandWriteOpsParsers, CommonFields_Ordered) { for (bool ordered : {true, false}) { auto cmd = BSON("insert" << "bar" << "documents" << BSON_ARRAY(BSONObj()) << "ordered" << ordered); auto op = parseInsertCommand("foo", cmd); ASSERT_EQ(op.continueOnError, !ordered); } } TEST(CommandWriteOpsParsers, CommonFields_IgnoredFields) { // These flags are ignored, so there is nothing to check other than that this doesn't throw. auto cmd = BSON("insert" << "bar" << "documents" << BSON_ARRAY(BSONObj()) << "maxTimeMS" << 1000 << "shardVersion" << BSONObj()); parseInsertCommand("foo", cmd); } TEST(CommandWriteOpsParsers, GarbageFieldsAtTopLevel) { auto cmd = BSON("insert" << "bar" << "documents" << BSON_ARRAY(BSONObj()) << "GARBAGE" << 1); ASSERT_THROWS_CODE(parseInsertCommand("foo", cmd), UserException, ErrorCodes::FailedToParse); } TEST(CommandWriteOpsParsers, GarbageFieldsInUpdateDoc) { auto cmd = BSON("update" << "bar" << "updates" << BSON_ARRAY("q" << BSONObj() << "u" << BSONObj() << "GARBAGE" << 1)); ASSERT_THROWS_CODE(parseInsertCommand("foo", cmd), UserException, ErrorCodes::FailedToParse); } TEST(CommandWriteOpsParsers, GarbageFieldsInDeleteDoc) { auto cmd = BSON("delete" << "bar" << "deletes" << BSON_ARRAY("q" << BSONObj() << "limit" << 0 << "GARBAGE" << 1)); ASSERT_THROWS_CODE(parseInsertCommand("foo", cmd), UserException, ErrorCodes::FailedToParse); } TEST(CommandWriteOpsParsers, SingleInsert) { const auto ns = NamespaceString("test", "foo"); const BSONObj obj = BSON("x" << 1); auto cmd = BSON("insert" << ns.coll() << "documents" << BSON_ARRAY(obj)); const auto op = parseInsertCommand(ns.db(), cmd); ASSERT_EQ(op.ns.ns(), ns.ns()); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT(!op.continueOnError); ASSERT_EQ(op.documents.size(), 1u); ASSERT_EQ(op.documents[0], obj); } TEST(CommandWriteOpsParsers, EmptyMultiInsertFails) { const auto ns = NamespaceString("test", "foo"); auto cmd = BSON("insert" << ns.coll() << "documents" << BSONArray()); ASSERT_THROWS_CODE(parseInsertCommand(ns.db(), cmd), UserException, ErrorCodes::InvalidLength); } TEST(CommandWriteOpsParsers, RealMultiInsert) { const auto ns = NamespaceString("test", "foo"); const BSONObj obj0 = BSON("x" << 0); const BSONObj obj1 = BSON("x" << 1); auto cmd = BSON("insert" << ns.coll() << "documents" << BSON_ARRAY(obj0 << obj1)); const auto op = parseInsertCommand(ns.db(), cmd); ASSERT_EQ(op.ns.ns(), ns.ns()); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT(!op.continueOnError); ASSERT_EQ(op.documents.size(), 2u); ASSERT_EQ(op.documents[0], obj0); ASSERT_EQ(op.documents[1], obj1); } TEST(CommandWriteOpsParsers, Update) { const auto ns = NamespaceString("test", "foo"); const BSONObj query = BSON("x" << 1); const BSONObj update = BSON("$inc" << BSON("x" << 1)); for (bool upsert : {false, true}) { for (bool multi : {false, true}) { auto cmd = BSON("update" << ns.coll() << "updates" << BSON_ARRAY(BSON("q" << query << "u" << update << "upsert" << upsert << "multi" << multi))); auto op = parseUpdateCommand(ns.db(), cmd); ASSERT_EQ(op.ns.ns(), ns.ns()); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT_EQ(op.continueOnError, false); ASSERT_EQ(op.updates.size(), 1u); ASSERT_EQ(op.updates[0].query, query); ASSERT_EQ(op.updates[0].update, update); ASSERT_EQ(op.updates[0].upsert, upsert); ASSERT_EQ(op.updates[0].multi, multi); } } } TEST(CommandWriteOpsParsers, Remove) { const auto ns = NamespaceString("test", "foo"); const BSONObj query = BSON("x" << 1); for (bool multi : {false, true}) { auto cmd = BSON("delete" << ns.coll() << "deletes" << BSON_ARRAY(BSON("q" << query << "limit" << (multi ? 0 : 1)))); auto op = parseDeleteCommand(ns.db(), cmd); ASSERT_EQ(op.ns.ns(), ns.ns()); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT_EQ(op.continueOnError, false); ASSERT_EQ(op.deletes.size(), 1u); ASSERT_EQ(op.deletes[0].query, query); ASSERT_EQ(op.deletes[0].multi, multi); } } TEST(CommandWriteOpsParsers, RemoveErrorsWithBadLimit) { // Only 1 and 0 should be accepted. for (BSONElement limit : BSON_ARRAY(-1 << 2 << 0.5)) { auto cmd = BSON("delete" << "bar" << "deletes" << BSON_ARRAY("q" << BSONObj() << "limit" << limit)); ASSERT_THROWS_CODE( parseInsertCommand("foo", cmd), UserException, ErrorCodes::FailedToParse); } } namespace { /** * A mock DBClient that just captures the Message that is sent for legacy writes. */ class MyMockDBClient final : public DBClientBase { public: Message message; // The last message sent. void say(Message& toSend, bool isRetry = false, std::string* actualServer = nullptr) { message = std::move(toSend); } // The rest of these are just filling out the pure-virtual parts of the interface. bool lazySupported() const { return false; } std::string getServerAddress() const { return ""; } std::string toString() const { return ""; } bool call(Message& toSend, Message& response, bool assertOk, std::string* actualServer) { invariant(!"call() not implemented"); } virtual int getMinWireVersion() { return 0; } virtual int getMaxWireVersion() { return 0; } virtual bool isFailed() const { return false; } virtual bool isStillConnected() { return true; } virtual double getSoTimeout() const { return 0; } virtual ConnectionString::ConnectionType type() const { return ConnectionString::MASTER; } }; } // namespace TEST(LegacyWriteOpsParsers, SingleInsert) { const std::string ns = "test.foo"; const BSONObj obj = BSON("x" << 1); for (bool continueOnError : {false, true}) { MyMockDBClient client; client.insert(ns, obj, continueOnError ? InsertOption_ContinueOnError : 0); const auto op = parseLegacyInsert(client.message); ASSERT_EQ(op.ns.ns(), ns); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT_EQ(op.continueOnError, continueOnError); ASSERT_EQ(op.documents.size(), 1u); ASSERT_EQ(op.documents[0], obj); } } TEST(LegacyWriteOpsParsers, EmptyMultiInsertFails) { const std::string ns = "test.foo"; for (bool continueOnError : {false, true}) { MyMockDBClient client; client.insert( ns, std::vector{}, continueOnError ? InsertOption_ContinueOnError : 0); ASSERT_THROWS_CODE( parseLegacyInsert(client.message), UserException, ErrorCodes::InvalidLength); } } TEST(LegacyWriteOpsParsers, RealMultiInsert) { const std::string ns = "test.foo"; const BSONObj obj0 = BSON("x" << 0); const BSONObj obj1 = BSON("x" << 1); for (bool continueOnError : {false, true}) { MyMockDBClient client; client.insert(ns, {obj0, obj1}, continueOnError ? InsertOption_ContinueOnError : 0); const auto op = parseLegacyInsert(client.message); ASSERT_EQ(op.ns.ns(), ns); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT_EQ(op.continueOnError, continueOnError); ASSERT_EQ(op.documents.size(), 2u); ASSERT_EQ(op.documents[0], obj0); ASSERT_EQ(op.documents[1], obj1); } } TEST(LegacyWriteOpsParsers, Update) { const std::string ns = "test.foo"; const BSONObj query = BSON("x" << 1); const BSONObj update = BSON("$inc" << BSON("x" << 1)); for (bool upsert : {false, true}) { for (bool multi : {false, true}) { MyMockDBClient client; client.update(ns, query, update, upsert, multi); const auto op = parseLegacyUpdate(client.message); ASSERT_EQ(op.ns.ns(), ns); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT_EQ(op.continueOnError, false); ASSERT_EQ(op.updates.size(), 1u); ASSERT_EQ(op.updates[0].query, query); ASSERT_EQ(op.updates[0].update, update); ASSERT_EQ(op.updates[0].upsert, upsert); ASSERT_EQ(op.updates[0].multi, multi); } } } TEST(LegacyWriteOpsParsers, Remove) { const std::string ns = "test.foo"; const BSONObj query = BSON("x" << 1); for (bool multi : {false, true}) { MyMockDBClient client; client.remove(ns, query, multi ? 0 : RemoveOption_JustOne); const auto op = parseLegacyDelete(client.message); ASSERT_EQ(op.ns.ns(), ns); ASSERT(!op.writeConcern); ASSERT(!op.bypassDocumentValidation); ASSERT_EQ(op.continueOnError, false); ASSERT_EQ(op.deletes.size(), 1u); ASSERT_EQ(op.deletes[0].query, query); ASSERT_EQ(op.deletes[0].multi, multi); } } }