diff --git a/src/binary.ts b/src/binary.ts index f87a94ec..3875601b 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -31,6 +31,10 @@ export class Binary { get _bsontype(): 'Binary' { return 'Binary'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } /** * Binary default subtype @@ -305,6 +309,11 @@ const UUID_BYTE_LENGTH = 16; * @public */ export class UUID extends Binary { + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } + static cacheHexString: boolean; /** UUID hexString cache @internal */ diff --git a/src/code.ts b/src/code.ts index 5f2f51a7..9c11dd4a 100644 --- a/src/code.ts +++ b/src/code.ts @@ -15,6 +15,10 @@ export class Code { get _bsontype(): 'Code' { return 'Code'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } code: string; diff --git a/src/db_ref.ts b/src/db_ref.ts index a8997890..d56b0edc 100644 --- a/src/db_ref.ts +++ b/src/db_ref.ts @@ -32,6 +32,10 @@ export class DBRef { get _bsontype(): 'DBRef' { return 'DBRef'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } collection!: string; oid!: ObjectId; diff --git a/src/decimal128.ts b/src/decimal128.ts index d705b4cc..0d20625a 100644 --- a/src/decimal128.ts +++ b/src/decimal128.ts @@ -130,6 +130,10 @@ export class Decimal128 { get _bsontype(): 'Decimal128' { return 'Decimal128'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } readonly bytes!: Uint8Array; diff --git a/src/double.ts b/src/double.ts index 7a4feb64..87ff8038 100644 --- a/src/double.ts +++ b/src/double.ts @@ -14,6 +14,10 @@ export class Double { get _bsontype(): 'Double' { return 'Double'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } value!: number; /** diff --git a/src/extended_json.ts b/src/extended_json.ts index f925630d..dcfef767 100644 --- a/src/extended_json.ts +++ b/src/extended_json.ts @@ -273,13 +273,9 @@ const BSON_TYPE_MAPPINGS = { ), MaxKey: () => new MaxKey(), MinKey: () => new MinKey(), - ObjectID: (o: ObjectId) => new ObjectId(o), - // The _bsontype for ObjectId is spelled with a capital "D", to the mapping above will be used (most of the time) - // specifically BSON versions 4.0.0 and 4.0.1 the _bsontype was changed to "ObjectId" so we keep this mapping to support - // those version of BSON ObjectId: (o: ObjectId) => new ObjectId(o), BSONRegExp: (o: BSONRegExp) => new BSONRegExp(o.pattern, o.options), - Symbol: (o: BSONSymbol) => new BSONSymbol(o.value), + BSONSymbol: (o: BSONSymbol) => new BSONSymbol(o.value), Timestamp: (o: Timestamp) => Timestamp.fromBits(o.low, o.high) } as const; @@ -310,6 +306,13 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) { } } return _doc; + } else if ( + doc != null && + typeof doc === 'object' && + typeof doc._bsontype === 'string' && + doc[Symbol.for('@@mdb.bson.version')] == null + ) { + throw new BSONError('Unsupported BSON version, bson types must be from bson 5.0 or later'); } else if (isBSONType(doc)) { // the "document" is really just a BSON type object // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/int_32.ts b/src/int_32.ts index 6160b718..dc7bbfa9 100644 --- a/src/int_32.ts +++ b/src/int_32.ts @@ -14,6 +14,10 @@ export class Int32 { get _bsontype(): 'Int32' { return 'Int32'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } value!: number; /** diff --git a/src/long.ts b/src/long.ts index 7d23aeea..cc8fa39c 100644 --- a/src/long.ts +++ b/src/long.ts @@ -102,6 +102,10 @@ export class Long { get _bsontype(): 'Long' { return 'Long'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } /** An indicator used to reliably determine if an object is a Long or not. */ get __isLong__(): boolean { diff --git a/src/max_key.ts b/src/max_key.ts index 5541a65a..f8ce78eb 100644 --- a/src/max_key.ts +++ b/src/max_key.ts @@ -12,6 +12,10 @@ export class MaxKey { get _bsontype(): 'MaxKey' { return 'MaxKey'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } /** @internal */ toExtendedJSON(): MaxKeyExtended { diff --git a/src/min_key.ts b/src/min_key.ts index 99181449..79d23e26 100644 --- a/src/min_key.ts +++ b/src/min_key.ts @@ -12,6 +12,10 @@ export class MinKey { get _bsontype(): 'MinKey' { return 'MinKey'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } /** @internal */ toExtendedJSON(): MinKeyExtended { diff --git a/src/objectid.ts b/src/objectid.ts index 6ec18a32..772e08e7 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -28,8 +28,12 @@ const kId = Symbol('id'); * @category BSONType */ export class ObjectId { - get _bsontype(): 'ObjectID' { - return 'ObjectID'; + get _bsontype(): 'ObjectId' { + return 'ObjectId'; + } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; } /** @internal */ diff --git a/src/parser/calculate_size.ts b/src/parser/calculate_size.ts index e96bf63e..73b6d898 100644 --- a/src/parser/calculate_size.ts +++ b/src/parser/calculate_size.ts @@ -79,7 +79,7 @@ function calculateElement( case 'object': if (value == null || value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + 1; - } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { + } else if (value['_bsontype'] === 'ObjectId') { return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (12 + 1); } else if (value instanceof Date || isDate(value)) { return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1); diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index b12bcedd..123983b3 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -684,13 +684,11 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value === null) { index = serializeNull(buffer, key, value, index); - } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { - index = serializeObjectId(buffer, key, value, index); } else if (isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); - } else if (typeof value === 'object' && value['_bsontype'] == null) { + } else if (typeof value === 'object' && value._bsontype == null) { index = serializeObject( buffer, key, @@ -702,15 +700,19 @@ export function serializeInto( ignoreUndefined, path ); - } else if (value['_bsontype'] === 'Decimal128') { + } else if (typeof value === 'object' && value[Symbol.for('@@mdb.bson.version')] == null) { + throw new BSONError('Unsupported BSON version, bson types must be from bson 5.0 or later'); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); + } else if (value._bsontype === 'Decimal128') { index = serializeDecimal128(buffer, key, value, index); - } else if (value['_bsontype'] === 'Long' || value['_bsontype'] === 'Timestamp') { + } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { index = serializeLong(buffer, key, value, index); - } else if (value['_bsontype'] === 'Double') { + } else if (value._bsontype === 'Double') { index = serializeDouble(buffer, key, value, index); } else if (typeof value === 'function' && serializeFunctions) { index = serializeFunction(buffer, key, value, index); - } else if (value['_bsontype'] === 'Code') { + } else if (value._bsontype === 'Code') { index = serializeCode( buffer, key, @@ -722,20 +724,20 @@ export function serializeInto( ignoreUndefined, path ); - } else if (value['_bsontype'] === 'Binary') { + } else if (value._bsontype === 'Binary') { index = serializeBinary(buffer, key, value, index); - } else if (value['_bsontype'] === 'Symbol') { + } else if (value._bsontype === 'Symbol') { index = serializeSymbol(buffer, key, value, index); - } else if (value['_bsontype'] === 'DBRef') { + } else if (value._bsontype === 'DBRef') { index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); - } else if (value['_bsontype'] === 'BSONRegExp') { + } else if (value._bsontype === 'BSONRegExp') { index = serializeBSONRegExp(buffer, key, value, index); - } else if (value['_bsontype'] === 'Int32') { + } else if (value._bsontype === 'Int32') { index = serializeInt32(buffer, key, value, index); - } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { + } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); - } else if (typeof value['_bsontype'] !== 'undefined') { - throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value['_bsontype'])}`); + } else if (typeof value._bsontype !== 'undefined') { + throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); } } } else if (object instanceof Map || isMap(object)) { @@ -785,13 +787,11 @@ export function serializeInto( index = serializeDate(buffer, key, value, index); } else if (value === null || (value === undefined && ignoreUndefined === false)) { index = serializeNull(buffer, key, value, index); - } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { - index = serializeObjectId(buffer, key, value, index); } else if (isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); - } else if (type === 'object' && value['_bsontype'] == null) { + } else if (type === 'object' && value._bsontype == null) { index = serializeObject( buffer, key, @@ -803,13 +803,17 @@ export function serializeInto( ignoreUndefined, path ); - } else if (type === 'object' && value['_bsontype'] === 'Decimal128') { + } else if (typeof value === 'object' && value[Symbol.for('@@mdb.bson.version')] == null) { + throw new BSONError('Unsupported BSON version, bson types must be from bson 5.0 or later'); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); + } else if (type === 'object' && value._bsontype === 'Decimal128') { index = serializeDecimal128(buffer, key, value, index); - } else if (value['_bsontype'] === 'Long' || value['_bsontype'] === 'Timestamp') { + } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { index = serializeLong(buffer, key, value, index); - } else if (value['_bsontype'] === 'Double') { + } else if (value._bsontype === 'Double') { index = serializeDouble(buffer, key, value, index); - } else if (value['_bsontype'] === 'Code') { + } else if (value._bsontype === 'Code') { index = serializeCode( buffer, key, @@ -823,20 +827,20 @@ export function serializeInto( ); } else if (typeof value === 'function' && serializeFunctions) { index = serializeFunction(buffer, key, value, index); - } else if (value['_bsontype'] === 'Binary') { + } else if (value._bsontype === 'Binary') { index = serializeBinary(buffer, key, value, index); - } else if (value['_bsontype'] === 'Symbol') { + } else if (value._bsontype === 'Symbol') { index = serializeSymbol(buffer, key, value, index); - } else if (value['_bsontype'] === 'DBRef') { + } else if (value._bsontype === 'DBRef') { index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); - } else if (value['_bsontype'] === 'BSONRegExp') { + } else if (value._bsontype === 'BSONRegExp') { index = serializeBSONRegExp(buffer, key, value, index); - } else if (value['_bsontype'] === 'Int32') { + } else if (value._bsontype === 'Int32') { index = serializeInt32(buffer, key, value, index); - } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { + } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); - } else if (typeof value['_bsontype'] !== 'undefined') { - throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value['_bsontype'])}`); + } else if (typeof value._bsontype !== 'undefined') { + throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); } } } else { @@ -890,13 +894,11 @@ export function serializeInto( if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index); } else if (value === null) { index = serializeNull(buffer, key, value, index); - } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { - index = serializeObjectId(buffer, key, value, index); } else if (isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); - } else if (type === 'object' && value['_bsontype'] == null) { + } else if (type === 'object' && value._bsontype == null) { index = serializeObject( buffer, key, @@ -908,13 +910,17 @@ export function serializeInto( ignoreUndefined, path ); - } else if (type === 'object' && value['_bsontype'] === 'Decimal128') { + } else if (typeof value === 'object' && value[Symbol.for('@@mdb.bson.version')] == null) { + throw new BSONError('Unsupported BSON version, bson types must be from bson 5.0 or later'); + } else if (value._bsontype === 'ObjectId') { + index = serializeObjectId(buffer, key, value, index); + } else if (type === 'object' && value._bsontype === 'Decimal128') { index = serializeDecimal128(buffer, key, value, index); - } else if (value['_bsontype'] === 'Long' || value['_bsontype'] === 'Timestamp') { + } else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { index = serializeLong(buffer, key, value, index); - } else if (value['_bsontype'] === 'Double') { + } else if (value._bsontype === 'Double') { index = serializeDouble(buffer, key, value, index); - } else if (value['_bsontype'] === 'Code') { + } else if (value._bsontype === 'Code') { index = serializeCode( buffer, key, @@ -928,20 +934,20 @@ export function serializeInto( ); } else if (typeof value === 'function' && serializeFunctions) { index = serializeFunction(buffer, key, value, index); - } else if (value['_bsontype'] === 'Binary') { + } else if (value._bsontype === 'Binary') { index = serializeBinary(buffer, key, value, index); - } else if (value['_bsontype'] === 'Symbol') { + } else if (value._bsontype === 'Symbol') { index = serializeSymbol(buffer, key, value, index); - } else if (value['_bsontype'] === 'DBRef') { + } else if (value._bsontype === 'DBRef') { index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); - } else if (value['_bsontype'] === 'BSONRegExp') { + } else if (value._bsontype === 'BSONRegExp') { index = serializeBSONRegExp(buffer, key, value, index); - } else if (value['_bsontype'] === 'Int32') { + } else if (value._bsontype === 'Int32') { index = serializeInt32(buffer, key, value, index); - } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { + } else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); - } else if (typeof value['_bsontype'] !== 'undefined') { - throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value['_bsontype'])}`); + } else if (typeof value._bsontype !== 'undefined') { + throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); } } } diff --git a/src/regexp.ts b/src/regexp.ts index 5c9a475b..6996c16f 100644 --- a/src/regexp.ts +++ b/src/regexp.ts @@ -28,6 +28,10 @@ export class BSONRegExp { get _bsontype(): 'BSONRegExp' { return 'BSONRegExp'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } pattern!: string; options!: string; @@ -100,4 +104,13 @@ export class BSONRegExp { } throw new BSONTypeError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`); } + + /** @internal */ + [Symbol.for('nodejs.util.inspect.custom')](): string { + return this.inspect(); + } + + inspect(): string { + return `new BSONRegExp(${JSON.stringify(this.pattern)}, ${JSON.stringify(this.options)})`; + } } diff --git a/src/symbol.ts b/src/symbol.ts index a45554b4..feba31e5 100644 --- a/src/symbol.ts +++ b/src/symbol.ts @@ -9,8 +9,12 @@ export interface BSONSymbolExtended { * @category BSONType */ export class BSONSymbol { - get _bsontype(): 'Symbol' { - return 'Symbol'; + get _bsontype(): 'BSONSymbol' { + return 'BSONSymbol'; + } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; } value!: string; diff --git a/src/timestamp.ts b/src/timestamp.ts index 78d7fc4c..1a160828 100644 --- a/src/timestamp.ts +++ b/src/timestamp.ts @@ -32,6 +32,10 @@ export class Timestamp extends LongWithoutOverridesClass { get _bsontype(): 'Timestamp' { return 'Timestamp'; } + /** @internal */ + get [Symbol.for('@@mdb.bson.version')](): 5 { + return 5; + } static readonly MAX_VALUE = Long.MAX_UNSIGNED_VALUE; diff --git a/test/node/bson_older_versions_tests.js b/test/node/bson_older_versions_tests.js deleted file mode 100644 index 3f459269..00000000 --- a/test/node/bson_older_versions_tests.js +++ /dev/null @@ -1,254 +0,0 @@ -'use strict'; - -const currentNodeBSON = require('../register-bson'); -const vm = require('vm'); -const fs = require('fs'); -const rimraf = require('rimraf'); -const cp = require('child_process'); -const { __isWeb__ } = require('../register-bson'); - -// node-fetch is an es-module -let fetch; - -/* - * This file tests that previous versions of BSON - * serialize and deserialize correctly in the most recent version of BSON, - * and that the different distributions (browser, Node.js, etc.) of the - * most recent version are mutually compatible as well. - * - * This is an unusual situation to run into as users should be using one BSON lib version - * but it does arise with sub deps etc. and we wish to protect against unexpected behavior - * - * If backwards compatibility breaks there should be clear warnings/failures - * rather than empty or zero-ed values. - */ -const OLD_VERSIONS = ['v1.1.5', 'v1.1.4']; -const getZipUrl = ver => `https://github.com/mongodb/js-bson/archive/${ver}.zip`; -const getImportPath = ver => `../../bson-${ver}/js-bson-${ver.substring(1)}`; - -function downloadZip(version, done) { - // downloads a zip of previous BSON version - fetch(getZipUrl(version)) - .then(r => { - return r.arrayBuffer(); - }) - .then(r => { - fs.writeFileSync(`bson-${version}.zip`, new Uint8Array(r)); - try { - // unzips the code, right now these test won't handle versions written in TS - cp.execSync(`unzip bson-${version}.zip -d bson-${version}`); - } catch (err) { - return done(err); - } - done(); - }); -} - -// TODO(NODE-4843): These tests are failing not because of an error with BSON but the bundling changes -// Need to be accounted for. We can make the fixes in the ticket focused on this cross compat testing -describe.skip('Mutual version and distribution compatibility', function () { - before(function () { - if (__isWeb__) this.skip(); - }); - - before(async () => { - fetch = await import('node-fetch').then(mod => mod.default); - }); - - OLD_VERSIONS.forEach(version => { - before(function (done) { - this.timeout(30000); // Downloading may take a few seconds. - if (Number(process.version.split('.')[0].substring(1)) < 8) { - // WHATWG fetch doesn't download correctly prior to node 8 - // but we should be safe by testing on node 8 + - return done(); - } - if (fs.existsSync(`bson-${version}.zip`)) { - fs.unlinkSync(`bson-${version}.zip`); - rimraf(`./bson-${version}`, err => { - if (err) done(err); - - // download old versions - downloadZip(version, done); - }); - } else { - // download old versions - downloadZip(version, done); - } - }); - - after(function (done) { - try { - fs.unlinkSync(`bson-${version}.zip`); - } catch (e) { - // ignore - } - rimraf(`./bson-${version}`, err => { - if (err) done(err); - done(); - }); - }); - }); - - // Node.js requires an .mjs filename extension for loading ES modules. - before(() => { - try { - fs.writeFileSync( - './bson.browser.esm.mjs', - fs.readFileSync(__dirname + '/../../dist/bson.browser.esm.js') - ); - fs.writeFileSync('./bson.esm.mjs', fs.readFileSync(__dirname + '/../../dist/bson.esm.js')); - } catch (e) { - // bundling fails in CI on Windows, no idea why, hence also the - // process.platform !== 'win32' check below - } - }); - - after(() => { - try { - fs.unlinkSync('./bson.browser.esm.mjs'); - fs.unlinkSync('./bson.esm.mjs'); - } catch (e) { - // ignore - } - }); - - const variants = OLD_VERSIONS.map(version => ({ - name: `legacy ${version}`, - load: () => { - const bson = require(getImportPath(version)); - bson.serialize = bson.prototype.serialize; - bson.deserialize = bson.prototype.deserialize; - return Promise.resolve(bson); - }, - legacy: true - })).concat([ - { - name: 'Node.js lib/bson', - load: () => Promise.resolve(currentNodeBSON) - }, - { - name: 'Browser ESM', - // eval because import is a syntax error in earlier Node.js versions - // that are still supported in CI - load: () => eval(`import("${__dirname}/../../bson.browser.esm.mjs")`), - usesBufferPolyfill: true - }, - { - name: 'Browser UMD', - load: () => Promise.resolve(require('../../dist/bson.browser.umd.js')), - usesBufferPolyfill: true - }, - { - name: 'Generic bundle', - load: () => { - const source = fs.readFileSync(__dirname + '/../../dist/bson.bundle.js', 'utf8'); - return Promise.resolve(vm.runInNewContext(`${source}; BSON`, { global, process })); - }, - usesBufferPolyfill: true - }, - { - name: 'Node.js ESM', - load: () => eval(`import("${__dirname}/../../bson.esm.mjs")`) - } - ]); - - const makeObjects = bson => [ - new bson.ObjectId('5f16b8bebe434dc98cdfc9ca'), - new bson.DBRef('a', new bson.ObjectId('5f16b8bebe434dc98cdfc9cb'), 'db'), - new bson.MinKey(), - new bson.MaxKey(), - // TODO(NODE-4843): Timestamp has a necessary incompatibility, two number ctor throws now -- new bson.Timestamp(1, 100), - new bson.Timestamp(new bson.Long(1, 100, true)), - new bson.Code('abc'), - bson.Decimal128.fromString('1'), - bson.Long.fromString('1'), - new bson.Binary(Buffer.from('abcäbc🎉'), 128), - new Date('2021-05-04T15:49:33.000Z'), - /match/ - ]; - - for (const from of variants) { - for (const to of variants) { - describe(`serializing objects from ${from.name} using ${to.name}`, () => { - let fromObjects; - let fromBSON; - let toBSON; - - before(function () { - // Load the from/to BSON versions asynchronously because e.g. ESM - // requires asynchronous loading. - return Promise.resolve() - .then(() => { - return from.load(); - }) - .then(loaded => { - fromBSON = loaded; - return to.load(); - }) - .then(loaded => { - toBSON = loaded; - }) - .then( - () => { - if (from.usesBufferPolyfill || to.usesBufferPolyfill) { - // TODO(NODE-3555): The buffer polyfill does not correctly identify ArrayBuffers, will be fixed by removing - return this.skip(); - } - fromObjects = makeObjects(fromBSON); - }, - err => { - if (+process.version.slice(1).split('.')[0] >= 12) { - throw err; // On Node.js 12+, all loading is expected to work. - } else { - this.skip(); // Otherwise, e.g. ESM can't be loaded, so just skip. - } - } - ); - }); - - it('serializes in a compatible way', function () { - for (const object of fromObjects) { - // If the object in question uses Buffers in its serialization, and - // its Buffer was created using the polyfill, and we're serializing - // using a legacy version that uses buf.copy(), then that fails - // because the Buffer polyfill's typechecking is buggy, so we - // skip these cases. - // This is tracked as https://jira.mongodb.org/browse/NODE-2848 - // and would be addressed by https://github.com/feross/buffer/pull/285 - // if that is merged at some point. - if (from.usesBufferPolyfill || to.usesBufferPolyfill) { - // TODO(NODE-3555): The buffer polyfill does not correctly identify ArrayBuffers, will be fixed by removing - return this.skip(); - } - if ( - from.usesBufferPolyfill && - to.legacy && - ['ObjectId', 'Decimal128', 'DBRef', 'Binary'].includes(object.constructor.name) - ) { - continue; - } - - try { - // Check that both BSON versions serialize to equal Buffers - expect(toBSON.serialize({ object })).to.deep.equal(fromBSON.serialize({ object })); - if (!from.legacy) { - // Check that serializing using one version and deserializing using - // the other gives back the original object. - const cloned = fromBSON.deserialize(toBSON.serialize({ object })).object; - expect(fromBSON.EJSON.serialize(cloned)).to.deep.equal( - fromBSON.EJSON.serialize(object) - ); - } - } catch (err) { - // If something fails, note the object type in the error message - // for easier debugging. - err.message += ` (${object.constructor.name})`; - throw err; - } - } - }); - }); - } - } -}); diff --git a/test/node/bson_test.js b/test/node/bson_test.js index b71c4d6e..c3423045 100644 --- a/test/node/bson_test.js +++ b/test/node/bson_test.js @@ -1806,85 +1806,6 @@ describe('BSON', function () { done(); }); - it('should serialize ObjectIds from old bson versions', function () { - // In versions 4.0.0 and 4.0.1, we used _bsontype="ObjectId" which broke - // backwards compatibility with mongodb-core and other code. It was reverted - // back to "ObjectID" (capital D) in later library versions. - // The test below ensures that all three versions of Object ID work OK: - // 1. The current version's class - // 2. A simulation of the class from library 4.0.0 - // 3. The class currently in use by mongodb (not tested in browser where mongodb is unavailable) - - // test the old ObjectID class (in mongodb-core 3.1) because MongoDB drivers still return it - function getOldBSON() { - try { - // do a dynamic resolve to avoid exception when running browser tests - const file = require.resolve('mongodb-core'); - const oldModule = require(file).BSON; - const funcs = new oldModule.BSON(); - oldModule.serialize = funcs.serialize; - oldModule.deserialize = funcs.deserialize; - return oldModule; - } catch (e) { - return BSON; // if mongo is unavailable, e.g. browser tests, just re-use new BSON - } - } - - const OldBSON = getOldBSON(); - const OldObjectID = OldBSON === BSON ? BSON.ObjectId : OldBSON.ObjectID; - - // create a wrapper simulating the old ObjectId class from v4.0.0 - class ObjectIdv400 { - constructor() { - this.oid = new ObjectId(); - } - get id() { - return this.oid.id; - } - toString() { - return this.oid.toString(); - } - } - Object.defineProperty(ObjectIdv400.prototype, '_bsontype', { value: 'ObjectId' }); - - // Array - const array = [new ObjectIdv400(), new OldObjectID(), new ObjectId()]; - const deserializedArrayAsMap = BSON.deserialize( - BSON.serialize(Object.fromEntries(array.entries())) - ); - const deserializedArray = Object.keys(deserializedArrayAsMap).map( - x => deserializedArrayAsMap[x] - ); - expect(deserializedArray.map(x => x.toString())).to.eql(array.map(x => x.toString())); - - // Map - const map = new Map(); - map.set('oldBsonType', new ObjectIdv400()); - map.set('reallyOldBsonType', new OldObjectID()); - map.set('newBsonType', new ObjectId()); - const deserializedMapAsObject = BSON.deserialize(BSON.serialize(map), { relaxed: false }); - const deserializedMap = new Map( - Object.keys(deserializedMapAsObject).map(k => [k, deserializedMapAsObject[k]]) - ); - - map.forEach((value, key) => { - expect(deserializedMap.has(key)).to.be.true; - const deserializedMapValue = deserializedMap.get(key); - expect(deserializedMapValue.toString()).to.equal(value.toString()); - }); - - // Object - const record = { - oldBsonType: new ObjectIdv400(), - reallyOldBsonType: new OldObjectID(), - newBsonType: new ObjectId() - }; - const deserializedObject = BSON.deserialize(BSON.serialize(record)); - expect(deserializedObject).to.have.keys(['oldBsonType', 'reallyOldBsonType', 'newBsonType']); - expect(record.oldBsonType.toString()).to.equal(deserializedObject.oldBsonType.toString()); - expect(record.newBsonType.toString()).to.equal(deserializedObject.newBsonType.toString()); - }); - it('should throw if invalid BSON types are input to BSON serializer', function () { const oid = new ObjectId('111111111111111111111111'); const badBsonType = Object.assign({}, oid, { _bsontype: 'bogus' }); diff --git a/test/node/bson_type_classes.test.ts b/test/node/bson_type_classes.test.ts new file mode 100644 index 00000000..ffe8af4f --- /dev/null +++ b/test/node/bson_type_classes.test.ts @@ -0,0 +1,68 @@ +import { expect } from 'chai'; +import { + Binary, + BSONRegExp, + BSONSymbol, + Code, + DBRef, + Decimal128, + Double, + Int32, + Long, + MaxKey, + MinKey, + ObjectId, + Timestamp, + UUID +} from '../register-bson'; + +const BSONTypeClasses = [ + Binary, + Code, + DBRef, + Decimal128, + Double, + Int32, + Long, + MinKey, + MaxKey, + ObjectId, + BSONRegExp, + BSONSymbol, + Timestamp, + UUID +]; + +describe('BSON Type classes common interfaces', () => { + for (const TypeClass of BSONTypeClasses) { + describe(TypeClass.name, () => { + if (TypeClass.name !== 'UUID') { + it(`defines a _bsontype property equal to its name`, () => + expect(TypeClass.prototype).to.have.property('_bsontype', TypeClass.name)); + } else { + it(`UUID inherits _bsontype from Binary`, () => + expect(Object.getPrototypeOf(TypeClass.prototype)).to.have.property( + '_bsontype', + 'Binary' + )); + } + + it(`defines a Symbol.for('@@mdb.bson.version') property equal to 5`, () => + expect(TypeClass.prototype).to.have.property(Symbol.for('@@mdb.bson.version'), 5)); + + it(`defines a static fromExtendedJSON() method`, () => + expect(TypeClass).to.have.property('fromExtendedJSON').that.is.a('function')); + + it(`defines a toExtendedJSON() method`, () => + expect(TypeClass.prototype).to.have.property('toExtendedJSON').that.is.a('function')); + + it(`defines an inspect() method`, () => + expect(TypeClass.prototype).to.have.property('inspect').that.is.a('function')); + + it(`defines a [Symbol.for('nodejs.util.inspect.custom')]() method`, () => + expect(TypeClass.prototype) + .to.have.property(Symbol.for('nodejs.util.inspect.custom')) + .that.is.a('function')); + }); + } +}); diff --git a/test/node/cross_compat.test.ts b/test/node/cross_compat.test.ts new file mode 100644 index 00000000..88d9c103 --- /dev/null +++ b/test/node/cross_compat.test.ts @@ -0,0 +1,83 @@ +import { expect } from 'chai'; +import { + BSON, + EJSON, + Binary, + BSONRegExp, + BSONSymbol, + Code, + DBRef, + Decimal128, + Double, + Int32, + Long, + MaxKey, + MinKey, + ObjectId, + Timestamp, + UUID +} from '../register-bson'; + +const BSONTypeClasses = { + Binary: () => { + return new Binary(Buffer.alloc(3)); + }, + Code: () => { + return new Code('function () {}'); + }, + DBRef: () => { + return new DBRef('test', new ObjectId('00'.repeat(12))); + }, + Decimal128: () => { + return new Decimal128('1.28'); + }, + Double: () => { + return new Double(1.28); + }, + Int32: () => { + return new Int32(1); + }, + Long: () => { + return Long.fromNumber(1); + }, + MinKey: () => { + return new MinKey(); + }, + MaxKey: () => { + return new MaxKey(); + }, + ObjectId: () => { + return new ObjectId('00'.repeat(12)); + }, + BSONRegExp: () => { + return new BSONRegExp('abc', 'i'); + }, + BSONSymbol: () => { + return new BSONSymbol('abc'); + }, + Timestamp: () => { + return new Timestamp({ i: 0, t: 1 }); + }, + UUID: () => { + return new UUID('74e65f2f-6fdb-4c56-8785-bddb8ad79ea2'); + } +}; + +describe('Prevent previous major versions from working with BSON v5 serialize and stringify', function () { + for (const [typeName, typeMaker] of Object.entries(BSONTypeClasses)) { + it(`serialize throws if ${typeName} is missing a version symbol`, () => { + const type = typeMaker(); + Object.defineProperty(type, Symbol.for('@@mdb.bson.version'), { value: null }); // set an own property that overrides the getter + expect(() => BSON.serialize({ type })).to.throw(/Unsupported BSON version/); + expect(() => BSON.serialize({ a: [type] })).to.throw(/Unsupported BSON version/); + expect(() => BSON.serialize(new Map([['type', type]]))).to.throw(/Unsupported BSON version/); + }); + + it(`stringify throws if ${typeName} is missing a version symbol`, () => { + const type = typeMaker(); + Object.defineProperty(type, Symbol.for('@@mdb.bson.version'), { value: null }); // set an own property that overrides the getter + expect(() => EJSON.stringify({ type })).to.throw(/Unsupported BSON version/); + expect(() => EJSON.stringify({ a: [type] })).to.throw(/Unsupported BSON version/); + }); + } +}); diff --git a/test/node/extended_json.test.ts b/test/node/extended_json.test.ts index 4a372088..89cc8881 100644 --- a/test/node/extended_json.test.ts +++ b/test/node/extended_json.test.ts @@ -14,36 +14,11 @@ const Int32 = BSON.Int32; const Long = BSON.Long; const MaxKey = BSON.MaxKey; const MinKey = BSON.MinKey; -const ObjectID = BSON.ObjectId; const ObjectId = BSON.ObjectId; const BSONRegExp = BSON.BSONRegExp; const BSONSymbol = BSON.BSONSymbol; const Timestamp = BSON.Timestamp; -// Several tests in this file can test interop between current library versions and library version 1.1.0, because -// between 1.1.0 and 4.0.0 there was a significant rewrite. To minimize maintenance issues of a hard dependency on -// the old version, these interop tests are inactive by default. To activate, edit the check:test script in package.json: -// "check:test": "npm i --no-save --force bson@1.1.0 && mocha ./test/node && npm uninstall --no-save --force bson@1.1.0" -// -function getOldBSON() { - try { - // do a dynamic resolve to avoid exception when running browser tests - const file = require.resolve('bson'); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const oldModule = require(file).BSON; - const funcs = new oldModule.BSON(); - oldModule.serialize = funcs.serialize; - oldModule.deserialize = funcs.deserialize; - return oldModule; - } catch (e) { - return BSON; // if old bson lib is unavailable, e.g. browser tests, just re-use new BSON - } -} - -const OldBSON = getOldBSON(); -const OldObjectID = OldBSON === BSON ? BSON.ObjectId : OldBSON.ObjectID; -const usingOldBSON = OldBSON !== BSON; - describe('Extended JSON', function () { let doc = {}; @@ -66,8 +41,6 @@ describe('Extended JSON', function () { maxKey: new MaxKey(), minKey: new MinKey(), objectId: ObjectId.createFromHexString('111111111111111111111111'), - objectID: ObjectID.createFromHexString('111111111111111111111111'), - oldObjectID: OldObjectID.createFromHexString('111111111111111111111111'), regexp: new BSONRegExp('hello world', 'i'), symbol: new BSONSymbol('symbol'), timestamp: Timestamp.fromNumber(1000), @@ -81,7 +54,7 @@ describe('Extended JSON', function () { it('should correctly extend an existing mongodb module', function () { // TODO(NODE-4377): doubleNumberIntFit should be a double not a $numberLong const json = - '{"_id":{"$numberInt":"100"},"gh":{"$numberInt":"1"},"binary":{"$binary":{"base64":"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==","subType":"00"}},"date":{"$date":{"$numberLong":"1488372056737"}},"code":{"$code":"function() {}","$scope":{"a":{"$numberInt":"1"}}},"dbRef":{"$ref":"tests","$id":{"$numberInt":"1"},"$db":"test"},"decimal":{"$numberDecimal":"100"},"double":{"$numberDouble":"10.1"},"int32":{"$numberInt":"10"},"long":{"$numberLong":"200"},"maxKey":{"$maxKey":1},"minKey":{"$minKey":1},"objectId":{"$oid":"111111111111111111111111"},"objectID":{"$oid":"111111111111111111111111"},"oldObjectID":{"$oid":"111111111111111111111111"},"regexp":{"$regularExpression":{"pattern":"hello world","options":"i"}},"symbol":{"$symbol":"symbol"},"timestamp":{"$timestamp":{"t":0,"i":1000}},"int32Number":{"$numberInt":"300"},"doubleNumber":{"$numberDouble":"200.2"},"longNumberIntFit":{"$numberLong":"7036874417766400"},"doubleNumberIntFit":{"$numberLong":"19007199250000000"}}'; + '{"_id":{"$numberInt":"100"},"gh":{"$numberInt":"1"},"binary":{"$binary":{"base64":"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==","subType":"00"}},"date":{"$date":{"$numberLong":"1488372056737"}},"code":{"$code":"function() {}","$scope":{"a":{"$numberInt":"1"}}},"dbRef":{"$ref":"tests","$id":{"$numberInt":"1"},"$db":"test"},"decimal":{"$numberDecimal":"100"},"double":{"$numberDouble":"10.1"},"int32":{"$numberInt":"10"},"long":{"$numberLong":"200"},"maxKey":{"$maxKey":1},"minKey":{"$minKey":1},"objectId":{"$oid":"111111111111111111111111"},"regexp":{"$regularExpression":{"pattern":"hello world","options":"i"}},"symbol":{"$symbol":"symbol"},"timestamp":{"$timestamp":{"t":0,"i":1000}},"int32Number":{"$numberInt":"300"},"doubleNumber":{"$numberDouble":"200.2"},"longNumberIntFit":{"$numberLong":"7036874417766400"},"doubleNumberIntFit":{"$numberLong":"19007199250000000"}}'; expect(json).to.equal(EJSON.stringify(doc, null, 0, { relaxed: false })); }); @@ -128,10 +101,6 @@ describe('Extended JSON', function () { it('should correctly serialize bson types when they are values', function () { let serialized = EJSON.stringify(new ObjectId('591801a468f9e7024b6235ea'), { relaxed: false }); expect(serialized).to.equal('{"$oid":"591801a468f9e7024b6235ea"}'); - serialized = EJSON.stringify(new ObjectID('591801a468f9e7024b6235ea'), { relaxed: false }); - expect(serialized).to.equal('{"$oid":"591801a468f9e7024b6235ea"}'); - serialized = EJSON.stringify(new OldObjectID('591801a468f9e7024b6235ea'), { relaxed: false }); - expect(serialized).to.equal('{"$oid":"591801a468f9e7024b6235ea"}'); serialized = EJSON.stringify(new Int32(42), { relaxed: false }); expect(serialized).to.equal('{"$numberInt":"42"}'); @@ -142,20 +111,6 @@ describe('Extended JSON', function () { { relaxed: false } ); expect(serialized).to.equal('{"_id":{"$nin":[{"$oid":"591801a468f9e7024b6235ea"}]}}'); - serialized = EJSON.stringify( - { - _id: { $nin: [new ObjectID('591801a468f9e7024b6235ea')] } - }, - { relaxed: false } - ); - expect(serialized).to.equal('{"_id":{"$nin":[{"$oid":"591801a468f9e7024b6235ea"}]}}'); - serialized = EJSON.stringify( - { - _id: { $nin: [new OldObjectID('591801a468f9e7024b6235ea')] } - }, - { relaxed: false } - ); - expect(serialized).to.equal('{"_id":{"$nin":[{"$oid":"591801a468f9e7024b6235ea"}]}}'); serialized = EJSON.stringify(new Binary(new Uint8Array([1, 2, 3, 4, 5])), { relaxed: false }); expect(serialized).to.equal('{"$binary":{"base64":"AQIDBAU=","subType":"00"}}'); @@ -237,8 +192,6 @@ describe('Extended JSON', function () { maxKey: new MaxKey(), minKey: new MinKey(), objectId: ObjectId.createFromHexString('111111111111111111111111'), - objectID: ObjectID.createFromHexString('111111111111111111111111'), - oldObjectID: OldObjectID.createFromHexString('111111111111111111111111'), bsonRegExp: new BSONRegExp('hello world', 'i'), symbol: new BSONSymbol('symbol'), timestamp: new Timestamp(), @@ -258,8 +211,6 @@ describe('Extended JSON', function () { maxKey: { $maxKey: 1 }, minKey: { $minKey: 1 }, objectId: { $oid: '111111111111111111111111' }, - objectID: { $oid: '111111111111111111111111' }, - oldObjectID: { $oid: '111111111111111111111111' }, bsonRegExp: { $regularExpression: { pattern: 'hello world', options: 'i' } }, symbol: { $symbol: 'symbol' }, timestamp: { $timestamp: { t: 0, i: 0 } }, @@ -280,8 +231,6 @@ describe('Extended JSON', function () { maxKey: { $maxKey: 1 }, minKey: { $minKey: 1 }, objectId: { $oid: '111111111111111111111111' }, - objectID: { $oid: '111111111111111111111111' }, - oldObjectID: { $oid: '111111111111111111111111' }, bsonRegExp: { $regularExpression: { pattern: 'hello world', options: 'i' } }, symbol: { $symbol: 'symbol' }, timestamp: { $timestamp: { t: 0, i: 0 } } @@ -314,8 +263,6 @@ describe('Extended JSON', function () { expect(result.minKey).to.be.an.instanceOf(BSON.MinKey); // objectID expect(result.objectId.toString()).to.equal('111111111111111111111111'); - expect(result.objectID.toString()).to.equal('111111111111111111111111'); - expect(result.oldObjectID.toString()).to.equal('111111111111111111111111'); //bsonRegExp expect(result.bsonRegExp).to.be.an.instanceOf(BSON.BSONRegExp); expect(result.bsonRegExp.pattern).to.equal('hello world'); @@ -352,159 +299,6 @@ describe('Extended JSON', function () { expect(serialized).to.equal('{"a":10}'); }); - if (!usingOldBSON) { - it.skip('skipping 4.x/1.x interop tests', () => { - // ignore - }); - } else { - it('should interoperate 4.x with 1.x versions of this library', function () { - const buffer = Buffer.alloc(64); - for (let i = 0; i < buffer.length; i++) { - buffer[i] = i; - } - const [oldBsonObject, newBsonObject] = [OldBSON, BSON].map(bsonModule => { - const bsonTypes = { - binary: new bsonModule.Binary(buffer), - code: new bsonModule.Code('function() {}'), - dbRef: new bsonModule.DBRef('tests', new Int32(1), 'test'), - decimal128: bsonModule.Decimal128.fromString('9991223372036854775807'), - double: new bsonModule.Double(10.1), - int32: new bsonModule.Int32(10), - long: bsonModule.Long.fromString('1223372036854775807'), - maxKey: new bsonModule.MaxKey(), - // minKey: new bsonModule.MinKey(), // broken until #310 is fixed in 1.x - objectId: bsonModule.ObjectId.createFromHexString('111111111111111111111111'), - objectID: bsonModule.ObjectId.createFromHexString('111111111111111111111111'), - bsonRegExp: new bsonModule.BSONRegExp('hello world', 'i'), - symbol: bsonModule.BSONSymbol - ? new bsonModule.BSONSymbol('symbol') - : new bsonModule.Symbol('symbol'), - timestamp: new bsonModule.Timestamp() - }; - return bsonTypes; - }); - - const serializationOptions = {}; - const bsonBuffers = { - oldObjectOldSerializer: OldBSON.serialize(oldBsonObject, serializationOptions), - oldObjectNewSerializer: BSON.serialize(oldBsonObject, serializationOptions), - newObjectOldSerializer: OldBSON.serialize(newBsonObject, serializationOptions), - newObjectNewSerializer: BSON.serialize(newBsonObject, serializationOptions) - }; - - const expectedBufferBase64 = - 'VgEAAAViaW5hcnkAQAAAAAAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/DWNvZGUADgAAAGZ1bmN0aW9uKCkge30AA2RiUmVmACwAAAACJHJlZgAGAAAAdGVzdHMAECRpZAABAAAAAiRkYgAFAAAAdGVzdAAAE2RlY2ltYWwxMjgA//837RjxE6AdAgAAAABAMAFkb3VibGUAMzMzMzMzJEAQaW50MzIACgAAABJsb25nAP//38RiSvoQf21heEtleQAHb2JqZWN0SWQAERERERERERERERERB29iamVjdElEABEREREREREREREREQtic29uUmVnRXhwAGhlbGxvIHdvcmxkAGkADnN5bWJvbAAHAAAAc3ltYm9sABF0aW1lc3RhbXAAAAAAAAAAAAAA'; - const expectedBuffer = Buffer.from(expectedBufferBase64, 'base64'); - - // Regardless of which library version created the objects, and which library version - // is being used to serialize the objects, validate that the correct BSON is returned. - expect(expectedBuffer).to.deep.equal(bsonBuffers.newObjectNewSerializer); - expect(expectedBuffer).to.deep.equal(bsonBuffers.newObjectOldSerializer); - expect(expectedBuffer).to.deep.equal(bsonBuffers.oldObjectNewSerializer); - expect(expectedBuffer).to.deep.equal(bsonBuffers.oldObjectOldSerializer); - - // Finally, validate that the BSON buffer above is correctly deserialized back to EJSON by the new library, - // regardless of which library version's deserializer is used. This is useful because the 1.x deserializer - // generates 1.x objects, while the 4.x serializer generates 4.x objects. The 4.x EJSON serializer should - // be able to handle both. - const deserializationOptions = { promoteValues: false }; - const deserialized = { - usingOldDeserializer: OldBSON.deserialize(expectedBuffer, deserializationOptions), - usingNewDeserializer: BSON.deserialize(expectedBuffer, deserializationOptions) - }; - // Apparently the Symbol BSON type was deprecated in V4. Symbols in BSON are deserialized as strings in V4 - // Therefore, for this type we know there will be a difference between the V1 library and the V4 library, - // so remove Symbol from the list of BSON types that are being compared. - // Browser tests currently don't handle BSON Symbol correctly, so only test this under Node where OldBSON !=== BSON module. - if (BSON !== OldBSON) { - expect(deserialized.usingOldDeserializer['symbol'].value).to.equal( - deserialized.usingNewDeserializer['symbol'] - ); - } - delete deserialized.usingOldDeserializer['symbol']; - delete deserialized.usingNewDeserializer['symbol']; - - const ejsonExpected = { - binary: { - $binary: { - base64: - 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==', - subType: '00' - } - }, - code: { $code: 'function() {}' }, - dbRef: { $ref: 'tests', $id: { $numberInt: '1' }, $db: 'test' }, - decimal128: { $numberDecimal: '9991223372036854775807' }, - double: { $numberDouble: '10.1' }, - int32: { $numberInt: '10' }, - long: { $numberLong: '1223372036854775807' }, - maxKey: { $maxKey: 1 }, - // minKey: { $minKey: 1 }, // broken until #310 is fixed in 1.x branch - objectId: { $oid: '111111111111111111111111' }, - objectID: { $oid: '111111111111111111111111' }, - bsonRegExp: { $regularExpression: { pattern: 'hello world', options: 'i' } }, - // symbol: { $symbol: 'symbol' }, // removed because this type is deprecated. See comment above. - timestamp: { $timestamp: { t: 0, i: 0 } } - }; - const ejsonSerializeOptions = { relaxed: false }; - const resultOld = EJSON.serialize(deserialized.usingOldDeserializer, ejsonSerializeOptions); - expect(resultOld).to.deep.equal(ejsonExpected); - const resultNew = EJSON.serialize(deserialized.usingNewDeserializer, ejsonSerializeOptions); - expect(resultNew).to.deep.equal(ejsonExpected); - }); - - // Must special-case the test for MinKey, because of #310. When #310 is fixed and is picked up - // by mongodb-core, then remove this test case and uncomment the MinKey checks in the test case above - it('should interop with MinKey 1.x and 4.x, except the case that #310 breaks', function () { - if (!usingOldBSON) { - it.skip('interop tests', () => { - // ignore - }); - return; - } - - const serializationOptions = {}; - const deserializationOptions = { promoteValues: false }; - - // when #310 is fixed and the fix makes it into mongodb-core. - const [oldMinKey, newMinKey] = [OldBSON, BSON].map(bsonModule => { - const bsonTypes = { - minKey: new bsonModule.MinKey() - }; - return bsonTypes; - }); - - const expectedBufferBase64MinKey = 'DQAAAP9taW5LZXkAAA=='; - const expectedBufferMinKey = Buffer.from(expectedBufferBase64MinKey, 'base64'); - - const bsonBuffersMinKey = { - oldObjectOldSerializer: OldBSON.serialize(oldMinKey, serializationOptions), - oldObjectNewSerializer: BSON.serialize(oldMinKey, serializationOptions), - newObjectOldSerializer: OldBSON.serialize(newMinKey, serializationOptions), - newObjectNewSerializer: BSON.serialize(newMinKey, serializationOptions) - }; - - expect(expectedBufferMinKey).to.deep.equal(bsonBuffersMinKey.newObjectNewSerializer); - // expect(expectedBufferMinKey).to.deep.equal(bsonBuffersMinKey.newObjectOldSerializer); // this is the case that's broken by #310 - expect(expectedBufferMinKey).to.deep.equal(bsonBuffersMinKey.oldObjectNewSerializer); - expect(expectedBufferMinKey).to.deep.equal(bsonBuffersMinKey.oldObjectOldSerializer); - - const ejsonExpected = { - minKey: { $minKey: 1 } - }; - - const deserialized = { - usingOldDeserializer: OldBSON.deserialize(expectedBufferMinKey, deserializationOptions), - usingNewDeserializer: BSON.deserialize(expectedBufferMinKey, deserializationOptions) - }; - const ejsonSerializeOptions = { relaxed: false }; - const resultOld = EJSON.serialize(deserialized.usingOldDeserializer, ejsonSerializeOptions); - expect(resultOld).to.deep.equal(ejsonExpected); - const resultNew = EJSON.serialize(deserialized.usingNewDeserializer, ejsonSerializeOptions); - expect(resultNew).to.deep.equal(ejsonExpected); - }); - } - it('should throw if invalid BSON types are input to EJSON serializer', function () { const oid = new ObjectId('111111111111111111111111'); const badBsonType = Object.assign({}, oid, { _bsontype: 'bogus' }); diff --git a/test/node/parser/serializer.test.ts b/test/node/parser/serializer.test.ts index 352fc538..26cfb282 100644 --- a/test/node/parser/serializer.test.ts +++ b/test/node/parser/serializer.test.ts @@ -44,9 +44,11 @@ describe('serialize()', () => { it('does not permit objects with a _bsontype string to be serialized at the root', () => { expect(() => BSON.serialize({ _bsontype: 'iLoveJavascript' })).to.throw(/BSON types cannot/); // a nested invalid _bsontype throws something different - expect(() => BSON.serialize({ a: { _bsontype: 'iLoveJavascript' } })).to.throw( - /invalid _bsontype/ - ); + expect(() => + BSON.serialize({ + a: { _bsontype: 'iLoveJavascript', [Symbol.for('@@mdb.bson.version')]: 5 } + }) + ).to.throw(/invalid _bsontype/); }); it('does permit objects with a _bsontype prop that is not a string', () => { diff --git a/test/node/type_identifier_tests.js b/test/node/type_identifier_tests.js index f23308d1..542bb921 100644 --- a/test/node/type_identifier_tests.js +++ b/test/node/type_identifier_tests.js @@ -18,12 +18,11 @@ const { } = require('../register-bson'); describe('_bsontype identifier', () => { - // The two out of the norm types: - it('should be equal to ObjectID for ObjectId', () => { - expect(ObjectId.prototype._bsontype).to.equal('ObjectID'); + it('should be equal to ObjectId for ObjectId', () => { + expect(ObjectId.prototype._bsontype).to.equal('ObjectId'); }); - it('should be equal to Symbol for BSONSymbol', () => { - expect(BSONSymbol.prototype._bsontype).to.equal('Symbol'); + it('should be equal to BSONSymbol for BSONSymbol', () => { + expect(BSONSymbol.prototype._bsontype).to.equal('BSONSymbol'); }); it('should be equal to Timestamp for Timestamp', () => { // TODO(NODE-2624): Make Timestamp hold its long value on a property rather than be a subclass diff --git a/test/types/bson.test-d.ts b/test/types/bson.test-d.ts index efa3e66d..448a97af 100644 --- a/test/types/bson.test-d.ts +++ b/test/types/bson.test-d.ts @@ -51,18 +51,14 @@ expectError(MinKey.prototype.toJSON); expectError(Long.prototype.toJSON); expectError(BSONRegExp.prototype.toJSON); -// ObjectID uses a capital "D", this does not relate to the class name, or export name, only the determination for serialization -expectType<'ObjectID'>(ObjectId.prototype._bsontype) -// BSONSymbol was renamed to not conflict with the global JS Symbol -// but its _bsontype is still 'Symbol' -expectType<'Symbol'>(BSONSymbol.prototype._bsontype) - // We hack TS to say that the prototype has _bsontype='Timestamp' // but it actually is _bsontype='Long', inside the Timestamp constructor // we override the property on the instance // TODO(NODE-2624): Make Timestamp hold its long value on a property rather than be a subclass expectType<'Timestamp'>(Timestamp.prototype._bsontype) +expectType<'ObjectId'>(ObjectId.prototype._bsontype) +expectType<'BSONSymbol'>(BSONSymbol.prototype._bsontype) expectType<'Binary'>(Binary.prototype._bsontype) expectType<'Code'>(Code.prototype._bsontype) expectType<'DBRef'>(DBRef.prototype._bsontype)