summaryrefslogtreecommitdiff
path: root/jstests/core/validate_js_timestamp.js
blob: bf641cfd851d938363244fd2955839ced0a48000 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
 * 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)}}]);
}());