/** * 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/db/pipeline/aggregation_request.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/db/catalog/document_validation.h" #include "mongo/db/namespace_string.h" #include "mongo/db/pipeline/document.h" #include "mongo/db/pipeline/document_value_test_util.h" #include "mongo/db/pipeline/value.h" #include "mongo/unittest/unittest.h" #include "mongo/util/assert_util.h" namespace mongo { namespace { // // Parsing // TEST(AggregationRequestTest, ShouldParseAllKnownOptions) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson( "{pipeline: [{$match: {a: 'abc'}}], explain: true, allowDiskUse: true, fromRouter: true, " "bypassDocumentValidation: true, collation: {locale: 'en_US'}, cursor: {batchSize: 10}}"); auto request = unittest::assertGet(AggregationRequest::parseFromBSON(nss, inputBson)); ASSERT_TRUE(request.isExplain()); ASSERT_TRUE(request.shouldAllowDiskUse()); ASSERT_TRUE(request.isFromRouter()); ASSERT_TRUE(request.shouldBypassDocumentValidation()); ASSERT_TRUE(request.isCursorCommand()); ASSERT_EQ(request.getBatchSize().get(), 10); ASSERT_BSONOBJ_EQ(request.getCollation(), BSON("locale" << "en_US")); } // // Serialization // TEST(AggregationRequestTest, ShouldOnlySerializeRequiredFieldsIfNoOptionalFieldsAreSpecified) { NamespaceString nss("a.collection"); AggregationRequest request(nss, {}); auto expectedSerialization = Document{{AggregationRequest::kCommandName, nss.coll()}, {AggregationRequest::kPipelineName, Value(std::vector{})}}; ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization); } TEST(AggregationRequestTest, ShouldNotSerializeOptionalValuesIfEquivalentToDefault) { NamespaceString nss("a.collection"); AggregationRequest request(nss, {}); request.setExplain(false); request.setAllowDiskUse(false); request.setFromRouter(false); request.setBypassDocumentValidation(false); request.setCollation(BSONObj()); auto expectedSerialization = Document{{AggregationRequest::kCommandName, nss.coll()}, {AggregationRequest::kPipelineName, Value(std::vector{})}}; ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization); } TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) { NamespaceString nss("a.collection"); AggregationRequest request(nss, {}); request.setExplain(true); request.setAllowDiskUse(true); request.setFromRouter(true); request.setBypassDocumentValidation(true); const auto collationObj = BSON("locale" << "en_US"); request.setCollation(collationObj); auto expectedSerialization = Document{{AggregationRequest::kCommandName, nss.coll()}, {AggregationRequest::kPipelineName, Value(std::vector{})}, {AggregationRequest::kExplainName, true}, {AggregationRequest::kAllowDiskUseName, true}, {AggregationRequest::kFromRouterName, true}, {bypassDocumentValidationCommandOption(), true}, {AggregationRequest::kCollationName, collationObj}}; ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization); } TEST(AggregationRequestTest, ShouldSetBatchSizeToDefaultOnEmptyCursorObject) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], cursor: {}}"); auto request = AggregationRequest::parseFromBSON(nss, inputBson); ASSERT_OK(request.getStatus()); ASSERT_TRUE(request.getValue().isCursorCommand()); ASSERT_TRUE(request.getValue().getBatchSize()); ASSERT_EQ(request.getValue().getBatchSize().get(), AggregationRequest::kDefaultBatchSize); } TEST(AggregationRequestTest, NoBatchSizeWhenCursorObjectNotSet) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}]}"); auto request = AggregationRequest::parseFromBSON(nss, inputBson); ASSERT_OK(request.getStatus()); ASSERT_FALSE(request.getValue().isCursorCommand()); ASSERT_FALSE(request.getValue().getBatchSize()); } // // Error cases. // TEST(AggregationRequestTest, ShouldRejectNonArrayPipeline) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: {}}"); ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldRejectPipelineArrayIfAnElementIsNotAnObject) { NamespaceString nss("a.collection"); BSONObj inputBson = fromjson("{pipeline: [4]}"); ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}, 4]}"); ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldRejectNonObjectCollation) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], collation: 1}"); ASSERT_NOT_OK( AggregationRequest::parseFromBSON(NamespaceString("a.collection"), inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldRejectNonBoolExplain) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], explain: 1}"); ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldRejectNonBoolFromRouter) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], fromRouter: 1}"); ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldRejectNonBoolAllowDiskUse) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], allowDiskUse: 1}"); ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } // // Ignore fields parsed elsewhere. // TEST(AggregationRequestTest, ShouldIgnoreFieldsPrefixedWithDollar) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], $unknown: 1}"); ASSERT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldIgnoreWriteConcernOption) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], writeConcern: 'invalid'}"); ASSERT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldIgnoreMaxTimeMsOption) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], maxTimeMS: 'invalid'}"); ASSERT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } TEST(AggregationRequestTest, ShouldIgnoreReadConcernOption) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}], readConcern: 'invalid'}"); ASSERT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } } // namespace } // namespace mongo