/** * Copyright (C) 2017 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/bson/bsonmisc.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/idl/unittest_gen.h" #include "mongo/unittest/unittest.h" #include "mongo/util/net/op_msg.h" using namespace mongo::idl::test; using namespace mongo::idl::import; namespace mongo { namespace { bool isEquals(ConstDataRange left, const std::vector& right) { auto rightCDR = makeCDR(right); return std::equal(left.data(), left.data() + left.length(), rightCDR.data(), rightCDR.data() + rightCDR.length()); } bool isEquals(const std::array& left, const std::array& right) { return std::equal( left.data(), left.data() + left.size(), right.data(), right.data() + right.size()); } bool isEqual(const ConstDataRange& left, const ConstDataRange& right) { return std::equal( left.data(), left.data() + left.length(), right.data(), right.data() + right.length()); } bool isEquals(const std::vector& left, const std::vector>& rightVector) { auto right = transformVector(rightVector); return std::equal( left.data(), left.data() + left.size(), right.data(), right.data() + right.size(), isEqual); } bool isEquals(const std::vector>& left, const std::vector>& right) { return std::equal( left.data(), left.data() + left.size(), right.data(), right.data() + right.size()); } /** * Flatten an OpMsgRequest into a BSONObj. */ BSONObj flatten(const OpMsgRequest& msg) { BSONObjBuilder builder; builder.appendElements(msg.body); for (auto&& docSeq : msg.sequences) { builder.append(docSeq.name, docSeq.objs); } return builder.obj(); } /** * Validate two OpMsgRequests are the same regardless of whether they both use DocumentSequences. */ void assertOpMsgEquals(const OpMsgRequest& left, const OpMsgRequest& right) { auto flatLeft = flatten(left); auto flatRight = flatten(right); ASSERT_BSONOBJ_EQ(flatLeft, flatRight); } /** * Validate two OpMsgRequests are the same including their DocumentSequences. */ void assertOpMsgEqualsExact(const OpMsgRequest& left, const OpMsgRequest& right) { ASSERT_BSONOBJ_EQ(left.body, right.body); ASSERT_EQUALS(left.sequences.size(), right.sequences.size()); for (size_t i = 0; i < left.sequences.size(); ++i) { auto leftItem = left.sequences[i]; auto rightItem = right.sequences[i]; ASSERT_TRUE(std::equal(leftItem.objs.begin(), leftItem.objs.end(), rightItem.objs.begin(), rightItem.objs.end(), [](const BSONObj& leftBson, const BSONObj& rightBson) { return SimpleBSONObjComparator::kInstance.compare( leftBson, rightBson) == 0; })); ASSERT_EQUALS(leftItem.name, rightItem.name); } } BSONObj appendDB(const BSONObj& obj, StringData dbName) { BSONObjBuilder builder; builder.appendElements(obj); builder.append("$db", dbName); return builder.obj(); } // Use a separate function to get better error messages when types do not match. template void assert_same_types() { MONGO_STATIC_ASSERT(std::is_same::value); } template void TestLoopback(TestT test_value) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("value" << test_value); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), Test_bson_type); auto testStruct = ParserT::parse(ctxt, testDoc); assert_same_types(); ASSERT_EQUALS(testStruct.getValue(), test_value); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can roundtrip from the just parsed document { auto loopbackDoc = testStruct.toBSON(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; ParserT one_new; one_new.setValue(test_value); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); // Validate the operator == works // Use ASSERT instead of ASSERT_EQ to avoid operator<< ASSERT(one_new == testStruct); } } /// Type tests: // Positive: Test we can serialize the type out and back again TEST(IDLOneTypeTests, TestLoopbackTest) { TestLoopback("test_value"); TestLoopback(123); TestLoopback(456); TestLoopback(3.14159); TestLoopback(true); TestLoopback(OID::max()); TestLoopback(Date_t::now()); TestLoopback(Timestamp::max()); } // Test a BSONObj can be passed through an IDL type TEST(IDLOneTypeTests, TestObjectLoopbackTest) { IDLParserErrorContext ctxt("root"); auto testValue = BSON("Hello" << "World"); auto testDoc = BSON("value" << testValue); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), Object); auto testStruct = One_plain_object::parse(ctxt, testDoc); assert_same_types(); ASSERT_BSONOBJ_EQ(testStruct.getValue(), testValue); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; One_plain_object one_new; one_new.setValue(testValue); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Test if a given value for a given bson document parses successfully or fails if the bson types // mismatch. template void TestParse(TestT test_value) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("value" << test_value); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), Test_bson_type); if (Parser_bson_type != Test_bson_type) { ASSERT_THROWS(ParserT::parse(ctxt, testDoc), AssertionException); } else { (void)ParserT::parse(ctxt, testDoc); } } // Test each of types either fail or succeeded based on the parser's bson type template void TestParsers() { TestParse("test_value"); TestParse(123); TestParse(456); TestParse(3.14159); TestParse(true); TestParse(OID::max()); TestParse(Date_t::now()); TestParse(Timestamp::max()); } // Negative: document with wrong types for required field TEST(IDLOneTypeTests, TestNegativeWrongTypes) { TestParsers(); TestParsers(); TestParsers(); TestParsers(); TestParsers(); TestParsers(); TestParsers(); TestParsers(); } // Mixed: test a type that accepts multiple bson types TEST(IDLOneTypeTests, TestSafeInt32) { TestParse("test_value"); TestParse(123); TestParse(456); TestParse(3.14159); TestParse(true); TestParse(OID::max()); TestParse(Date_t::now()); TestParse(Timestamp::max()); } // Mixed: test a type that accepts NamespaceString TEST(IDLOneTypeTests, TestNamespaceString) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(One_namespacestring::kValueFieldName << "foo.bar"); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), String); auto testStruct = One_namespacestring::parse(ctxt, testDoc); assert_same_types(); ASSERT_EQUALS(testStruct.getValue(), NamespaceString("foo.bar")); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; One_namespacestring one_new; one_new.setValue(NamespaceString("foo.bar")); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } // Negative: invalid namespace { auto testBadDoc = BSON("value" << StringData("foo\0bar", 7)); ASSERT_THROWS(One_namespacestring::parse(ctxt, testBadDoc), AssertionException); } } // Postive: Test any type TEST(IDLOneTypeTests, TestAnyType) { IDLParserErrorContext ctxt("root"); // Positive: string field { auto testDoc = BSON("value" << "Foo"); auto testStruct = One_any_basic_type::parse(ctxt, testDoc); BSONObjBuilder builder; testStruct.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } // Positive: int field { auto testDoc = BSON("value" << 12); auto testStruct = One_any_basic_type::parse(ctxt, testDoc); BSONObjBuilder builder; testStruct.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Postive: Test object type TEST(IDLOneTypeTests, TestObjectType) { IDLParserErrorContext ctxt("root"); // Positive: object { auto testDoc = BSON("value" << BSON("value" << "foo")); auto testStruct = One_any_basic_type::parse(ctxt, testDoc); BSONObjBuilder builder; testStruct.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Negative: Test object type TEST(IDLOneTypeTests, TestObjectTypeNegative) { IDLParserErrorContext ctxt("root"); // Negative: string field { auto testDoc = BSON("value" << "Foo"); One_any_basic_type::parse(ctxt, testDoc); } // Negative: int field { auto testDoc = BSON("value" << 12); One_any_basic_type::parse(ctxt, testDoc); } } /// Struct tests: // Positive: strict, 3 required fields // Negative: strict, ensure extra fields fail // Negative: strict, duplicate fields TEST(IDLStructTests, TestStrictStruct) { IDLParserErrorContext ctxt("root"); // Positive: Just 3 required fields { auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234); RequiredStrictField3::parse(ctxt, testDoc); } // Negative: Missing 1 required field { auto testDoc = BSON("field2" << 123 << "field3" << 1234); ASSERT_THROWS(RequiredStrictField3::parse(ctxt, testDoc), AssertionException); } { auto testDoc = BSON("field1" << 12 << "field3" << 1234); ASSERT_THROWS(RequiredStrictField3::parse(ctxt, testDoc), AssertionException); } { auto testDoc = BSON("field1" << 12 << "field2" << 123); ASSERT_THROWS(RequiredStrictField3::parse(ctxt, testDoc), AssertionException); } // Negative: Extra field { auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234 << "field4" << 1234); ASSERT_THROWS(RequiredStrictField3::parse(ctxt, testDoc), AssertionException); } // Negative: Duplicate field { auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234 << "field2" << 12345); ASSERT_THROWS(RequiredStrictField3::parse(ctxt, testDoc), AssertionException); } } // Positive: non-strict, ensure extra fields work // Negative: non-strict, duplicate fields TEST(IDLStructTests, TestNonStrictStruct) { IDLParserErrorContext ctxt("root"); // Positive: Just 3 required fields { auto testDoc = BSON(RequiredNonStrictField3::kCppField1FieldName << 12 << "2" << 123 << "3" << 1234); auto testStruct = RequiredNonStrictField3::parse(ctxt, testDoc); assert_same_types(); assert_same_types(); assert_same_types(); } // Negative: Missing 1 required field { auto testDoc = BSON("2" << 123 << "3" << 1234); ASSERT_THROWS(RequiredNonStrictField3::parse(ctxt, testDoc), AssertionException); } { auto testDoc = BSON("1" << 12 << "3" << 1234); ASSERT_THROWS(RequiredNonStrictField3::parse(ctxt, testDoc), AssertionException); } { auto testDoc = BSON("1" << 12 << "2" << 123); ASSERT_THROWS(RequiredNonStrictField3::parse(ctxt, testDoc), AssertionException); } // Positive: Extra field { auto testDoc = BSON("1" << 12 << "2" << 123 << "3" << 1234 << "field4" << 1234); RequiredNonStrictField3::parse(ctxt, testDoc); } // Negative: Duplicate field { auto testDoc = BSON("1" << 12 << "2" << 123 << "3" << 1234 << "2" << 12345); ASSERT_THROWS(RequiredNonStrictField3::parse(ctxt, testDoc), AssertionException); } // Negative: Duplicate extra field { auto testDoc = BSON("field4" << 1234 << "1" << 12 << "2" << 123 << "3" << 1234 << "field4" << 1234); ASSERT_THROWS(RequiredNonStrictField3::parse(ctxt, testDoc), AssertionException); } } /// Struct default comparison tests TEST(IDLCompareTests, TestAllFields) { IDLParserErrorContext ctxt("root"); // Positive: equality works { CompareAllField3 origStruct; origStruct.setField1(12); origStruct.setField2(123); origStruct.setField3(1234); auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234); auto parsedStruct = CompareAllField3::parse(ctxt, testDoc); // Avoid ASSET_ to avoid operator << ASSERT_TRUE(origStruct == parsedStruct); ASSERT_FALSE(origStruct != parsedStruct); ASSERT_FALSE(origStruct < parsedStruct); ASSERT_FALSE(parsedStruct < origStruct); } // Positive: not equality works in field 3 { CompareAllField3 origStruct; origStruct.setField1(12); origStruct.setField2(123); origStruct.setField3(12345); auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234); auto parsedStruct = CompareAllField3::parse(ctxt, testDoc); // Avoid ASSET_ to avoid operator << ASSERT_FALSE(origStruct == parsedStruct); ASSERT_TRUE(origStruct != parsedStruct); ASSERT_FALSE(origStruct < parsedStruct); ASSERT_TRUE(parsedStruct < origStruct); } } /// Struct partial comparison tests TEST(IDLCompareTests, TestSomeFields) { IDLParserErrorContext ctxt("root"); // Positive: partial equality works when field 2 is different { CompareSomeField3 origStruct; origStruct.setField1(12); origStruct.setField2(12345); origStruct.setField3(1234); auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234); auto parsedStruct = CompareSomeField3::parse(ctxt, testDoc); // Avoid ASSET_ to avoid operator << ASSERT_TRUE(origStruct == parsedStruct); ASSERT_FALSE(origStruct != parsedStruct); ASSERT_FALSE(origStruct < parsedStruct); ASSERT_FALSE(parsedStruct < origStruct); } // Positive: partial equality works when field 3 is different { CompareSomeField3 origStruct; origStruct.setField1(12); origStruct.setField2(1); origStruct.setField3(12345); auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234); auto parsedStruct = CompareSomeField3::parse(ctxt, testDoc); // Avoid ASSET_ to avoid operator << ASSERT_FALSE(origStruct == parsedStruct); ASSERT_TRUE(origStruct != parsedStruct); ASSERT_FALSE(origStruct < parsedStruct); ASSERT_TRUE(parsedStruct < origStruct); } // Positive: partial equality works when field 1 is different { CompareSomeField3 origStruct; origStruct.setField1(123); origStruct.setField2(1); origStruct.setField3(1234); auto testDoc = BSON("field1" << 12 << "field2" << 123 << "field3" << 1234); auto parsedStruct = CompareSomeField3::parse(ctxt, testDoc); // Avoid ASSET_ to avoid operator << ASSERT_FALSE(origStruct == parsedStruct); ASSERT_TRUE(origStruct != parsedStruct); ASSERT_FALSE(origStruct < parsedStruct); ASSERT_TRUE(parsedStruct < origStruct); } } /// Field tests // Positive: check ignored field is ignored TEST(IDLFieldTests, TestStrictStructIgnoredField) { IDLParserErrorContext ctxt("root"); // Positive: Test ignored field is ignored { auto testDoc = BSON("required_field" << 12 << "ignored_field" << 123); IgnoredField::parse(ctxt, testDoc); } // Positive: Test ignored field is not required { auto testDoc = BSON("required_field" << 12); IgnoredField::parse(ctxt, testDoc); } } // First test: test an empty document and the default value // Second test: test a non-empty document and that we do not get the default value #define TEST_DEFAULT_VALUES(field_name, default_value, new_value) \ { \ auto testDoc = BSONObj(); \ auto testStruct = Default_values::parse(ctxt, testDoc); \ ASSERT_EQUALS(testStruct.get##field_name(), default_value); \ } \ { \ auto testDoc = BSON(#field_name << new_value); \ auto testStruct = Default_values::parse(ctxt, testDoc); \ ASSERT_EQUALS(testStruct.get##field_name(), new_value); \ } // Mixed: struct strict, and ignored field works TEST(IDLFieldTests, TestDefaultFields) { IDLParserErrorContext ctxt("root"); TEST_DEFAULT_VALUES(V_string, "a default", "foo"); TEST_DEFAULT_VALUES(V_int, 42, 3); TEST_DEFAULT_VALUES(V_long, 423, 4LL); TEST_DEFAULT_VALUES(V_double, 3.14159, 2.8); TEST_DEFAULT_VALUES(V_bool, true, false); } // Positive: struct strict, and optional field works TEST(IDLFieldTests, TestOptionalFields) { IDLParserErrorContext ctxt("root"); // Positive: Test document with only string field { auto testDoc = BSON("field1" << "Foo"); auto testStruct = Optional_field::parse(ctxt, testDoc); assert_same_types>(); assert_same_types>(); assert_same_types&>(); assert_same_types>(); assert_same_types>>(); ASSERT_EQUALS("Foo", testStruct.getField1().get()); ASSERT_FALSE(testStruct.getField2().is_initialized()); } // Positive: Serialize struct with only string field { BSONObjBuilder builder; Optional_field testStruct; auto field1 = boost::optional("Foo"); testStruct.setField1(field1); testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); auto testDoc = BSON("field1" << "Foo"); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test document with only int field { auto testDoc = BSON("field2" << 123); auto testStruct = Optional_field::parse(ctxt, testDoc); ASSERT_FALSE(testStruct.getField1().is_initialized()); ASSERT_EQUALS(123, testStruct.getField2().get()); } // Positive: Serialize struct with only int field { BSONObjBuilder builder; Optional_field testStruct; testStruct.setField2(123); testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); auto testDoc = BSON("field2" << 123); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } } // Positive: Test a nested struct TEST(IDLNestedStruct, TestDuplicateTypes) { IDLParserErrorContext ctxt("root"); // Positive: Test document auto testDoc = BSON( "field1" << BSON("field1" << 1 << "field2" << 2 << "field3" << 3) << "field3" << BSON("field1" << 4 << "field2" << 5 << "field3" << 6)); auto testStruct = NestedWithDuplicateTypes::parse(ctxt, testDoc); assert_same_types(); assert_same_types&>(); assert_same_types(); ASSERT_EQUALS(1, testStruct.getField1().getField1()); ASSERT_EQUALS(2, testStruct.getField1().getField2()); ASSERT_EQUALS(3, testStruct.getField1().getField3()); ASSERT_FALSE(testStruct.getField2()); ASSERT_EQUALS(4, testStruct.getField3().getField1()); ASSERT_EQUALS(5, testStruct.getField3().getField2()); ASSERT_EQUALS(6, testStruct.getField3().getField3()); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; NestedWithDuplicateTypes nested_structs; RequiredStrictField3 f1; f1.setField1(1); f1.setField2(2); f1.setField3(3); nested_structs.setField1(f1); RequiredStrictField3 f3; f3.setField1(4); f3.setField2(5); f3.setField3(6); nested_structs.setField3(f3); nested_structs.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Positive: Arrays of simple types TEST(IDLArrayTests, TestSimpleArrays) { IDLParserErrorContext ctxt("root"); // Positive: Test document uint8_t array1[] = {1, 2, 3}; uint8_t array2[] = {4, 6, 8}; uint8_t array15[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; uint8_t array16[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; auto testDoc = BSON("field1" << BSON_ARRAY("Foo" << "Bar" << "???") << "field2" << BSON_ARRAY(1 << 2 << 3) << "field3" << BSON_ARRAY(1.2 << 3.4 << 5.6) << "field4" << BSON_ARRAY(BSONBinData(array1, 3, BinDataGeneral) << BSONBinData(array2, 3, BinDataGeneral)) << "field5" << BSON_ARRAY(BSONBinData(array15, 16, newUUID) << BSONBinData(array16, 16, newUUID))); auto testStruct = Simple_array_fields::parse(ctxt, testDoc); assert_same_types>(); assert_same_types&>(); assert_same_types&>(); assert_same_types>(); assert_same_types>&>(); std::vector field1{"Foo", "Bar", "???"}; ASSERT_TRUE(field1 == testStruct.getField1()); std::vector field2{1, 2, 3}; ASSERT_TRUE(field2 == testStruct.getField2()); std::vector field3{1.2, 3.4, 5.6}; ASSERT_TRUE(field3 == testStruct.getField3()); std::vector> field4{{1, 2, 3}, {4, 6, 8}}; ASSERT_TRUE(isEquals(testStruct.getField4(), field4)); std::vector> field5{ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}; ASSERT_TRUE(isEquals(testStruct.getField5(), field5)); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; Simple_array_fields array_fields; array_fields.setField1(field1); array_fields.setField2(field2); array_fields.setField3(field3); array_fields.setField4(transformVector(field4)); array_fields.setField5(field5); array_fields.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Positive: Optional Arrays TEST(IDLArrayTests, TestSimpleOptionalArrays) { IDLParserErrorContext ctxt("root"); // Positive: Test document auto testDoc = BSON("field1" << BSON_ARRAY("Foo" << "Bar" << "???") << "field2" << BSON_ARRAY(1 << 2 << 3) << "field3" << BSON_ARRAY(1.2 << 3.4 << 5.6) ); auto testStruct = Optional_array_fields::parse(ctxt, testDoc); assert_same_types>>(); assert_same_types>&>(); assert_same_types>&>(); assert_same_types>>(); assert_same_types>>&>(); std::vector field1{"Foo", "Bar", "???"}; ASSERT_TRUE(field1 == testStruct.getField1().get()); std::vector field2{1, 2, 3}; ASSERT_TRUE(field2 == testStruct.getField2().get()); std::vector field3{1.2, 3.4, 5.6}; ASSERT_TRUE(field3 == testStruct.getField3().get()); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; Optional_array_fields array_fields; array_fields.setField1(field1); array_fields.setField2(field2); array_fields.setField3(field3); array_fields.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Negative: Test mixed type arrays TEST(IDLArrayTests, TestBadArrays) { IDLParserErrorContext ctxt("root"); // Negative: Test not an array { auto testDoc = BSON("field1" << 123); ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), AssertionException); } // Negative: Test array with mixed types { auto testDoc = BSON("field1" << BSON_ARRAY(1.2 << 3.4 << 5.6)); ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), AssertionException); } } // Positive: Test arrays with good field names but made with BSONObjBuilder TEST(IDLArrayTests, TestGoodArrays) { IDLParserErrorContext ctxt("root"); { BSONObjBuilder builder; { BSONObjBuilder subBuilder(builder.subarrayStart("field1")); subBuilder.append("0", 1); subBuilder.append("1", 2); } auto testDoc = builder.obj(); Simple_int_array::parse(ctxt, testDoc); } } // Negative: Test arrays with bad field names TEST(IDLArrayTests, TestBadArrayFieldNames) { IDLParserErrorContext ctxt("root"); // Negative: string fields { BSONObjBuilder builder; { BSONObjBuilder subBuilder(builder.subarrayStart("field1")); subBuilder.append("0", 1); subBuilder.append("foo", 2); } auto testDoc = builder.obj(); ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), AssertionException); } // Negative: bad start { BSONObjBuilder builder; { BSONObjBuilder subBuilder(builder.subarrayStart("field1")); subBuilder.append("1", 1); subBuilder.append("2", 2); } auto testDoc = builder.obj(); ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), AssertionException); } // Negative: non-sequentially increasing { BSONObjBuilder builder; { BSONObjBuilder subBuilder(builder.subarrayStart("field1")); subBuilder.append("0", 1); subBuilder.append("2", 2); } auto testDoc = builder.obj(); ASSERT_THROWS(Simple_int_array::parse(ctxt, testDoc), AssertionException); } } // Postitive: Test arrays with complex types TEST(IDLArrayTests, TestArraysOfComplexTypes) { IDLParserErrorContext ctxt("root"); // Positive: Test document auto testDoc = BSON("field1" << BSON_ARRAY(1 << 2 << 3) << "field2" << BSON_ARRAY("a.b" << "c.d") << "field3" << BSON_ARRAY(1 << "2") << "field4" << BSON_ARRAY(BSONObj() << BSONObj()) << "field5" << BSON_ARRAY(BSONObj() << BSONObj() << BSONObj()) << "field6" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "field1o" << BSON_ARRAY(1 << 2 << 3) << "field2o" << BSON_ARRAY("a.b" << "c.d") << "field3o" << BSON_ARRAY(1 << "2") << "field4o" << BSON_ARRAY(BSONObj() << BSONObj()) << "field6o" << BSON_ARRAY(BSON("value" << "goodbye") << BSON("value" << "world")) ); auto testStruct = Complex_array_fields::parse(ctxt, testDoc); assert_same_types&>(); assert_same_types&>(); assert_same_types&>(); assert_same_types&>(); assert_same_types&>(); assert_same_types&>(); assert_same_types>&>(); assert_same_types>&>(); assert_same_types>&>(); assert_same_types>&>(); assert_same_types>&>(); assert_same_types>&>(); std::vector field1{1, 2, 3}; ASSERT_TRUE(field1 == testStruct.getField1()); std::vector field2{{"a", "b"}, {"c", "d"}}; ASSERT_TRUE(field2 == testStruct.getField2()); ASSERT_EQUALS(testStruct.getField6().size(), 2u); ASSERT_EQUALS(testStruct.getField6()[0].getValue(), "hello"); ASSERT_EQUALS(testStruct.getField6()[1].getValue(), "world"); ASSERT_EQUALS(testStruct.getField6o().get().size(), 2u); ASSERT_EQUALS(testStruct.getField6o().get()[0].getValue(), "goodbye"); ASSERT_EQUALS(testStruct.getField6o().get()[1].getValue(), "world"); } template void TestBinDataVector() { IDLParserErrorContext ctxt("root"); // Positive: Test document with only a generic bindata field uint8_t testData[] = {1, 2, 3}; auto testDoc = BSON("value" << BSONBinData(testData, 3, bindata_type)); auto testStruct = ParserT::parse(ctxt, testDoc); assert_same_types(); std::vector expected{1, 2, 3}; ASSERT_TRUE(isEquals(testStruct.getValue(), expected)); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; ParserT one_new; one_new.setValue(makeCDR(expected)); testStruct.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); // Validate the operator == works // Use ASSERT instead of ASSERT_EQ to avoid operator<< ASSERT(one_new == testStruct); } } TEST(IDLBinData, TestGeneric) { TestBinDataVector(); } TEST(IDLBinData, TestFunction) { TestBinDataVector(); } template void TestBinDataArray() { IDLParserErrorContext ctxt("root"); // Positive: Test document with only a generic bindata field uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; auto testDoc = BSON("value" << BSONBinData(testData, 16, bindata_type)); auto testStruct = ParserT::parse(ctxt, testDoc); assert_same_types>(); std::array expected{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; ASSERT_TRUE(isEquals(testStruct.getValue(), expected)); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; ParserT one_new; one_new.setValue(expected); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } TEST(IDLBinData, TestUUID) { TestBinDataArray(); } TEST(IDLBinData, TestMD5) { TestBinDataArray(); // Negative: Test document with a incorrectly size md5 field { IDLParserErrorContext ctxt("root"); uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; auto testDoc = BSON("value" << BSONBinData(testData, 15, MD5Type)); ASSERT_THROWS(One_md5::parse(ctxt, testDoc), AssertionException); } } // Test if a given value for a given bson document parses successfully or fails if the bson types // mismatch. template void TestBinDataParse() { IDLParserErrorContext ctxt("root"); uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; auto testDoc = BSON("value" << BSONBinData(testData, 16, Test_bindata_type)); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), BinData); ASSERT_EQUALS(element.binDataType(), Test_bindata_type); if (Parser_bindata_type != Test_bindata_type) { ASSERT_THROWS(ParserT::parse(ctxt, testDoc), AssertionException); } else { (void)ParserT::parse(ctxt, testDoc); } } template void TestBinDataParser() { TestBinDataParse(); TestBinDataParse(); TestBinDataParse(); TestBinDataParse(); } TEST(IDLBinData, TestParse) { TestBinDataParser(); TestBinDataParser(); TestBinDataParser(); TestBinDataParser(); TestBinDataParser(); } // Mixed: test a type that accepts a custom bindata type TEST(IDLBinData, TestCustomType) { IDLParserErrorContext ctxt("root"); uint8_t testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; auto testDoc = BSON("value" << BSONBinData(testData, 14, BinDataGeneral)); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), BinData); ASSERT_EQUALS(element.binDataType(), BinDataGeneral); auto testStruct = One_bindata_custom::parse(ctxt, testDoc); std::vector testVector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; ASSERT_TRUE(testStruct.getValue().getVector() == testVector); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; One_bindata_custom one_new; one_new.setValue(testVector); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Positive: test a type that accepts a custom UUID type TEST(IDLBinData, TestUUIDclass) { IDLParserErrorContext ctxt("root"); auto uuid = UUID::gen(); auto testDoc = BSON("value" << uuid); auto element = testDoc.firstElement(); ASSERT_EQUALS(element.type(), BinData); ASSERT_EQUALS(element.binDataType(), newUUID); auto testStruct = One_UUID::parse(ctxt, testDoc); ASSERT_TRUE(testStruct.getValue() == uuid); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; One_UUID one_new; one_new.setValue(uuid); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } /** * A simple class that derives from an IDL generated class */ class ClassDerivedFromStruct : public DerivedBaseStruct { public: static ClassDerivedFromStruct parseFromBSON(const IDLParserErrorContext& ctxt, const BSONObj& bsonObject) { ClassDerivedFromStruct o; o.parseProtected(ctxt, bsonObject); o._done = true; return o; } bool aRandomAdditionalMethod() { return true; } bool getDone() const { return _done; } private: bool _done = false; }; // Positive: demonstrate a class derived from an IDL parser. TEST(IDLCustomType, TestDerivedParser) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("field1" << 3 << "field2" << 5); auto testStruct = ClassDerivedFromStruct::parseFromBSON(ctxt, testDoc); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), 5); ASSERT_EQUALS(testStruct.getDone(), true); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; ClassDerivedFromStruct one_new; one_new.setField1(3); one_new.setField2(5); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Chained type testing // Check each of types // Check for round-tripping of fields and documents // Positive: demonstrate a class struct chained types TEST(IDLChainedType, TestChainedType) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("field1" << "abc" << "field2" << 5); auto testStruct = Chained_struct_only::parse(ctxt, testDoc); assert_same_types(); assert_same_types(); ASSERT_EQUALS(testStruct.getChainedType().getField1(), "abc"); ASSERT_EQUALS(testStruct.getAnotherChainedType().getField2(), 5); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; Chained_struct_only one_new; ChainedType ct; ct.setField1("abc"); one_new.setChainedType(ct); AnotherChainedType act; act.setField2(5); one_new.setAnotherChainedType(act); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Positive: demonstrate a struct with chained types ignoring extra fields TEST(IDLChainedType, TestExtraFields) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("field1" << "abc" << "field2" << 5 << "field3" << 123456); auto testStruct = Chained_struct_only::parse(ctxt, testDoc); ASSERT_EQUALS(testStruct.getChainedType().getField1(), "abc"); ASSERT_EQUALS(testStruct.getAnotherChainedType().getField2(), 5); } // Negative: demonstrate a struct with chained types with duplicate fields TEST(IDLChainedType, TestDuplicateFields) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("field1" << "abc" << "field2" << 5 << "field2" << 123456); ASSERT_THROWS(Chained_struct_only::parse(ctxt, testDoc), AssertionException); } // Positive: demonstrate a struct with chained structs TEST(IDLChainedType, TestChainedStruct) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("anyField" << 123.456 << "objectField" << BSON("random" << "pair") << "field3" << "abc"); auto testStruct = Chained_struct_mixed::parse(ctxt, testDoc); assert_same_types(); assert_same_types(); ASSERT_EQUALS(testStruct.getField3(), "abc"); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } } // Negative: demonstrate a struct with chained structs and extra fields TEST(IDLChainedType, TestChainedStructWithExtraFields) { IDLParserErrorContext ctxt("root"); // Extra field { auto testDoc = BSON("field3" << "abc" << "anyField" << 123.456 << "objectField" << BSON("random" << "pair") << "extraField" << 787); ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), AssertionException); } // Duplicate any { auto testDoc = BSON("field3" << "abc" << "anyField" << 123.456 << "objectField" << BSON("random" << "pair") << "anyField" << 787); ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), AssertionException); } // Duplicate object { auto testDoc = BSON("objectField" << BSON("fake" << "thing") << "field3" << "abc" << "anyField" << 123.456 << "objectField" << BSON("random" << "pair")); ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), AssertionException); } // Duplicate field3 { auto testDoc = BSON("field3" << "abc" << "anyField" << 123.456 << "objectField" << BSON("random" << "pair") << "field3" << "def"); ASSERT_THROWS(Chained_struct_mixed::parse(ctxt, testDoc), AssertionException); } } // Positive: demonstrate a struct with chained structs and types TEST(IDLChainedType, TestChainedMixedStruct) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("field1" << "abc" << "field2" << 5 << "stringField" << "def" << "field3" << 456); auto testStruct = Chained_struct_type_mixed::parse(ctxt, testDoc); assert_same_types(); assert_same_types(); ASSERT_EQUALS(testStruct.getChained_type().getField1(), "abc"); ASSERT_EQUALS(testStruct.getAnotherChainedType().getField2(), 5); ASSERT_EQUALS(testStruct.getChainedStringBasicType().getStringField(), "def"); ASSERT_EQUALS(testStruct.getField3(), 456); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; Chained_struct_type_mixed one_new; ChainedType ct; ct.setField1("abc"); one_new.setChained_type(ct); AnotherChainedType act; act.setField2(5); one_new.setAnotherChainedType(act); one_new.setField3(456); Chained_string_basic_type csbt; csbt.setStringField("def"); one_new.setChainedStringBasicType(csbt); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Positive: demonstrate a class derived from an IDL parser. TEST(IDLEnum, TestEnum) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("field1" << 2 << "field2" << "zero"); auto testStruct = StructWithEnum::parse(ctxt, testDoc); ASSERT_TRUE(testStruct.getField1() == IntEnum::c2); ASSERT_TRUE(testStruct.getField2() == StringEnumEnum::s0); assert_same_types(); assert_same_types>(); assert_same_types(); assert_same_types>(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(&builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; StructWithEnum one_new; one_new.setField1(IntEnum::c2); one_new.setField2(StringEnumEnum::s0); one_new.serialize(&builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); } } // Negative: test bad values TEST(IDLEnum, TestIntEnumNegative) { IDLParserErrorContext ctxt("root"); // Test string { auto testDoc = BSON("value" << "2"); ASSERT_THROWS(One_int_enum::parse(ctxt, testDoc), AssertionException); } // Test a value out of range { auto testDoc = BSON("value" << 4); ASSERT_THROWS(One_int_enum::parse(ctxt, testDoc), AssertionException); } // Test a negative number { auto testDoc = BSON("value" << -1); ASSERT_THROWS(One_int_enum::parse(ctxt, testDoc), AssertionException); } } TEST(IDLEnum, TestStringEnumNegative) { IDLParserErrorContext ctxt("root"); // Test int { auto testDoc = BSON("value" << 2); ASSERT_THROWS(One_string_enum::parse(ctxt, testDoc), AssertionException); } // Test a value out of range { auto testDoc = BSON("value" << "foo"); ASSERT_THROWS(One_string_enum::parse(ctxt, testDoc), AssertionException); } } OpMsgRequest makeOMR(BSONObj obj) { OpMsgRequest request; request.body = obj; return request; } // Positive: demonstrate a command wit concatenate with db TEST(IDLCommand, TestConcatentateWithDb) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(BasicConcatenateWithDbCommand::kCommandName << "coll1" << "field1" << 3 << "field2" << "five" << "$db" << "db"); auto testStruct = BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), "five"); ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); assert_same_types(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; OpMsgRequest reply = testStruct.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } // Positive: Test we can serialize from nothing the same document except for $db { auto testDocWithoutDb = BSON(BasicConcatenateWithDbCommand::kCommandName << "coll1" << "field1" << 3 << "field2" << "five"); BSONObjBuilder builder; BasicConcatenateWithDbCommand one_new(NamespaceString("db.coll1")); one_new.setField1(3); one_new.setField2("five"); one_new.serialize(BSONObj(), &builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDocWithoutDb, serializedDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; BasicConcatenateWithDbCommand one_new(NamespaceString("db.coll1")); one_new.setField1(3); one_new.setField2("five"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } TEST(IDLCommand, TestConcatentateWithDbSymbol) { IDLParserErrorContext ctxt("root"); // Postive - symbol??? { auto testDoc = BSON("BasicConcatenateWithDbCommand" << BSONSymbol("coll1") << "field1" << 3 << "field2" << "five" << "$db" << "db"); auto testStruct = BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); } } TEST(IDLCommand, TestConcatentateWithDbNegative) { IDLParserErrorContext ctxt("root"); // Negative - duplicate namespace field { auto testDoc = BSON("BasicConcatenateWithDbCommand" << 1 << "field1" << 3 << "BasicConcatenateWithDbCommand" << 1 << "field2" << "five"); ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } // Negative - namespace field wrong order { auto testDoc = BSON("field1" << 3 << "BasicConcatenateWithDbCommand" << 1 << "field2" << "five"); ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } // Negative - namespace missing { auto testDoc = BSON("field1" << 3 << "field2" << "five"); ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } // Negative - wrong type { auto testDoc = BSON("BasicConcatenateWithDbCommand" << 1 << "field1" << 3 << "field2" << "five"); ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } // Negative - bad ns with embedded null { StringData sd1("db\0foo", 6); auto testDoc = BSON("BasicConcatenateWithDbCommand" << sd1 << "field1" << 3 << "field2" << "five"); ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } } // Positive: demonstrate a command with concatenate with db TEST(IDLCommand, TestIgnore) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("BasicIgnoredCommand" << 1 << "field1" << 3 << "field2" << "five"); auto testDocWithDB = appendDB(testDoc, "admin"); auto testStruct = BasicIgnoredCommand::parse(ctxt, makeOMR(testDocWithDB)); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), "five"); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; testStruct.serialize(BSONObj(), &builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; BasicIgnoredCommand one_new; one_new.setField1(3); one_new.setField2("five"); one_new.setDbName("admin"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDocWithDB, reply.body); } } TEST(IDLCommand, TestIgnoredNegative) { IDLParserErrorContext ctxt("root"); // Negative - duplicate namespace field { auto testDoc = BSON( "BasicIgnoredCommand" << 1 << "field1" << 3 << "BasicIgnoredCommand" << 1 << "field2" << "five"); ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } // Negative - namespace field wrong order { auto testDoc = BSON("field1" << 3 << "BasicIgnoredCommand" << 1 << "field2" << "five"); ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } // Negative - namespace missing { auto testDoc = BSON("field1" << 3 << "field2" << "five"); ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)), AssertionException); } } // Positive: Test a command read and written to OpMsgRequest works TEST(IDLDocSequence, TestBasic) { IDLParserErrorContext ctxt("root"); auto testTempDoc = BSON("DocSequenceCommand" << "coll1" << "field1" << 3 << "field2" << "five" << "$db" << "db" << "structs" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request; request.body = testTempDoc; auto testStruct = DocSequenceCommand::parse(ctxt, request); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), "five"); ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); ASSERT_EQUALS("hello", testStruct.getStructs()[0].getValue()); ASSERT_EQUALS("world", testStruct.getStructs()[1].getValue()); assert_same_types(); // Positive: Test we can round trip to a document sequence from the just parsed document { OpMsgRequest loopbackRequest = testStruct.serialize(BSONObj()); assertOpMsgEquals(request, loopbackRequest); ASSERT_EQUALS(loopbackRequest.sequences.size(), 2UL); } // Positive: Test we can roundtrip just the body from the just parsed document { BSONObjBuilder builder; testStruct.serialize(BSONObj(), &builder); auto testTempDocWithoutDB = testTempDoc.removeField("$db"); ASSERT_BSONOBJ_EQ(testTempDocWithoutDB, builder.obj()); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; DocSequenceCommand one_new(NamespaceString("db.coll1")); one_new.setField1(3); one_new.setField2("five"); std::vector strings; One_string one_string; one_string.setValue("hello"); strings.push_back(one_string); One_string one_string2; one_string2.setValue("world"); strings.push_back(one_string2); one_new.setStructs(strings); std::vector objects; objects.push_back(BSON("foo" << 1)); one_new.setObjects(objects); OpMsgRequest serializeRequest = one_new.serialize(BSONObj()); assertOpMsgEquals(request, serializeRequest); } } // Negative: Test a OpMsgRequest read without $db TEST(IDLDocSequence, TestMissingDB) { IDLParserErrorContext ctxt("root"); auto testTempDoc = BSON("DocSequenceCommand" << "coll1" << "field1" << 3 << "field2" << "five" << "structs" << BSON_ARRAY(BSON("value" << "hello")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request; request.body = testTempDoc; ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), AssertionException); } // Positive: Test a command read and written to OpMsgRequest with content in DocumentSequence works template void TestDocSequence(StringData name) { IDLParserErrorContext ctxt("root"); auto testTempDoc = BSON(name << "coll1" << "field1" << 3 << "field2" << "five"); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); request.sequences.push_back({"objects", {BSON("foo" << 1)}}); auto testStruct = TestT::parse(ctxt, request); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), "five"); ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); ASSERT_EQUALS("hello", testStruct.getStructs()[0].getValue()); ASSERT_EQUALS("world", testStruct.getStructs()[1].getValue()); auto opmsg = testStruct.serialize(BSONObj()); ASSERT_EQUALS(2UL, opmsg.sequences.size()); assertOpMsgEquals(opmsg, request); assertOpMsgEqualsExact(opmsg, request); } // Positive: Test a command read and written to OpMsgRequest with content in DocumentSequence works TEST(IDLDocSequence, TestDocSequence) { TestDocSequence("DocSequenceCommand"); TestDocSequence("DocSequenceCommandNonStrict"); } // Negative: Bad Doc Sequences template void TestBadDocSequences(StringData name, bool extraFieldAllowed) { IDLParserErrorContext ctxt("root"); auto testTempDoc = BSON(name << "coll1" << "field1" << 3 << "field2" << "five"); // Negative: Duplicate fields in doc sequence { OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); request.sequences.push_back({"structs", {BSON("foo" << 1)}}); ASSERT_THROWS(TestT::parse(ctxt, request), AssertionException); } // Negative: Extra field in document sequence { OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); request.sequences.push_back({"objects", {BSON("foo" << 1)}}); request.sequences.push_back({"extra", {BSON("foo" << 1)}}); if (!extraFieldAllowed) { ASSERT_THROWS(TestT::parse(ctxt, request), AssertionException); } else { /*void*/ TestT::parse(ctxt, request); } } // Negative: Missing field in both document sequence and body { OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"objects", {BSON("foo" << 1)}}); ASSERT_THROWS(TestT::parse(ctxt, request), AssertionException); } // Negative: Missing field in both document sequence and body { OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); ASSERT_THROWS(TestT::parse(ctxt, request), AssertionException); } } // Negative: Bad Doc Sequences TEST(IDLDocSequence, TestBadDocSequences) { TestBadDocSequences("DocSequenceCommand", false); TestBadDocSequences("DocSequenceCommandNonStrict", true); } // Negative: Duplicate field across body and document sequence template void TestDuplicateDocSequences(StringData name) { IDLParserErrorContext ctxt("root"); // Negative: Duplicate fields in doc sequence and body { auto testTempDoc = BSON(name << "coll1" << "field1" << 3 << "field2" << "five" << "structs" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), AssertionException); } // Negative: Duplicate fields in doc sequence and body { auto testTempDoc = BSON(name << "coll1" << "field1" << 3 << "field2" << "five" << "structs" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"objects", {BSON("foo" << 1)}}); ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), AssertionException); } } // Negative: Duplicate field across body and document sequence TEST(IDLDocSequence, TestDuplicateDocSequences) { TestDuplicateDocSequences("DocSequenceCommand"); TestDuplicateDocSequences("DocSequenceCommandNonStrict"); } // Positive: Test empty document sequence TEST(IDLDocSequence, TestEmptySequence) { IDLParserErrorContext ctxt("root"); // Negative: Duplicate fields in doc sequence and body { auto testTempDoc = BSON("DocSequenceCommand" << "coll1" << "field1" << 3 << "field2" << "five" << "structs" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {}}); ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), AssertionException); } // Positive: Empty document sequence { auto testTempDoc = BSON("DocSequenceCommand" << "coll1" << "field1" << 3 << "field2" << "five" << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {}}); auto testStruct = DocSequenceCommand::parse(ctxt, request); ASSERT_EQUALS(0UL, testStruct.getStructs().size()); } } // Positive: Test all the OpMsg well known fields are ignored TEST(IDLDocSequence, TestWellKnownFieldsAreIgnored) { IDLParserErrorContext ctxt("root"); auto knownFields = {"$audit", "$client", "$configServerState", "$oplogQueryData", "$queryOptions", "$readPreference", "$replData", "$clusterTime", "maxTimeMS", "readConcern", "shardVersion", "tracking_info", "writeConcern"}; for (auto knownField : knownFields) { auto testTempDoc = BSON("DocSequenceCommand" << "coll1" << "field1" << 3 << "field2" << "five" << knownField << "extra" << "structs" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); // Validate it can be parsed as a OpMsgRequest. { auto testStruct = DocSequenceCommand::parse(ctxt, request); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); } // Validate it can be parsed as just a BSON document. { auto testStruct = DocSequenceCommand::parse(ctxt, request.body); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); } } } // Positive: Test all the OpMsg well known fields are passed through except $db. TEST(IDLDocSequence, TestWellKnownFieldsPassthrough) { IDLParserErrorContext ctxt("root"); auto knownFields = {"$audit", "$client", "$configServerState", "$oplogQueryData", "$queryOptions", "$readPreference", "$replData", "$clusterTime", "maxTimeMS", "readConcern", "shardVersion", "tracking_info", "writeConcern"}; for (auto knownField : knownFields) { auto testTempDoc = BSON("DocSequenceCommand" << "coll1" << "field1" << 3 << "field2" << "five" << "$db" << "db" << knownField << "extra" << "structs" << BSON_ARRAY(BSON("value" << "hello") << BSON("value" << "world")) << "objects" << BSON_ARRAY(BSON("foo" << 1))); OpMsgRequest request; request.body = testTempDoc; auto testStruct = DocSequenceCommand::parse(ctxt, request); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); auto reply = testStruct.serialize(testTempDoc); assertOpMsgEquals(request, reply); } } // Postive: Extra Fields in non-strict parser TEST(IDLDocSequence, TestNonStrict) { IDLParserErrorContext ctxt("root"); // Positive: Extra field in document sequence { auto testTempDoc = BSON("DocSequenceCommandNonStrict" << "coll1" << "field1" << 3 << "field2" << "five"); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); request.sequences.push_back({"objects", {BSON("foo" << 1)}}); request.sequences.push_back({"extra", {BSON("foo" << 1)}}); auto testStruct = DocSequenceCommandNonStrict::parse(ctxt, request); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); } // Positive: Extra field in body { auto testTempDoc = BSON("DocSequenceCommandNonStrict" << "coll1" << "field1" << 3 << "field2" << "five" << "extra" << 1); OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); request.sequences.push_back({"structs", {BSON("value" << "hello"), BSON("value" << "world")}}); request.sequences.push_back({"objects", {BSON("foo" << 1)}}); auto testStruct = DocSequenceCommandNonStrict::parse(ctxt, request); ASSERT_EQUALS(2UL, testStruct.getStructs().size()); } } // Postive: Test a Command known field does not propagate from passthrough to the final BSON if it // is included as a field in the command. TEST(IDLCommand, TestKnownFieldDuplicate) { IDLParserErrorContext ctxt("root"); auto testPassthrough = BSON("$db" << "foo" << "maxTimeMS" << 6 << "$client" << "foo"); auto testDoc = BSON("KnownFieldCommand" << "coll1" << "$db" << "db" << "field1" << 28 << "maxTimeMS" << 42); auto testStruct = KnownFieldCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(28, testStruct.getField1()); ASSERT_EQUALS(42, testStruct.getMaxTimeMS()); auto expectedDoc = BSON("KnownFieldCommand" << "coll1" << "field1" << 28 << "maxTimeMS" << 42 << "$db" << "db" << "$client" << "foo"); ASSERT_BSONOBJ_EQ(expectedDoc, testStruct.serialize(testPassthrough).body); } // Positive: Test an inline nested chain struct works TEST(IDLChainedStruct, TestInline) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON("stringField" << "bar" << "field3" << "foo"); auto testStruct = Chained_struct_inline::parse(ctxt, testDoc); ASSERT_EQUALS(testStruct.getChained_string_inline_basic_type().getStringField(), "bar"); ASSERT_EQUALS(testStruct.getField3(), "foo"); assert_same_types(); assert_same_types(); // Positive: Test we can round trip to a document from the just parsed document { BSONObj loopbackDoc = testStruct.toBSON(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; Chained_struct_inline one_new; one_new.setField3("foo"); Chained_string_inline_basic_type f1; f1.setStringField("bar"); one_new.setChained_string_inline_basic_type(f1); BSONObj loopbackDoc = one_new.toBSON(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); } } // Positive: verify a command a string arg TEST(IDLTypeCommand, TestString) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(CommandTypeStringCommand::kCommandName << "foo" << "field1" << 3 << "$db" << "db"); auto testStruct = CommandTypeStringCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getCommandParameter(), "foo"); assert_same_types(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; OpMsgRequest reply = testStruct.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } // Positive: Test we can serialize from nothing the same document except for $db { auto testDocWithoutDb = BSON(CommandTypeStringCommand::kCommandName << "foo" << "field1" << 3); BSONObjBuilder builder; CommandTypeStringCommand one_new("foo"); one_new.setField1(3); one_new.setDbName("db"); one_new.serialize(BSONObj(), &builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDocWithoutDb, serializedDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; CommandTypeStringCommand one_new("foo"); one_new.setField1(3); one_new.setDbName("db"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } // Positive: verify a command can take an array of object TEST(IDLTypeCommand, TestArrayObject) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(CommandTypeArrayObjectCommand::kCommandName << BSON_ARRAY(BSON("sample" << "doc")) << "$db" << "db"); auto testStruct = CommandTypeArrayObjectCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getCommandParameter().size(), 1UL); assert_same_types&>(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; OpMsgRequest reply = testStruct.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; std::vector vec; vec.emplace_back(BSON("sample" << "doc")); CommandTypeArrayObjectCommand one_new(vec); one_new.setDbName("db"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } // Positive: verify a command can take a struct TEST(IDLTypeCommand, TestStruct) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(CommandTypeStructCommand::kCommandName << BSON("value" << "sample") << "$db" << "db"); auto testStruct = CommandTypeStructCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getCommandParameter().getValue(), "sample"); assert_same_types(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; OpMsgRequest reply = testStruct.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; One_string os; os.setValue("sample"); CommandTypeStructCommand one_new(os); one_new.setDbName("db"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } // Positive: verify a command can take an array of structs TEST(IDLTypeCommand, TestStructArray) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(CommandTypeArrayStructCommand::kCommandName << BSON_ARRAY(BSON("value" << "sample")) << "$db" << "db"); auto testStruct = CommandTypeArrayStructCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getCommandParameter().size(), 1UL); assert_same_types&>(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; OpMsgRequest reply = testStruct.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; std::vector vec; One_string os; os.setValue("sample"); vec.push_back(os); CommandTypeArrayStructCommand one_new(vec); one_new.setDbName("db"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } // Positive: verify a command a string arg and alternate C++ name TEST(IDLTypeCommand, TestUnderscoreCommand) { IDLParserErrorContext ctxt("root"); auto testDoc = BSON(WellNamedCommand::kCommandName << "foo" << "field1" << 3 << "$db" << "db"); auto testStruct = WellNamedCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getCommandParameter(), "foo"); assert_same_types(); // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; OpMsgRequest reply = testStruct.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } // Positive: Test we can serialize from nothing the same document except for $db { auto testDocWithoutDb = BSON(WellNamedCommand::kCommandName << "foo" << "field1" << 3); BSONObjBuilder builder; WellNamedCommand one_new("foo"); one_new.setField1(3); one_new.setDbName("db"); one_new.serialize(BSONObj(), &builder); auto serializedDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDocWithoutDb, serializedDoc); } // Positive: Test we can serialize from nothing the same document { BSONObjBuilder builder; WellNamedCommand one_new("foo"); one_new.setField1(3); one_new.setDbName("db"); OpMsgRequest reply = one_new.serialize(BSONObj()); ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } } // namespace } // namespace mongo