mongo/jstests/aggregation/expressions/convert_string_to_object_ar...

98 lines
4.1 KiB
JavaScript

/**
* Tests behavior of conversions to string to array and object.
*
* @tags: [
* requires_fcv_83,
* ]
*/
import {runConvertTests} from "jstests/libs/query/convert_shared.js";
const coll = db.expression_convert_bindata;
coll.drop();
const requiresFCV83 = true;
function makeNestedArrayString(n) {
return ["[".repeat(n), '"1"', "]".repeat(n)].join("");
}
function makeLongArrayString(n) {
return ["[", '"1",'.repeat(n - 1), '"1"]'].join("");
}
function makeValidConversion(_id, input) {
const expected = JSON.parse(input);
const target = Array.isArray(expected) ? "array" : "object";
return {_id, input, target, expected};
}
const conversionTestDocs = [
// Simple cases.
makeValidConversion(0, "[]"),
makeValidConversion(1, '["bar"]'),
makeValidConversion(2, '{"foo": "bar"}'),
makeValidConversion(3, "[null]"),
makeValidConversion(4, "[false]"),
makeValidConversion(5, "[true]"),
makeValidConversion(6, "[-999]"),
makeValidConversion(7, '{"embedded-null": "her\\u0000e"}'),
makeValidConversion(8, '["nul\\u0000l"]'),
makeValidConversion(9, '[[[["nested", {"object":"here"}]]]]'),
makeValidConversion(10, '{"nested": [[["array"]]]}'),
// Very deep and wide (but not too deep or large) inputs.
makeValidConversion(11, `{"nested145": ${makeNestedArrayString(145)}, "another": ${makeNestedArrayString(145)}}`),
makeValidConversion(12, `{"long100K": ${makeLongArrayString(100_000)}}`),
// Numeric conversions.
makeValidConversion(13, '{"number": 1.2e+3}'),
makeValidConversion(14, '{"number": 18446744073709551615}'),
makeValidConversion(15, '{"number": -18446744073709551615}'),
// Non-ascii characters
makeValidConversion(16, '{"車B1234 こんにちは": "車B1234 こんにちは"}'),
// To protect against injections, ensure we don't attempt to parse/execute the resulting object
// as MQL.
makeValidConversion(17, '{"$toLong": ";;]"}'),
makeValidConversion(18, '{"$toString": "$missing"}'),
makeValidConversion(19, '["$missing", "$input"]'),
makeValidConversion(20, '{"$literal": 123}'),
makeValidConversion(21, '{"$toString": {}}'),
makeValidConversion(22, '{"$toString": []}'),
];
const illegalConversionTestDocs = [
// We hit the BSON depth limit.
{_id: 0, input: `{"nested200": ${makeNestedArrayString(200)}}`, target: "object"},
// We hit the BSON size limit (because BSON arrays are stored like {"0": val, "1": val}).
{_id: 1, input: `[${makeLongArrayString(1_200_000)}]`, target: "array"},
{_id: 2, input: `{"large": ${makeLongArrayString(1_200_000)}}`, target: "object"},
// Valid input but mismatched target type.
{_id: 3, input: "[{}]", target: "object"},
{_id: 4, input: '{"f": []}', target: "array"},
// Technically valid JSON but not a top-level object or array.
{_id: 5, input: '"str"', target: "object"},
{_id: 6, input: "123", target: "object"},
{_id: 7, input: "true", target: "array"},
{_id: 8, input: "null", target: "object"},
// Embedded null in key string.
{_id: 9, input: '{"f\\u0000oo": 1}', target: "object"},
// Unescaped embedded null anywhere.
{_id: 10, input: '{"foo": "b\\0ar"}', target: "object"},
{_id: 11, input: '{"f\\0oo": "bar"}', target: "object"},
{_id: 12, input: '{\\0"foo": "bar"}', target: "object"},
// Unsupported JSON-type syntax like trailing commas, jsonlines etc.
{_id: 13, input: '{"foo": 1}\n{"foo": 2}', target: "object"},
{_id: 14, input: '{"foo": 1}\n{"foo": 2}', target: "array"},
{_id: 15, input: '{"foo": 1,}', target: "object"},
{_id: 16, input: '["foo",1,]', target: "array"},
{_id: 17, input: '["foo",,]', target: "array"},
// MQL syntax, i.e. not valid JSON.
{_id: 18, input: "{$literal: 123}", target: "object"},
{_id: 19, input: "[{$literal: 123}]", target: "array"},
{_id: 20, input: "{$toString: {}}", target: "object"},
];
// One test document for each "nullish" value.
const nullTestDocs = [{_id: 0, input: null}, {_id: 1, input: undefined}, {_id: 2 /* input is missing */}];
runConvertTests({coll, requiresFCV83, conversionTestDocs, illegalConversionTestDocs, nullTestDocs});