mirror of https://github.com/mongodb/mongo
163 lines
5.5 KiB
JavaScript
163 lines
5.5 KiB
JavaScript
/**
|
|
* Test validation of Timestamp objects that get returned from $function execution, whether they are
|
|
* returned directly or as a value in a document.
|
|
*
|
|
* The Timestamp constructor ensures that users cannot _create_ an invalid timestamp, but there is
|
|
* nothing stopping a user function from modifying the timestamp afterwards with invalid values.
|
|
*
|
|
* @tags: [
|
|
* requires_fcv_63,
|
|
* requires_scripting
|
|
* ]
|
|
*/
|
|
(function() {
|
|
"use strict";
|
|
|
|
/**
|
|
* Each test case executes a pipeline (created by the `testPipeline' function below) that mutates a
|
|
* Timestamp object according to the 'assignments' field and ensures that execution fails with the
|
|
* expected error code and error message.
|
|
*/
|
|
const testCases = [
|
|
{
|
|
assignments: {t: 20000000000},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp time"
|
|
},
|
|
{
|
|
assignments: {i: 20000000000},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp increment"
|
|
},
|
|
{
|
|
assignments: {t: -1},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp time"
|
|
},
|
|
{
|
|
assignments: {i: -1},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp increment"
|
|
},
|
|
{
|
|
assignments: {t: "str"},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp time"
|
|
},
|
|
{
|
|
assignments: {i: "str"},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp increment"
|
|
},
|
|
{
|
|
assignments: {t: {foo: "bar"}},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp time"
|
|
},
|
|
{
|
|
assignments: {i: {foo: "bar"}},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp increment"
|
|
},
|
|
{
|
|
assignments: {t: [2]},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp time"
|
|
},
|
|
{
|
|
assignments: {i: [2]},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp increment"
|
|
},
|
|
{
|
|
assignments: {t: "str1", i: "str2"},
|
|
expectedErrorCode: ErrorCodes.BadValue,
|
|
errorShouldContain: "Timestamp"
|
|
},
|
|
{
|
|
assignments: {t: "REMOVE"},
|
|
expectedErrorCode: 6900900,
|
|
errorShouldContain: "missing timestamp field"
|
|
},
|
|
{
|
|
assignments: {i: "REMOVE"},
|
|
expectedErrorCode: 6900901,
|
|
errorShouldContain: "missing increment field"
|
|
},
|
|
{
|
|
assignments: {i: "REMOVE", t: "REMOVE"},
|
|
expectedErrorCode: [6900900, 6900901],
|
|
errorShouldContain: "missing"
|
|
},
|
|
];
|
|
|
|
/**
|
|
* The test pipeline evaluates a $function expression on exactly one document, passing 'assignments'
|
|
* and 'embedInObject' as its arguments.
|
|
*
|
|
* The test function creates a valid Timestamp but then mutates it, bypassing any validation checks,
|
|
* and returns it, either as a scalar or as a value in a document, depending on the 'embedInObject'
|
|
* argument.
|
|
*
|
|
* Mutation follows the specification in the 'assignments' argument: each field-value pair (f, v) in
|
|
* 'assignments' is executed as a 'timestamp.f = v' assignemnt, except when v is the special
|
|
* "REMOVE" value, which deletes the field from the timestamp.
|
|
*/
|
|
function testPipeline(assignments, embedInObject) {
|
|
return [
|
|
{$documents: [{unvalidatedUserData: assignments, embedInObject: embedInObject}]},
|
|
{
|
|
$project: {
|
|
computedField: {
|
|
$function: {
|
|
body: function(assignments, embedInObject) {
|
|
let timestamp = Timestamp(1, 1);
|
|
|
|
for (let [field, value] of Object.entries(JSON.parse(assignments))) {
|
|
if (value !== "REMOVE") {
|
|
timestamp[field] = value;
|
|
} else {
|
|
delete timestamp[field];
|
|
}
|
|
}
|
|
|
|
return embedInObject ? {result: timestamp} : timestamp;
|
|
},
|
|
args: ["$unvalidatedUserData", "$embedInObject"],
|
|
lang: "js"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Execute each test case twice: once with 'embedInObject' set to false and once with it set to
|
|
* true.
|
|
*/
|
|
for (let {assignments, expectedErrorCode, errorShouldContain} of testCases) {
|
|
let error = assert.commandFailedWithCode(
|
|
db.runCommand(
|
|
{aggregate: 1, pipeline: testPipeline(tojson(assignments), false), cursor: {}}),
|
|
expectedErrorCode);
|
|
assert(error.errmsg.indexOf(errorShouldContain) >= 0, error);
|
|
|
|
error = assert.commandFailedWithCode(
|
|
db.runCommand(
|
|
{aggregate: 1, pipeline: testPipeline(tojson(assignments), true), cursor: {}}),
|
|
expectedErrorCode);
|
|
assert(error.errmsg.indexOf(errorShouldContain) >= 0, error);
|
|
}
|
|
|
|
/**
|
|
* Additionally, test a case where the function execution makes a legal modification to a
|
|
* Timestamp object, producing a valid timestamp.
|
|
*/
|
|
let result = db.aggregate(testPipeline(tojson({t: 123.0, i: 456.0}), false)).toArray();
|
|
assert.sameMembers(result, [{computedField: Timestamp(123, 456)}]);
|
|
|
|
result = db.aggregate(testPipeline(tojson({t: 123.0, i: 456.0}), true)).toArray();
|
|
assert.sameMembers(result, [{computedField: {result: Timestamp(123, 456)}}]);
|
|
}());
|