/** * Copyright (C) 2020-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * 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 * Server Side Public License for more details. * * You should have received a copy of the Server Side 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 Server Side 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/matcher/doc_validation_error_test.h" #include "mongo/db/matcher/doc_validation_util.h" namespace mongo::doc_validation_error { namespace { /** * Parses a MatchExpression from 'query' and returns a validation error generated by the parsed * MatchExpression against document 'document'. */ BSONObj generateValidationError( const BSONObj& query, const BSONObj& document, const bool shouldThrow, const int maxDocValidationErrorSize = kDefaultMaxDocValidationErrorSize, const int maxConsideredValues = internalQueryMaxDocValidationErrorConsideredValues.load()) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); expCtx->isParsingCollectionValidator = true; StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(result.getStatus()); MatchExpression* expr = result.getValue().get(); // Verify that the document fails to match against the query or whether it should throw based // on the value of 'shouldThrow'. if (shouldThrow) { ASSERT_THROWS(expr->matchesBSON(document), DBException); } else { ASSERT_FALSE(expr->matchesBSON(document)); } return doc_validation_error::generateError( *expr, document.hasField("_id") ? document : document.addField(BSON("_id" << 1).firstElement()), maxDocValidationErrorSize, maxConsideredValues); } } // namespace void verifyGeneratedError(const BSONObj& query, const BSONObj& document, const BSONObj& expectedError, bool shouldThrow) { auto generatedError = generateValidationError(query, document, shouldThrow); // Verify that the generated error details match the expected error. ASSERT_TRUE(generatedError.hasField("details")); ASSERT_TRUE(generatedError["details"].isABSONObj()); ASSERT_BSONOBJ_EQ(generatedError["details"].Obj(), expectedError); } namespace { // Verifies that 'failingDocumentId' attribute is correctly set in a document validation error. TEST(GenerateValidationError, FailingDocumentId) { BSONObj query = BSON("a" << BSON("$eq" << 2)); BSONObj document = BSON("a" << 1 << "_id" << 10); BSONObj expectedError = BSON("failingDocumentId" << 10 << "details" << BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 1)); ASSERT_BSONOBJ_EQ( doc_validation_error::generateValidationError(query, document, false /* shouldThrow */), expectedError); } /** * Utility which verifies that 'obj' has no fields from 'missingFields'. */ void verifyExpectedFieldsAreMissing(const BSONObj& obj, const std::set& missingFields) { for (auto&& field : missingFields) { ASSERT_FALSE(obj.hasField(field)); } for (auto&& elem : obj) { if (elem.type() == BSONType::Object) { verifyExpectedFieldsAreMissing(elem.Obj(), missingFields); } else if (elem.type() == BSONType::Array) { for (auto&& arrayElem : elem.embeddedObject()) { if (arrayElem.type() == BSONType::Object) { verifyExpectedFieldsAreMissing(arrayElem.embeddedObject(), missingFields); } } } } } /** * Generates a document validation error for 'query' and 'document' and verifies that it is a * truncated error. Most importantly, the generated error should be a valid BSONObj (i.e. * does not exceed 16MB), but should have a 'truncated' field at the top level with a value * of 'true', and there should be no 'consideredValues' or 'specifiedAs' fields within the * error. */ void verifyTruncatedError(const BSONObj& query, const BSONObj& document, const int maxDocValidationErrorSize, const int maxConsideredValuesSize) { auto generatedError = generateValidationError(query, document, false /* shouldThrow */, maxDocValidationErrorSize, maxConsideredValuesSize); ASSERT_TRUE(generatedError.hasField("truncated")); auto elem = generatedError["truncated"]; ASSERT_TRUE(elem.isBoolean()); ASSERT_TRUE(elem.boolean()); const std::set missingFields{"specifiedAs", "consideredValues", "consideredValue"}; verifyExpectedFieldsAreMissing(generatedError, missingFields); ASSERT_LTE(generatedError.objsize(), maxDocValidationErrorSize); } // Comparison operators. // $eq TEST(ComparisonMatchExpression, BasicEq) { BSONObj query = BSON("a" << BSON("$eq" << 2)); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 1); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqMissingPath) { BSONObj query = BSON("a" << BSON("$eq" << 2)); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$eq" << 2)); BSONObj document = BSON("a" << BSON_ARRAY(3 << 4 << 5)); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(3 << 4 << 5)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocumentSingleElement) { BSONObj query = BSON("a.b" << BSON("$eq" << 2)); BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << 3))); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 3); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocument) { BSONObj query = BSON("a.b" << BSON("$eq" << 2)); BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << 3) << BSON("b" << 4) << BSON("b" << 5))); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(3 << 4 << 5)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedArrays) { BSONObj query = BSON("a.b" << BSON("$eq" << 0)); BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(3 << 4)))); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(1 << 2 << 3 << 4)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqEmptyArray) { BSONObj query = BSON("a" << BSON("$eq" << BSON_ARRAY(1 << 2))); BSONObj document = BSON("a" << BSONArray{}); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSONArray{}); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, EqNoOperator) { BSONObj query = BSON("a" << 2); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$eq" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 1); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $ne TEST(ComparisonMatchExpression, BasicNe) { BSONObj query = BSON("a" << BSON("$ne" << 2)); BSONObj document = BSON("a" << 2); BSONObj expectedError = BSON("operatorName" << "$ne" << "specifiedAs" << query << "reason" << "comparison succeeded" << "consideredValue" << 2); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, NeImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$ne" << 2)); BSONObj document = BSON("a" << BSON_ARRAY(1 << 2 << 3)); BSONObj expectedError = BSON("operatorName" << "$ne" << "specifiedAs" << query << "reason" << "comparison succeeded" << "consideredValues" << BSON_ARRAY(1 << 2 << 3)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $lt TEST(ComparisonMatchExpression, BasicLt) { BSONObj query = BSON("a" << BSON("$lt" << 0)); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$lt" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 1); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, LtMissingPath) { BSONObj query = BSON("a" << BSON("$lt" << 0)); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$lt" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, LtImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$lt" << 0)); BSONObj document = BSON("a" << BSON_ARRAY(3 << 4 << 5)); BSONObj expectedError = BSON("operatorName" << "$lt" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(3 << 4 << 5)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $lte TEST(ComparisonMatchExpression, BasicLte) { BSONObj query = BSON("a" << BSON("$lte" << 0)); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$lte" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 1); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, LteMissingPath) { BSONObj query = BSON("a" << BSON("$lte" << 0)); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$lte" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, LteImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$lte" << 0)); BSONObj document = BSON("a" << BSON_ARRAY(3 << 4 << 5)); BSONObj expectedError = BSON("operatorName" << "$lte" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(3 << 4 << 5)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $gt TEST(ComparisonMatchExpression, BasicGt) { BSONObj query = BSON("a" << BSON("$gt" << 3)); BSONObj document = BSON("a" << 0); BSONObj expectedError = BSON("operatorName" << "$gt" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 0); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, GtMissingPath) { BSONObj query = BSON("a" << BSON("$gt" << 3)); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$gt" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, GtImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$gt" << 3)); BSONObj document = BSON("a" << BSON_ARRAY(0 << 1 << 2)); BSONObj expectedError = BSON("operatorName" << "$gt" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(0 << 1 << 2)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $gte TEST(ComparisonMatchExpression, BasicGte) { BSONObj query = BSON("a" << BSON("$gte" << 3)); BSONObj document = BSON("a" << 0); BSONObj expectedError = BSON("operatorName" << "$gte" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValue" << 0); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, GteMissingPath) { BSONObj query = BSON("a" << BSON("$gte" << 3)); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$gte" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, GteImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$gte" << 3)); BSONObj document = BSON("a" << BSON_ARRAY(0 << 1 << 2)); BSONObj expectedError = BSON("operatorName" << "$gte" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSON_ARRAY(0 << 1 << 2)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, GteImplicitArrayTraversalEmptyArray) { BSONObj query = BSON("a" << BSON("$gte" << 3)); BSONObj document = BSON("a" << BSONArray()); BSONObj expectedError = BSON("operatorName" << "$gte" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << BSONArray{}); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $in TEST(ComparisonMatchExpression, BasicIn) { BSONObj query = BSON("a" << BSON("$in" << BSON_ARRAY(1 << 2 << 3))); BSONObj document = BSON("a" << 4); BSONObj expectedError = BSON("operatorName" << "$in" << "specifiedAs" << query << "reason" << "no matching value found in array" << "consideredValue" << 4); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, InMissingPath) { BSONObj query = BSON("a" << BSON("$in" << BSON_ARRAY(1 << 2 << 3))); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$in" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, InNestedDocumentsAndArrays) { BSONObj query = BSON("a.b" << BSON("$in" << BSON_ARRAY(5 << 6 << 7 << BSON_ARRAY(2 << 3 << 4)))); BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(3 << 4)))); BSONObj expectedError = BSON("operatorName" << "$in" << "specifiedAs" << query << "reason" << "no matching value found in array" << "consideredValues" << BSON_ARRAY(1 << 2 << 3 << 4)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $nin TEST(ComparisonMatchExpression, BasicNin) { BSONObj query = BSON("a" << BSON("$nin" << BSON_ARRAY(1 << 2 << 3))); BSONObj document = BSON("a" << 3); BSONObj expectedError = BSON("operatorName" << "$nin" << "specifiedAs" << query << "reason" << "matching value found in array" << "consideredValue" << 3); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, NinNestedDocumentsAndArrays) { BSONObj query = BSON("a.b" << BSON("$nin" << BSON_ARRAY(1 << BSON_ARRAY(2 << 3 << 4)))); BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(3 << 4)))); BSONObj expectedError = BSON("operatorName" << "$nin" << "specifiedAs" << query << "reason" << "matching value found in array" << "consideredValues" << BSON_ARRAY(1 << 2 << 3 << 4)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verify that Comparison operators which accept a regex ($in and $nin) work as expected. TEST(ComparisonMatchExpression, InAcceptsRegex) { BSONObj query = BSON( "a" << BSON("$in" << BSON_ARRAY(BSONRegEx("^v") << BSONRegEx("^b") << BSONRegEx("^c")))); BSONObj document = BSON("a" << "Validation"); BSONObj expectedError = BSON("operatorName" << "$in" << "specifiedAs" << query << "reason" << "no matching value found in array" << "consideredValue" << "Validation"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ComparisonMatchExpression, NinAcceptsRegex) { BSONObj query = BSON( "a" << BSON("$nin" << BSON_ARRAY(BSONRegEx("^v") << BSONRegEx("^b") << BSONRegEx("^c")))); BSONObj document = BSON("a" << "berry"); BSONObj expectedError = BSON("operatorName" << "$nin" << "specifiedAs" << query << "reason" << "matching value found in array" << "consideredValue" << "berry"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Logical operators // $and TEST(LogicalMatchExpression, BasicAnd) { BSONObj failingClause = BSON("a" << BSON("$lt" << 10)); BSONObj query = BSON("$and" << BSON_ARRAY(BSON("b" << BSON("$gt" << 0)) << failingClause)); BSONObj document = BSON("a" << 11 << "b" << 2); BSONObj expectedError = BSON("operatorName" << "$and" << "clausesNotSatisfied" << BSON_ARRAY(BSON("index" << 1 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << failingClause << "reason" << "comparison failed" << "consideredValue" << 11)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, ImplicitAnd) { BSONObj failingClause = BSON("a" << BSON("$lt" << 10)); BSONObj query = BSON("a" << BSON("$gt" << 0 << "$lt" << 10)); BSONObj document = BSON("a" << 11); BSONObj expectedError = BSON("operatorName" << "$and" << "clausesNotSatisfied" << BSON_ARRAY(BSON("index" << 1 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << failingClause << "reason" << "comparison failed" << "consideredValue" << 11)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, AndMultipleFailingClauses) { BSONObj firstFailingClause = BSON("a" << BSON("$lt" << 10)); BSONObj secondFailingClause = BSON("a" << BSON("$gt" << 20)); BSONObj query = BSON("$and" << BSON_ARRAY(firstFailingClause << secondFailingClause)); BSONObj document = BSON("a" << 15); BSONObj expectedError = BSON( "operatorName" << "$and" << "clausesNotSatisfied" << BSON_ARRAY(BSON("index" << 0 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << firstFailingClause << "reason" << "comparison failed" << "consideredValue" << 15)) << BSON("index" << 1 << "details" << BSON("operatorName" << "$gt" << "specifiedAs" << secondFailingClause << "reason" << "comparison failed" << "consideredValue" << 15)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NestedAndDoesNotReportErrorDetailsIfItMatches) { BSONObj query = fromjson("{$and: [{$and: [{a: 1}]}, {$and: [{b: 1}]}]}"); BSONObj document = fromjson("{a: 1, b: 2}"); BSONObj expectedError = fromjson( "{operatorName: '$and', clausesNotSatisfied: [{index: 1, details: {" " operatorName: '$and', clausesNotSatisfied: [{index: 0, details: {" " operatorName: '$eq', " " specifiedAs: {b: 1}, " " reason: 'comparison failed', " " consideredValue: 2}}]}}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $or TEST(LogicalMatchExpression, BasicOr) { BSONObj failingClause = BSON("a" << BSON("$lt" << 10)); BSONObj query = BSON("$or" << BSON_ARRAY(failingClause)); BSONObj document = BSON("a" << 11); BSONObj expectedError = BSON("operatorName" << "$or" << "clausesNotSatisfied" << BSON_ARRAY(BSON("index" << 0 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << failingClause << "reason" << "comparison failed" << "consideredValue" << 11)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, OrMultipleFailingClauses) { BSONObj firstFailingClause = BSON("a" << BSON("$lt" << 10)); BSONObj secondFailingClause = BSON("a" << BSON("$gt" << 20)); BSONObj query = BSON("$or" << BSON_ARRAY(firstFailingClause << secondFailingClause)); BSONObj document = BSON("a" << 15); BSONObj expectedError = BSON( "operatorName" << "$or" << "clausesNotSatisfied" << BSON_ARRAY(BSON("index" << 0 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << firstFailingClause << "reason" << "comparison failed" << "consideredValue" << 15)) << BSON("index" << 1 << "details" << BSON("operatorName" << "$gt" << "specifiedAs" << secondFailingClause << "reason" << "comparison failed" << "consideredValue" << 15)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $nor TEST(LogicalMatchExpression, BasicNor) { BSONObj firstClause = BSON("a" << BSON("$gt" << 10)); BSONObj secondFailingClause = BSON("b" << BSON("$lt" << 10)); BSONObj query = BSON("$nor" << BSON_ARRAY(firstClause << secondFailingClause)); BSONObj document = BSON("a" << 9 << "b" << 9); BSONObj expectedError = BSON("operatorName" << "$nor" << "clausesSatisfied" << BSON_ARRAY(BSON("index" << 1 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << secondFailingClause << "reason" << "comparison succeeded" << "consideredValue" << 9)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NorAllSuccessfulClauses) { BSONObj firstFailingClause = BSON("a" << BSON("$lt" << 20)); BSONObj secondFailingClause = BSON("a" << BSON("$gt" << 10)); BSONObj query = BSON("$nor" << BSON_ARRAY(firstFailingClause << secondFailingClause)); BSONObj document = BSON("a" << 15); BSONObj expectedError = BSON( "operatorName" << "$nor" << "clausesSatisfied" << BSON_ARRAY(BSON("index" << 0 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << firstFailingClause << "reason" << "comparison succeeded" << "consideredValue" << 15)) << BSON("index" << 1 << "details" << BSON("operatorName" << "$gt" << "specifiedAs" << secondFailingClause << "reason" << "comparison succeeded" << "consideredValue" << 15)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $not TEST(LogicalMatchExpression, BasicNot) { BSONObj failingClause = BSON("$lt" << 10); BSONObj failingQuery = BSON("a" << failingClause); BSONObj query = BSON("a" << BSON("$not" << failingClause)); BSONObj document = BSON("a" << 9); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << failingQuery << "reason" << "comparison succeeded" << "consideredValue" << 9)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NotOverImplicitAnd) { BSONObj failingQuery = BSON("$lt" << 20 << "$gt" << 5); BSONObj query = BSON("a" << BSON("$not" << failingQuery)); BSONObj document = BSON("a" << 10); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$and" << "clausesSatisfied" << BSON_ARRAY( BSON("index" << 0 << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << BSON("a" << BSON("$lt" << 20)) << "reason" << "comparison succeeded" << "consideredValue" << 10)) << BSON("index" << 1 << "details" << BSON("operatorName" << "$gt" << "specifiedAs" << BSON("a" << BSON("$gt" << 5)) << "reason" << "comparison succeeded" << "consideredValue" << 10))))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NestedNot) { BSONObj failingClause = BSON("$lt" << 10); BSONObj failingQuery = BSON("a" << failingClause); BSONObj query = BSON("a" << BSON("$not" << BSON("$not" << failingClause))); BSONObj document = BSON("a" << 11); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$lt" << "specifiedAs" << failingQuery << "reason" << "comparison failed" << "consideredValue" << 11))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Combine logical operators TEST(LogicalMatchExpression, NestedAndOr) { BSONObj query = fromjson( "{'$and':[" " {'$or': " " [{'price': {'$gt': 50}}, " " {'price': {'$lt': 20}}]}," " {'qty': {'$gt': 0}}," " {'qty': {'$lt': 10}}]}"); BSONObj document = fromjson("{'price': 30, 'qty': 30}"); BSONObj expectedError = fromjson( "{'operatorName': '$and'," "'clausesNotSatisfied': [" " {'index': 0, 'details': " " {'operatorName': '$or'," " 'clausesNotSatisfied': [" " {'index': 0, 'details': " " {'operatorName': '$gt'," " 'specifiedAs': {'price': {'$gt': 50}}," " 'reason': 'comparison failed'," " 'consideredValue': 30}}," " {'index': 1, 'details':" " {'operatorName': '$lt'," " 'specifiedAs': {'price': {'$lt': 20}}," " 'reason': 'comparison failed'," " 'consideredValue': 30}}]}}, " " {'index': 2, 'details': " " {'operatorName': '$lt'," " 'specifiedAs': {'qty': {'$lt': 10}}," " 'reason': 'comparison failed'," " 'consideredValue': 30}}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NestedAndOrOneFailingClause) { BSONObj query = fromjson( "{'$and':[" " {'$or':[{'price': {'$lt': 20}}]}," " {'qty': {'$gt': 0}}," " {'qty': {'$lt': 10}}]}"); BSONObj document = fromjson("{'price': 15, 'qty': 30}"); BSONObj expectedError = fromjson( "{'operatorName': '$and'," "'clausesNotSatisfied': [" " {'index': 2, 'details': " " {'operatorName': '$lt'," " 'specifiedAs': {'qty': {'$lt': 10}}," " 'reason': 'comparison failed'," " 'consideredValue': 30}}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NestedAndOrNorOneSuccessfulClause) { BSONObj query = fromjson( "{'$and':[" " {'$or': [" " {'price': {'$lt': 20}}]}," " {'$nor':[" " {'qty': {'$gt': 20}}," " {'qty': {'$lt': 20}}]}]}"); BSONObj document = fromjson("{'price': 10, 'qty': 15}"); BSONObj expectedError = fromjson( "{'operatorName': '$and'," "'clausesNotSatisfied': [" " {'index': 1, 'details': " " {'operatorName': '$nor'," " 'clausesSatisfied': [" " {'index': 1, 'details':" " {'operatorName': '$lt'," " 'specifiedAs': {'qty': {'$lt': 20}}," " 'reason': 'comparison succeeded'," " 'consideredValue': 15}}]}}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(LogicalMatchExpression, NestedAndOrNorNotOneFailingClause) { BSONObj query = fromjson( "{'$and':[" " {'$or': [" " {'price': {'$lt': 20}}]}," " {'$nor':[" " {'qty': {'$gt': 30}}," " {'qty': {'$not': {'$lt': 20}}}]}]}"); BSONObj document = fromjson("{'price': 10, 'qty': 25}"); BSONObj expectedError = fromjson( "{'operatorName': '$and'," "'clausesNotSatisfied': [" " {'index': 1, 'details': " " {'operatorName': '$nor'," " 'clausesSatisfied': [" " {'index': 1, 'details':" " {'operatorName': '$not'," " 'details': " " {'operatorName': '$lt'," " 'specifiedAs': {'qty': {'$lt': 20}}," " 'reason': 'comparison failed'," " 'consideredValue': 25}}}]}}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Miscellaneous operators. // $exists TEST(MiscellaneousMatchExpression, BasicExists) { BSONObj query = BSON("a" << BSON("$exists" << true)); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$exists" << "specifiedAs" << query << "reason" << "path does not exist"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NotExists) { BSONObj query = BSON("a" << BSON("$exists" << false)); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$exists" << "specifiedAs" << query << "reason" << "path does exist"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $type TEST(MiscellaneousMatchExpression, BasicType) { BSONObj query = BSON("a" << BSON("$type" << "int")); BSONObj document = BSON("a" << "one"); BSONObj expectedError = BSON("operatorName" << "$type" << "specifiedAs" << query << "reason" << "type did not match" << "consideredValue" << "one" << "consideredType" << "string"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NotType) { BSONObj failingClause = BSON("$type" << "string"); BSONObj failingQuery = BSON("a" << failingClause); BSONObj query = BSON("a" << BSON("$not" << failingClause)); BSONObj document = BSON("a" << "words"); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$type" << "specifiedAs" << failingQuery << "reason" << "type did match" << "consideredValue" << "words" << "consideredType" << "string")); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, TypeMissingPath) { BSONObj query = BSON("a" << BSON("$type" << "double")); BSONObj document = BSON("b" << 1); BSONObj expectedError = BSON("operatorName" << "$type" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, TypeImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$type" << "double")); BSONObj document = BSON("a" << BSON_ARRAY("x" << "y" << "z")); BSONObj expectedError = BSON("operatorName" << "$type" << "specifiedAs" << query << "reason" << "type did not match" << "consideredValues" << BSON_ARRAY("x" << "y" << "z") << "consideredType" << "string"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, TypeImplicitArrayTraversalEmptyArray) { BSONObj query = BSON("a" << BSON("$type" << "double")); BSONObj document = BSON("a" << BSONArray{}); BSONObj expectedError = BSON("operatorName" << "$type" << "specifiedAs" << query << "reason" << "type did not match" << "consideredValues" << BSONArray{}); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $expr TEST(MiscellaneousMatchExpression, BasicExpr) { BSONObj query = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a" << "$b"))); BSONObj document = BSON("a" << 1 << "b" << 2); BSONObj expectedError = BSON("operatorName" << "$expr" << "specifiedAs" << query << "reason" << "expression did not match" << "expressionResult" << false); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NorExpr) { BSONObj failingClause = BSON("$eq" << BSON_ARRAY("$a" << "$b")); BSONObj failingQuery = BSON("$expr" << failingClause); BSONObj query = BSON("$nor" << BSON_ARRAY(failingQuery)); BSONObj document = BSON("a" << 1 << "b" << 1); BSONObj expectedError = BSON("operatorName" << "$nor" << "clausesSatisfied" << BSON_ARRAY(BSON( "index" << 0 << "details" << BSON("operatorName" << "$expr" << "specifiedAs" << failingQuery << "reason" << "expression did match" << "expressionResult" << true)))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ExprImplicitArrayTraversal) { BSONObj query = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a" << "$b"))); BSONObj document = BSON("a" << BSON_ARRAY(0 << 1 << 2) << "b" << BSON_ARRAY(3 << 4 << 5)); BSONObj expectedError = BSON("operatorName" << "$expr" << "specifiedAs" << query << "reason" << "expression did not match" << "expressionResult" << false); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ExprWhichThrowsGeneratesError) { BSONObj query = fromjson("{$expr: {$divide: [10, 0]}}"); BSONObj doc = fromjson("{}"); BSONObj expectedError = fromjson( "{operatorName: '$expr', " "specifiedAs: {$expr: {$divide: [10, 0]}}," "reason: 'failed to evaluate aggregation expression'," "details: " " {code: 2, " " codeName: 'BadValue', " " errmsg: \"can't $divide by zero\"}}"); doc_validation_error::verifyGeneratedError(query, doc, expectedError, true /* shouldThrow */); } TEST(MiscellaneousMatchExpression, MultipleExprsWhichThrow) { BSONObj query = fromjson("{$and: [{$expr: {$concat: ['$a', '$b']}}, {$expr: {$divide: [10, 0]}}]}"); BSONObj doc = fromjson("{a: 'field b is not a string', b: 1}"); BSONObj expectedError = fromjson( "{operatorName: '$and', clausesNotSatisfied: [" "{index: 0, details: " " {operatorName: '$expr', " " specifiedAs: {$expr: {$concat: [\"$a\", \"$b\"]}}," " reason: 'failed to evaluate aggregation expression'," " details: " " {code: 16702, " " codeName: 'Location16702', " " errmsg: \"$concat only supports strings, not int\"}}}," "{index: 1, details: " " {operatorName: '$expr', " " specifiedAs: {$expr: {$divide: [10, 0]}}," " reason: 'failed to evaluate aggregation expression'," " details: " " {code: 2, " " codeName: 'BadValue', " " errmsg: \"can't $divide by zero\"}}}]}"); doc_validation_error::verifyGeneratedError(query, doc, expectedError, true /* shouldThrow */); } TEST(MiscellaneousMatchExpression, OneExprThrowsAmongMultiple) { BSONObj query = fromjson("{$and: [{$expr: {$concat: ['$a', '$b']}}, {$expr: {$divide: [10, 0]}}]}"); BSONObj doc = fromjson("{a: 'field b IS a string', b: 'This will concat and not throw'}"); BSONObj expectedError = fromjson( "{operatorName: '$and', clausesNotSatisfied: [" "{index: 1, details: " " {operatorName: '$expr', " " specifiedAs: {$expr: {$divide: [10, 0]}}," " reason: 'failed to evaluate aggregation expression'," " details: " " {code: 2, " " codeName: 'BadValue', " " errmsg: \"can't $divide by zero\"}}}]}"); doc_validation_error::verifyGeneratedError(query, doc, expectedError, true /* shouldThrow */); } TEST(MiscellaneousMatchExpression, ExprsWhichThrowUnderInversion) { BSONObj query = fromjson("{$nor: [{$expr: {$concat: ['$a', '$b']}}, {$expr: {$divide: [10, 0]}}]}"); BSONObj doc = fromjson("{a: 'field b is not a string', b: 1}"); BSONObj expectedError = fromjson( "{operatorName: '$nor', clausesSatisfied: [" "{index: 0, details: " " {operatorName: '$expr', " " specifiedAs: {$expr: {$concat: [\"$a\", \"$b\"]}}," " reason: 'failed to evaluate aggregation expression'," " details: " " {code: 16702, " " codeName: 'Location16702', " " errmsg: \"$concat only supports strings, not int\"}}}," "{index: 1, details: " " {operatorName: '$expr', " " specifiedAs: {$expr: {$divide: [10, 0]}}," " reason: 'failed to evaluate aggregation expression'," " details: " " {code: 2, " " codeName: 'BadValue', " " errmsg: \"can't $divide by zero\"}}}]}"); doc_validation_error::verifyGeneratedError(query, doc, expectedError, true /* shouldThrow */); } // $sampleRate TEST(MiscellaneousMatchExpression, SampleRateAlwaysFalse) { BSONObj query = fromjson("{$sampleRate: 0}"); BSONObj document = fromjson("{'will this always fail?': 'yes!'}"); BSONObj expectedError = BSON("operatorName" << "$sampleRate" << "specifiedAs" << query << "reason" << "expression did not match" << "expressionResult" << false); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, SampleRateAlwaysTrue) { BSONObj query = fromjson("{$nor: [{$sampleRate: 1}]}"); BSONObj document = fromjson("{'will this always succeed?': 'yes!'}"); BSONObj expectedError = fromjson( "{operatorName: '$nor', 'clausesSatisfied': [" " {index: 0, details:" " {operatorName: '$sampleRate'," " specifiedAs: {$sampleRate: 1}," " reason: 'expression did match'," " expressionResult: true}}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ExprExpressionResultNumeric) { BSONObj query = BSON("$expr" << 0); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$expr" << "specifiedAs" << query << "reason" << "expression did not match" << "expressionResult" << 0); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NorExprExpressionResultObject) { BSONObj failingExpression = BSON("$expr" << BSON("$literal" << BSON("b" << 1))); BSONObj query = BSON("$nor" << BSON_ARRAY(failingExpression)); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$nor" << "clausesSatisfied" << BSON_ARRAY(BSON("index" << 0 << "details" << BSON("operatorName" << "$expr" << "specifiedAs" << failingExpression << "reason" << "expression did match" << "expressionResult" << BSON("b" << 1))))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $mod TEST(MiscellaneousMatchExpression, BasicMod) { BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1))); BSONObj document = BSON("a" << 2); BSONObj expectedError = BSON("operatorName" << "$mod" << "specifiedAs" << query << "reason" << "$mod did not evaluate to expected remainder" << "consideredValue" << 2); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NotMod) { BSONObj failingClause = BSON("$mod" << BSON_ARRAY(2 << 0)); BSONObj failingQuery = BSON("a" << failingClause); BSONObj query = BSON("a" << BSON("$not" << failingClause)); BSONObj document = BSON("a" << 2); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$mod" << "specifiedAs" << failingQuery << "reason" << "$mod did evaluate to expected remainder" << "consideredValue" << 2)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ModMissingPath) { BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1))); BSONObj document = BSON("b" << 2); BSONObj expectedError = BSON("operatorName" << "$mod" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1))); BSONObj document = BSON("a" << BSON_ARRAY(0 << 2 << 4)); BSONObj expectedError = BSON("operatorName" << "$mod" << "specifiedAs" << query << "reason" << "$mod did not evaluate to expected remainder" << "consideredValues" << BSON_ARRAY(0 << 2 << 4)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ModNonNumeric) { BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1))); BSONObj document = BSON("a" << "two"); BSONObj expectedError = BSON("operatorName" << "$mod" << "specifiedAs" << query << "reason" << "type did not match" << "consideredType" << "string" << "expectedTypes" << BSON_ARRAY("decimal" << "double" << "int" << "long") << "consideredValue" << "two"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalNonNumeric) { BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1))); BSONObj document = BSON("a" << BSON_ARRAY("zero" << "two" << "four")); BSONObj expectedError = BSON("operatorName" << "$mod" << "specifiedAs" << query << "reason" << "type did not match" << "consideredType" << "string" << "expectedTypes" << BSON_ARRAY("decimal" << "double" << "int" << "long") << "consideredValues" << BSON_ARRAY("zero" << "two" << "four")); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalMixedTypes) { BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1))); BSONObj document = BSON("a" << BSON_ARRAY(0 << "two" << "four")); BSONObj expectedError = BSON("operatorName" << "$mod" << "specifiedAs" << query << "reason" << "$mod did not evaluate to expected remainder" << "consideredValues" << BSON_ARRAY(0 << "two" << "four")); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $regex TEST(MiscellaneousMatchExpression, BasicRegex) { BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" << "")); BSONObj document = BSON("a" << "one"); BSONObj expectedError = BSON("operatorName" << "$regex" << "specifiedAs" << query << "reason" << "regular expression did not match" << "consideredValue" << "one"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NotRegex) { BSONObj failingClause = BSON("$regex" << BSONRegEx("myRegex", "") << "$options" << ""); BSONObj failingQuery = BSON("a" << failingClause); BSONObj query = BSON("a" << BSON("$not" << failingClause)); BSONObj document = BSON("a" << "myRegex"); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$regex" << "specifiedAs" << failingQuery << "reason" << "regular expression did match" << "consideredValue" << "myRegex")); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, RegexMissingPath) { BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" << "")); BSONObj document = BSON("b" << "myRegex"); BSONObj expectedError = BSON("operatorName" << "$regex" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversal) { BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" << "")); BSONObj document = BSON("a" << BSON_ARRAY("x" << "y" << "z")); BSONObj expectedError = BSON("operatorName" << "$regex" << "specifiedAs" << query << "reason" << "regular expression did not match" << "consideredValues" << BSON_ARRAY("x" << "y" << "z")); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, RegexNonString) { BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" << "")); BSONObj document = BSON("a" << 1); BSONObj expectedError = BSON("operatorName" << "$regex" << "specifiedAs" << query << "reason" << "type did not match" << "consideredType" << "int" << "expectedTypes" << BSON_ARRAY("regex" << "string" << "symbol") << "consideredValue" << 1); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalNonString) { BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" << "")); BSONObj document = BSON("a" << BSON_ARRAY(0 << 1 << 2)); BSONObj expectedError = BSON("operatorName" << "$regex" << "specifiedAs" << query << "reason" << "type did not match" << "consideredType" << "int" << "expectedTypes" << BSON_ARRAY("regex" << "string" << "symbol") << "consideredValues" << BSON_ARRAY(0 << 1 << 2)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalMixedTypes) { BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" << "")); BSONObj document = BSON("a" << BSON_ARRAY("x" << 1 << 2)); BSONObj expectedError = BSON("operatorName" << "$regex" << "specifiedAs" << query << "reason" << "regular expression did not match" << "consideredValues" << BSON_ARRAY("x" << 1 << 2)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, RegexNoExplicitOperator) { BSONObj query = BSON("a" << BSONRegEx("^S")); BSONObj document = BSON("a" << "so sorry; not capitalized"); BSONObj expectedError = fromjson( "{operatorName: '$regex'," "specifiedAs: {a: /^S/}, " "reason: 'regular expression did not match'," "consideredValue: 'so sorry; not capitalized'}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(MiscellaneousMatchExpression, NotOverRegexNoExplicitOperator) { BSONObj query = BSON("a" << BSON("$not" << BSONRegEx("^S"))); BSONObj document = BSON("a" << "S"); BSONObj expectedError = fromjson( "{operatorName: '$not', 'details': " " {operatorName: '$regex'," " specifiedAs: {a: /^S/}, " " reason: 'regular expression did match'," " consideredValue: 'S'}}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllClear expression with numeric bitmask correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearNumeric) { BSONObj query = BSON("a" << BSON("$bitsAllClear" << 2)); BSONObj document = BSON("a" << 7); BSONObj expectedError = BSON("operatorName" << "$bitsAllClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 7); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllClear expression with numeric bitmask correctly generates a validation // error on unexpected match of value. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearNumericOnValueMatch) { BSONObj query = BSON("a" << BSON("$not" << BSON("$bitsAllClear" << 2))); BSONObj document = BSON("a" << 5); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$bitsAllClear" << "specifiedAs" << BSON("a" << BSON("$bitsAllClear" << 2)) << "reason" << "bitwise operator matched successfully" << "consideredValue" << 5)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllClear expression with position list correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearPositionList) { BSONObj query = BSON("a" << BSON("$bitsAllClear" << BSON_ARRAY(1))); BSONObj document = BSON("a" << 7); BSONObj expectedError = BSON("operatorName" << "$bitsAllClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 7); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllClear expression with BinData bitmask correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearBinData) { const unsigned char binaryData[] = {0x02}; BSONObj query = BSON("a" << BSON("$bitsAllClear" << BSONBinData( &binaryData, sizeof(binaryData), BinDataGeneral))); BSONObj document = BSON("a" << 7); BSONObj expectedError = BSON("operatorName" << "$bitsAllClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 7); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllSet expression correctly generates a validation error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllSetNumeric) { BSONObj query = BSON("a" << BSON("$bitsAllSet" << 2)); BSONObj document = BSON("a" << 5); BSONObj expectedError = BSON("operatorName" << "$bitsAllSet" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 5); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllSet expression with position list correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllSetPositionList) { BSONObj query = BSON("a" << BSON("$bitsAllSet" << BSON_ARRAY(1))); BSONObj document = BSON("a" << 5); BSONObj expectedError = BSON("operatorName" << "$bitsAllSet" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 5); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllSet expression with BinData bitmask correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllSetBinData) { const unsigned char binaryData[] = {0x02}; BSONObj query = BSON( "a" << BSON("$bitsAllSet" << BSONBinData(&binaryData, sizeof(binaryData), BinDataGeneral))); BSONObj document = BSON("a" << 5); BSONObj expectedError = BSON("operatorName" << "$bitsAllSet" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 5); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnyClear expression correctly generates a validation error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearNumeric) { BSONObj query = BSON("a" << BSON("$bitsAnyClear" << 3)); BSONObj document = BSON("a" << 7); BSONObj expectedError = BSON("operatorName" << "$bitsAnyClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 7); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnyClear expression with position list correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearPositionList) { BSONObj query = BSON("a" << BSON("$bitsAnyClear" << BSON_ARRAY(1 << 0))); BSONObj document = BSON("a" << 7); BSONObj expectedError = BSON("operatorName" << "$bitsAnyClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 7); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnyClear expression with BinData bitmask correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearBinData) { const unsigned char binaryData[] = {0x03}; BSONObj query = BSON("a" << BSON("$bitsAnyClear" << BSONBinData( &binaryData, sizeof(binaryData), BinDataGeneral))); BSONObj document = BSON("a" << 7); BSONObj expectedError = BSON("operatorName" << "$bitsAnyClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 7); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnySet expression correctly generates a validation error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnySetNumeric) { BSONObj query = BSON("a" << BSON("$bitsAnySet" << 3)); BSONObj document = BSON("a" << 0); BSONObj expectedError = BSON("operatorName" << "$bitsAnySet" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 0); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnySet expression with position list correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnySetPositionList) { BSONObj query = BSON("a" << BSON("$bitsAnySet" << BSON_ARRAY(1 << 0))); BSONObj document = BSON("a" << 0); BSONObj expectedError = BSON("operatorName" << "$bitsAnySet" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 0); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnySet expression with BinData bitmask correctly generates a validation // error. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnySetBinData) { const unsigned char binaryData[] = {0x03}; BSONObj query = BSON( "a" << BSON("$bitsAnySet" << BSONBinData(&binaryData, sizeof(binaryData), BinDataGeneral))); BSONObj document = BSON("a" << 0); BSONObj expectedError = BSON("operatorName" << "$bitsAnySet" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValue" << 0); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAnyClear expression correctly generates a validation error on value type // mismatch. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearOnTypeMismatch) { BSONObj query = BSON("a" << BSON("$bitsAnyClear" << 3)); BSONObj document = BSON("a" << "someString"); BSONObj expectedError = BSON("operatorName" << "$bitsAnyClear" << "specifiedAs" << query << "reason" << "type did not match" << "consideredType" << "string" << "expectedTypes" << BSON_ARRAY("binData" << "decimal" << "double" << "int" << "long") << "consideredValue" << "someString"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $bitsAllClear expression with numeric bitmask correctly generates a validation // error when applied on an array of numeric values. TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearOnValueArray) { BSONObj query = BSON("a" << BSON("$bitsAllClear" << 2)); BSONArray attributeValue = BSON_ARRAY(7 << 3); BSONObj document = BSON("a" << attributeValue); BSONObj expectedError = BSON("operatorName" << "$bitsAllClear" << "specifiedAs" << query << "reason" << "bitwise operator failed to match" << "consideredValues" << BSON_ARRAY(7 << 3)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoIntersects expression correctly generates a validation error. TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersects) { BSONObj query = fromjson( "{'a': {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, " "0], [0, 0]]]}}}}"); BSONObj point = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(3 << 3)); BSONObj document = BSON("a" << point); BSONObj expectedError = BSON("operatorName" << "$geoIntersects" << "specifiedAs" << query << "reason" << "none of the considered geometries intersected the expression’s geometry" << "consideredValue" << point); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoIntersects expression correctly generates a missing field validation error. TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsMissingField) { BSONObj query = fromjson( "{'a': {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, " "0], [0, 0]]]}}}}"); BSONObj point = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(3 << 3)); BSONObj document = BSON("b" << point); BSONObj expectedError = BSON("operatorName" << "$geoIntersects" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoIntersects expression correctly generates a validation error on unexpected // match of value. TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsOnValueMatch) { BSONObj subquery = fromjson( "{$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, 0], [0, " "0]]]}}}"); BSONObj query = BSON("a" << BSON("$not" << subquery)); BSONObj point = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(1 << 1)); BSONObj document = BSON("a" << point); BSONObj expectedError = BSON( "operatorName" << "$not" << "details" << BSON("operatorName" << "$geoIntersects" << "specifiedAs" << BSON("a" << subquery) << "reason" << "at least one of considered geometries intersected the expression’s geometry" << "consideredValue" << point)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoIntersects expression correctly generates a correct validation error on value // type mismatch. TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsOnTypeMismatch) { BSONObj query = fromjson( "{'a': {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, " "0], [0, 0]]]}}}}"); BSONObj point = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(3 << 3)); BSONObj document = BSON("a" << 2); BSONObj expectedError = BSON("operatorName" << "$geoIntersects" << "specifiedAs" << query << "reason" << "could not find a valid geometry at the given path" << "consideredValue" << 2); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(GeoMatchExpression, GeneratesValidationErrorForGeoWithin) { BSONObj query = fromjson( "{coordinates: {$geoWithin: {$centerSphere: [[-79.9081268,9.3547792], 0.02523]}}}"); BSONArray coordinates = BSON_ARRAY(-72.5419922 << 18.2312794); BSONObj document = BSON("coordinates" << coordinates); BSONObj expectedError = BSON("operatorName" << "$geoWithin" << "specifiedAs" << query << "reason" << "none of the considered geometries were contained within the " "expression’s geometry" << "consideredValues" << coordinates); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(GeoMatchExpression, GeneratesValidationErrorForGeoWithinTypeMismatch) { BSONObj query = fromjson( "{coordinates: {$geoWithin: {$centerSphere: [[-79.9081268,9.3547792], 0.02523]}}}"); // 'arr' is of $geoWithin's expected type (an array), but is not a valid geometry. BSONArray arr = BSON_ARRAY("foo" << "bar" << "baz"); BSONObj document = BSON("coordinates" << arr); BSONObj expectedError = BSON("operatorName" << "$geoWithin" << "specifiedAs" << query << "reason" << "could not find a valid geometry at the given path" << "consideredValues" << arr); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoIntersects expression correctly generates a validation error when applied on an // array of points. TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsOnValueArray) { BSONObj query = fromjson( "{'a': {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, " "0], [0, 0]]]}}}}"); BSONObj point1 = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(3 << 3)); BSONObj point2 = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(4 << 4)); auto points = BSON_ARRAY(point1 << point2); BSONObj document = BSON("a" << points); BSONObj expectedError = BSON("operatorName" << "$geoIntersects" << "specifiedAs" << query << "reason" << "none of the considered geometries intersected the expression’s geometry" << "consideredValues" << points); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoWithin expression correctly generates a validation error. TEST(GeoMatchExpression, GeneratesValidationErrorGeoWithin) { BSONObj query = fromjson( "{'a': {$geoWithin: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, 0], " "[0, 0]]]}}}}"); BSONObj point = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(3 << 3)); BSONObj document = BSON("a" << point); BSONObj expectedError = BSON("operatorName" << "$geoWithin" << "specifiedAs" << query << "reason" << "none of the considered geometries were contained within the expression’s geometry" << "consideredValue" << point); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Verifies that $geoWithin expression correctly generates an inverse validation error. TEST(GeoMatchExpression, GeneratesValidationErrorForMatchGeoWithin) { BSONObj subquery = fromjson( "{$geoWithin: {$geometry: {type: 'Polygon', coordinates: [[[0, 0], [0, 3], [3, 0], [0, " "0]]]}}}"); BSONObj query = BSON("a" << BSON("$not" << subquery)); BSONObj point = BSON("type" << "Point" << "coordinates" << BSON_ARRAY(1 << 1)); BSONObj document = BSON("a" << point); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$geoWithin" << "specifiedAs" << BSON("a" << subquery) << "reason" << "at least one of considered geometries was contained " "within the expression’s geometry" << "consideredValue" << point)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Array operators. // $size TEST(ArrayMatchingMatchExpression, BasicSize) { BSONObj query = BSON("a" << BSON("$size" << 2)); BSONObj document = BSON("a" << BSON_ARRAY(1 << 2 << 3)); BSONObj expectedError = BSON("operatorName" << "$size" << "specifiedAs" << query << "reason" << "array length was not equal to given size" << "consideredValue" << BSON_ARRAY(1 << 2 << 3)); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, SizeNonArray) { BSONObj query = BSON("a" << BSON("$size" << 2)); BSONObj document = BSON("a" << 3); BSONObj expectedError = BSON("operatorName" << "$size" << "specifiedAs" << query << "reason" << "type did not match" << "consideredType" << "int" << "expectedType" << "array" << "consideredValue" << 3); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, SizeMissingPath) { BSONObj query = BSON("a" << BSON("$size" << 2)); BSONObj document = BSON("b" << 3); BSONObj expectedError = BSON("operatorName" << "$size" << "specifiedAs" << query << "reason" << "field was missing"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, NotOverSize) { BSONObj query = BSON("a" << BSON("$not" << BSON("$size" << 2))); BSONObj document = BSON("a" << BSON_ARRAY(1 << 2)); BSONObj expectedError = BSON("operatorName" << "$not" << "details" << BSON("operatorName" << "$size" << "specifiedAs" << BSON("a" << BSON("$size" << 2)) << "reason" << "array length was equal to given size" << "consideredValue" << BSON_ARRAY(1 << 2))); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $all TEST(ArrayMatchingMatchExpression, BasicAll) { BSONObj query = fromjson("{'a': {'$all': [1,2,3]}}"); BSONObj document = fromjson("{'a': [1,2,4]}"); BSONObj expectedError = fromjson( "{'operatorName': '$all'," "'specifiedAs': {'a': {'$all': [1,2,3]}}," "'reason': 'array did not contain all specified values'," "'consideredValue': [1,2,4]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, AllRegex) { BSONObj query = fromjson("{'a': {'$all': [/^a/,/^b/]}}"); BSONObj document = fromjson("{'a': ['abc', 'cbc']}"); BSONObj expectedError = fromjson( "{'operatorName': '$all'," "'specifiedAs': {'a': {'$all': [/^a/,/^b/]}}," "'reason': 'array did not contain all specified values'," "'consideredValue': ['abc', 'cbc']}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, AllMissingPath) { BSONObj query = fromjson("{'a': {'$all': [1,2,3]}}"); BSONObj document = fromjson("{'b': [1,2,3]}"); BSONObj expectedError = fromjson( "{'operatorName': '$all'," "'specifiedAs': {'a': {'$all': [1,2,3]}}," "'reason': 'field was missing'}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, AllNoValues) { BSONObj query = fromjson("{'a': {'$all': []}}"); BSONObj document = fromjson("{'a': [1,2,3]}"); BSONObj expectedError = fromjson( "{'operatorName': '$all'," "'specifiedAs': {'a': {'$all': []}}," "'reason': 'expression always evaluates to false'}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, NotOverAll) { BSONObj query = fromjson("{'a': {'$not': {'$all': [1,2,3]}}}"); BSONObj document = fromjson("{'a': [1,2,3]}"); BSONObj expectedError = fromjson( "{'operatorName': '$not'," "'details':" " {'operatorName': '$all'," " 'specifiedAs': {'a': {'$all': [1,2,3]}}," " 'reason': 'array did contain all specified values'," " 'consideredValue': [1,2,3]}}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $elemMatch TEST(ArrayMatchingMatchExpression, BasicElemMatchValue) { BSONObj query = fromjson("{'a': {'$elemMatch': {'$gt': 0,'$lt': 10}}}"); BSONObj document = fromjson("{'a': [10,11,12]}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}}," "'reason': 'array did not satisfy the child predicate'," "'consideredValue': [10,11,12]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, ElemMatchValueMissingPath) { BSONObj query = fromjson("{'a': {'$elemMatch': {'$gt': 0,'$lt': 10}}}"); BSONObj document = fromjson("{'b': [10,11,12]}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}}," "'reason': 'field was missing'}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, ElemMatchValueNonArray) { BSONObj query = fromjson("{'a': {'$elemMatch': {'$gt': 0,'$lt': 10}}}"); BSONObj document = fromjson("{'a': 5}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}}," "'reason': 'type did not match'," "'consideredType': 'int'," "'expectedType': 'array'," "'consideredValue': 5}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, NotOverElemMatchValue) { BSONObj query = fromjson("{'a': {'$not': {'$elemMatch': {'$gt': 0,'$lt': 10}}}}"); BSONObj document = fromjson("{'a': [3,4,5]}"); BSONObj expectedError = fromjson( "{'operatorName': '$not', " "'details': {'operatorName': '$elemMatch'," " 'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}}," " 'reason': 'array did satisfy the child predicate'," " 'consideredValue': [3,4,5]}}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, BasicElemMatchObject) { BSONObj query = fromjson("{'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}}"); BSONObj document = fromjson("{'a': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}}," "'reason': 'array did not satisfy the child predicate'," "'consideredValue': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, ElemMatchObjectMissingPath) { BSONObj query = fromjson("{'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}}"); BSONObj document = fromjson("{'b': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}}," "'reason': 'field was missing'}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, ElemMatchObjectNonArray) { BSONObj query = fromjson("{'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}}"); BSONObj document = fromjson("{'a': 'foo'}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}}," "'reason': 'type did not match'," "'consideredType': 'string'," "'expectedType': 'array'," "'consideredValue': 'foo'}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, NestedElemMatchObject) { BSONObj query = fromjson("{'a': {'$elemMatch': {'b': {$elemMatch: {'c': {'$lt': 0}}}}}}"); BSONObj document = fromjson("{'a': [{'b': [{'c': [1,2,3]}, {'c': [4,5,6]}]}]}"); BSONObj expectedError = fromjson( "{'operatorName': '$elemMatch'," "'specifiedAs': {'a': {'$elemMatch': {'b': {$elemMatch: {'c': {'$lt': 0}}}}}}," "'reason': 'array did not satisfy the child predicate'," "'consideredValue': [{'b': [{'c': [1,2,3]}, {'c': [4,5,6]}]}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } TEST(ArrayMatchingMatchExpression, NotOverElemMatchObject) { BSONObj query = fromjson("{'a': {'$not': {'$elemMatch': {'b': {'$gte': 0}, 'c': {'$lt': 10}}}}}"); BSONObj document = fromjson("{'a': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}"); BSONObj expectedError = fromjson( "{'operatorName': '$not', " "'details': {'operatorName': '$elemMatch'," " 'specifiedAs': {'a':{'$elemMatch': {'b': {'$gte': 0}, 'c': {'$lt': 10}}}}," " 'reason': 'array did satisfy the child predicate'," " 'consideredValue': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // $all and $elemMatch TEST(ArrayMatchingMatchExpression, AllOverElemMatch) { BSONObj query = fromjson( "{'a': {$all: [" " {'$elemMatch': {'b': {'$gte': 0}}}," " {'$elemMatch': {'c': {'$lt': 0}}}]}}"); BSONObj document = fromjson("{'a': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}"); BSONObj expectedError = fromjson( "{'operatorName': '$all'," "'specifiedAs': {'a': {'$all': " " [{'$elemMatch': {'b': {'$gte': 0}}}, {'$elemMatch': {'c': {'$lt': 0}}}]}}," "'reason': 'array did not contain all specified values'," "'consideredValue': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}"); doc_validation_error::verifyGeneratedError(query, document, expectedError); } // Truncation tests /** * Generates a query of nested $ors that is 'desiredDepth' levels deep. */ BSONObj generateDeeplyNestedOr(std::uint32_t desiredDepth) { BSONObj query = BSON("a" << 0); for (auto i = 1u; i < desiredDepth; ++i) { // 'i' is cast to an int so that it can be used in constructing the deep query. BSONObj clause = BSON("a" << static_cast(i)); query = BSON("$or" << BSON_ARRAY(query << clause)); } ASSERT_OK(validateBSON(query.objdata(), query.objsize())); return query; } TEST(ValidationErrorTruncation, BasicDeeplyNestedError) { // The number of levels of nesting that a single failed $or will contribute to the generated // error. static constexpr int depthFactor = 3; // Verify that a query that is too deep will not return any error detail. auto verifyFailingQuery = [](const BSONObj& query, BSONObj& failingDoc) -> void { // 'failingQuery' will generate a truncated error with no detail. BSONObj error = generateValidationError(query, failingDoc, false /* shouldThrow */); auto reason = error.getField("reason"); auto truncated = error.getField("truncated"); ASSERT(reason); ASSERT_TRUE(truncated.Bool()); ASSERT_EQ(reason.type(), BSONType::String); ASSERT_EQ(reason.valueStringData(), "generated error was too deeply nested"); }; BSONObj doc = BSON("b" << 1); // Build a query which will generate an error that is deeper than the maximum allowed // BSONDepth. auto depth = doc_validation_error::computeMaxAllowedValidationErrorDepth() + (2 * depthFactor); depth /= depthFactor; verifyFailingQuery(generateDeeplyNestedOr(depth), doc); // Should still fail when just above the maximum allowed depth. --depth; verifyFailingQuery(generateDeeplyNestedOr(depth), doc); // Should no longer fail when below the maximum allowed depth. --depth; BSONObj error = generateValidationError(generateDeeplyNestedOr(depth), doc, false /* shouldThrow */); ASSERT_FALSE(error.hasField("reason")); ASSERT_FALSE(error.hasField("truncated")); ASSERT_EQ(error.getField("details").type(), BSONType::Object); } TEST(DocValidationTruncationTest, TruncatedConsideredValuesArray) { // Build a large array of numbers. BSONObj query = BSON("a" << BSON("$lt" << 0)); BSONArrayBuilder valuesArray; BSONArrayBuilder consideredValues; const auto consideredValuesLimit = internalQueryMaxDocValidationErrorConsideredValues.load(); constexpr int kNumElements = 1000; for (int i = 0; i < kNumElements; i++) { valuesArray.append(i); if (i < consideredValuesLimit) { consideredValues.append(i); } } BSONObj document = BSON("a" << valuesArray.arr()); BSONObj expectedError = BSON("operatorName" << "$lt" << "specifiedAs" << query << "reason" << "comparison failed" << "consideredValues" << consideredValues.arr() << "consideredValuesTruncated" << true); verifyGeneratedError(query, document, expectedError); } TEST(DocValidationTruncationTest, ConsideredValuesArrayNotTruncatedWhenConfiguredLimitMatches) { BSONObj query = BSON("a" << BSON("$lt" << 0)); BSONArrayBuilder valuesArray; auto consideredValuesLimit = internalQueryMaxDocValidationErrorConsideredValues.load(); for (int i = 0; i < consideredValuesLimit; i++) { valuesArray.append(i); } BSONObj document = BSON("a" << valuesArray.arr()); BSONObj generatedError = generateValidationError(query, document, false /* shouldThrow */); // Should not report 'consideredValuesTruncated: true' ASSERT_FALSE(generatedError.hasField("consideredValuesTruncated")); } TEST(DocValidationTruncationTest, BasicSizeTruncation) { // Configure error generation to use a maximum error size of 10KB. This allows for us to test // truncation behavior without constructing massive errors. static int maxSize = 10 * 1024; // Target size to use for constructing large queries/documents. static int targetSize = 10 * 1024; // Constructs an array of numbers of the desired size in bytes. auto buildNumberArray = [](int sizeInBytes, int value) -> BSONArray { BSONArrayBuilder builder; while (builder.len() < sizeInBytes) { builder.append(value); ++value; } return builder.arr(); }; // $in predicate that is of size 'maxSize' gets omitted from error output. { BSONObj query = BSON("a" << BSON("$in" << buildNumberArray(targetSize, 1))); BSONObj doc = BSON("a" << 0); verifyTruncatedError( query, doc, maxSize, internalQueryMaxDocValidationErrorConsideredValues.load()); } // Document which contains an array of size 'maxSize' will result in 'consideredValues' field // being omitted. { auto numberArray = buildNumberArray(targetSize, 1); BSONObj query = fromjson("{a: {$in: [0]}}"); BSONObj doc = BSON("a" << numberArray); // We use the number of elements returned here to ensure that none of the values in the // generated array get truncated. verifyTruncatedError(query, doc, maxSize, numberArray.nFields()); } // Construct the $in predicate and the array of values such that each have a size in bytes of // 7 KB that is under the 10KB size limit, but their combined size exceeds the configured size // limit of 10KB. targetSize = 7 * 1024; { auto queryArray = buildNumberArray(targetSize, 1); BSONObj largeQuery = BSON("a" << BSON("$in" << queryArray)); auto docArray = buildNumberArray(targetSize, queryArray.nFields() + 1); BSONObj largeDoc = BSON("a" << docArray); // Both the query and the doc should be underneath 'maxSize' individually. ASSERT_LESS_THAN(largeQuery.objsize(), maxSize); ASSERT_LESS_THAN(largeDoc.objsize(), maxSize); // The combined size exceeds 'maxSize' and will result in a truncated error. verifyTruncatedError(largeQuery, largeDoc, maxSize, docArray.nFields()); } // The number of $eq predicates is large enough that we will not be able to generate a detailed // error as the size of the truncated error details will dwarf the 1 KB limit. maxSize = 1024; { const int numClauses = 1000; BSONObj doc = BSON("a" << 0); BSONArrayBuilder andArgument; for (auto i = 1; i < numClauses; ++i) { andArgument.append(BSON("a" << i)); } BSONObj query = BSON("$and" << andArgument.arr()); auto error = generateValidationError(query, doc, false /* shouldThrow */, maxSize, internalQueryMaxDocValidationErrorConsideredValues.load()); // Generated error should have a note field and a failingDocumentId field, but no details // field. auto note = error["note"]; ASSERT_EQ(note.type(), BSONType::String); ASSERT_EQ(note.valueStringData(), "detailed error was too large"); ASSERT_TRUE(error.hasField("failingDocumentId")); ASSERT_FALSE(error.hasField("details")); } } TEST(DocValidationTruncationTest, DocValidationReportsProgrammingError) { // An internal error thrown by the doc_validation_error module should be caught and handled // appropriately. FailPointEnableBlock fp("docValidationInternalErrorFailPoint"); BSONObj query = BSON("a" << 1); BSONObj doc = BSON("b" << 4); auto error = generateValidationError(query, doc, false /* shouldThrow */, kDefaultMaxDocValidationErrorSize, internalQueryMaxDocValidationErrorConsideredValues.load()); // There should be an error with 'note' and 'details' fields. auto note = error["note"]; ASSERT_EQ(note.type(), BSONType::String); ASSERT_EQ(note.valueStringData(), "failed to generate document validation error"); ASSERT_TRUE(error.hasField("failingDocumentId")); auto details = error["details"]; ASSERT_EQ(details.type(), BSONType::Object); auto code = details.embeddedObject()["code"]; ASSERT_EQ(code.type(), BSONType::NumberInt); ASSERT_EQ(code.numberInt(), 4944300); } } // namespace } // namespace mongo::doc_validation_error