@deepkit/bson
Version: 
Deepkit BSON parser
1,317 lines (1,058 loc) • 43.8 kB
text/typescript
import { expect, test } from '@jest/globals';
import 'reflect-metadata';
import { createBSONSizer, getBSONSerializer, getBSONSizer, getValueSize, hexToByte, uuidStringToByte } from '../src/bson-serialize';
import { ExtractClassType, f, getClassSchema, jsonSerializer, nodeBufferToArrayBuffer, t } from '@deepkit/type';
import bson from 'bson';
import { getBSONDecoder } from '../src/bson-jit-parser';
import { randomBytes } from 'crypto';
import { parseObject, ParserV2 } from '../src/bson-parser';
import { ObjectId } from '../src/model';
import { BSON_BINARY_SUBTYPE_BIGINT, BSONType } from '../src/utils';
const { Binary, calculateObjectSize, deserialize, Long, ObjectId: OfficialObjectId, serialize } = bson;
test('hexToByte', () => {
    expect(hexToByte('00')).toBe(0);
    expect(hexToByte('01')).toBe(1);
    expect(hexToByte('0f')).toBe(15);
    expect(hexToByte('10')).toBe(16);
    expect(hexToByte('ff')).toBe(255);
    expect(hexToByte('f0')).toBe(240);
    expect(hexToByte('50')).toBe(80);
    expect(hexToByte('7f')).toBe(127);
    expect(hexToByte('f00f', 1)).toBe(15);
    expect(hexToByte('f0ff', 1)).toBe(255);
    expect(hexToByte('f00001', 2)).toBe(1);
    expect(hexToByte('f8')).toBe(16 * 15 + 8);
    expect(hexToByte('41')).toBe(16 * 4 + 1);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 1)).toBe(16 * 15 + 8);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 4)).toBe(16 * 4 + 1);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 6)).toBe(16 * 4 + 4);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 7)).toBe(16 * 2 + 15);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 8)).toBe(16 * 11 + 7);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 10)).toBe(16 * 12 + 3);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 11)).toBe(16 * 10 + 1);
    expect(uuidStringToByte('bef8de96-41fe-442f-b70c-c3a150f8c96c', 15)).toBe(16 * 6 + 12);
});
test('basic string', () => {
    const object = { name: 'Peter' };
    const expectedSize =
            4 //size uint32
            + 1 // type (string)
            + 'name\0'.length
            + (
                4 //string size uint32
                + 'Peter'.length + 1 //string content + null
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        name: t.string,
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('basic number int', () => {
    const object = { position: 24 };
    const expectedSize =
            4 //size uint32
            + 1 // type (number)
            + 'position\0'.length
            + (
                4 //int uint32
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        position: t.number,
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('basic long', () => {
    const object = { position: 3364367088039355000n };
    const expectedSize =
            4 //size uint32
            + 1 // type (number)
            + 'position\0'.length
            + (
                4 //uint32 low bits
                + 4 //uint32 high bits
            )
            + 1 //object null
    ;
    const schema = t.schema({
        position: t.number,
    });
    const serializer = getBSONSerializer(schema);
    const deserializer = getBSONDecoder(schema);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(serializer(object).byteLength).toBe(expectedSize);
    const reParsed = getBSONDecoder(schema)(serializer(object));
    expect(reParsed.position).toBe(3364367088039355000n);
    expect(serializer({ position: 123456n })).toEqual(serialize({ position: Long.fromNumber(123456) }));
    expect(serializer({ position: -123456n })).toEqual(serialize({ position: Long.fromNumber(-123456) }));
    expect(serializer({ position: 3364367088039355000n })).toEqual(serialize({ position: Long.fromBigInt(3364367088039355000n) }));
    expect(serializer({ position: -3364367088039355000n })).toEqual(serialize({ position: Long.fromBigInt(-3364367088039355000n) }));
    expect(deserializer(serializer({ position: 3364367088039355000n }))).toEqual({ position: 3364367088039355000n });
    expect(deserializer(serializer({ position: -3364367088039355000n }))).toEqual({ position: -3364367088039355000n });
});
test('basic bigint', () => {
    const object = { position: 3364367088039355000n };
    const expectedSize =
            4 //size uint32
            + 1 // type (binary)
            + 'position\0'.length
            + (
                4 //binary size
                + 1 //binary type
                + 9 //binary content
            )
            + 1 //object null
    ;
    const schema = t.schema({
        position: t.bigint,
    });
    const serializer = getBSONSerializer(schema);
    const deserializer = getBSONDecoder(schema);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(serializer(object).byteLength).toBe(expectedSize);
    const reParsed = deserializer(serializer(object));
    expect(reParsed.position).toBe(3364367088039355000n);
    //this cases are valid when dynamic bigint serialization is activated
    // expect(serializer({ position: 123456n })).toEqual(serialize({ position: 123456 }));
    // expect(serializer({ position: -123456n })).toEqual(serialize({ position: -123456 }));
    // expect(serializer({ position: 3364367088039355000n })).toEqual(serialize({ position: Long.fromBigInt(3364367088039355000n) }));
    // expect(serializer({ position: -3364367088039355000n })).toEqual(serialize({ position: Long.fromBigInt(-3364367088039355000n) }));
    //
    // expect(serializer({ position: 9223372036854775807n })).toEqual(serialize({ position: Long.fromBigInt(9223372036854775807n) }));
    // expect(serializer({ position: -9223372036854775807n })).toEqual(serialize({ position: Long.fromBigInt(-9223372036854775807n) }));
    expect(deserializer(serializer({ position: 123456n }))).toEqual({ position: 123456n });
    expect(deserializer(serializer({ position: -123456n }))).toEqual({ position: -123456n });
    expect(deserializer(serializer({ position: 3364367088039355000n }))).toEqual({ position: 3364367088039355000n });
    expect(deserializer(serializer({ position: -3364367088039355000n }))).toEqual({ position: -3364367088039355000n });
    expect(deserializer(serializer({ position: 9223372036854775807n }))).toEqual({ position: 9223372036854775807n });
    expect(deserializer(serializer({ position: -9223372036854775807n }))).toEqual({ position: -9223372036854775807n });
    {
        const bson = serializer({ position: 9223372036854775810n }); //force binary format
        expect(bson).toEqual(Buffer.from([
            29, 0, 0, 0, //size
            BSONType.BINARY, //type long
            112, 111, 115, 105, 116, 105, 111, 110, 0, //position\n string
            9, 0, 0, 0, //binary size, int32
            BSON_BINARY_SUBTYPE_BIGINT, //binary type
            1, //signum
            128, 0, 0, 0, 0, 0, 0, 2, //binary data
            0, //object null
        ]));
    }
    {
        const bson = serializer({ position: -9223372036854775810n }); //force binary format
        expect(bson).toEqual(Buffer.from([
            29, 0, 0, 0, //size
            BSONType.BINARY, //type long
            112, 111, 115, 105, 116, 105, 111, 110, 0, //position\n string
            9, 0, 0, 0, //binary size, int32
            BSON_BINARY_SUBTYPE_BIGINT, //binary type
            255, //signum, 255 = -1
            128, 0, 0, 0, 0, 0, 0, 2, //binary data
            0, //object null
        ]));
    }
});
test('basic any bigint', () => {
    const object = { position: 3364367088039355000n };
    const expectedSize =
            4 //size uint32
            + 1 // type (binary)
            + 'position\0'.length
            + (
                4 //binary size
                + 1 //binary type
                + 9 //binary content
            )
            + 1 //object null
    ;
    const schema = t.schema({
        position: t.any,
    });
    const serializer = getBSONSerializer(schema);
    const deserializer = getBSONDecoder(schema);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(serializer(object).byteLength).toBe(expectedSize);
    const reParsed = getBSONDecoder(schema)(serializer(object));
    expect(reParsed.position).toBe(3364367088039355000n);
    expect(deserializer(serializer({ position: 123456n }))).toEqual({ position: 123456n });
    expect(deserializer(serializer({ position: -123456n }))).toEqual({ position: -123456n });
    expect(deserializer(serializer({ position: 3364367088039355000n }))).toEqual({ position: 3364367088039355000n });
    expect(deserializer(serializer({ position: -3364367088039355000n }))).toEqual({ position: -3364367088039355000n });
    expect(deserializer(serializer({ position: 9223372036854775807n }))).toEqual({ position: 9223372036854775807n });
    expect(deserializer(serializer({ position: -9223372036854775807n }))).toEqual({ position: -9223372036854775807n });
    {
        const bson = serializer({ position: 9223372036854775810n }); //force binary format
        expect(bson).toEqual(Buffer.from([
            29, 0, 0, 0, //size
            BSONType.BINARY, //type long
            112, 111, 115, 105, 116, 105, 111, 110, 0, //position\n string
            9, 0, 0, 0, //binary size, int32
            BSON_BINARY_SUBTYPE_BIGINT, //binary type
            1, //signum
            128, 0, 0, 0, 0, 0, 0, 2, //binary data
            0, //object null
        ]));
    }
    {
        const bson = serializer({ position: -9223372036854775810n }); //force binary format
        expect(bson).toEqual(Buffer.from([
            29, 0, 0, 0, //size
            BSONType.BINARY, //type long
            112, 111, 115, 105, 116, 105, 111, 110, 0, //position\n string
            9, 0, 0, 0, //binary size, int32
            BSON_BINARY_SUBTYPE_BIGINT, //binary type
            255, //signum, 255 = -1
            128, 0, 0, 0, 0, 0, 0, 2, //binary data
            0, //object null
        ]));
    }
});
test('basic long bigint', () => {
    const bla: { n: number, m: string }[] = [
        { n: 1, m: '1' },
        { n: 1 << 16, m: 'max uint 16' },
        { n: (1 << 16) + 100, m: 'max uint 16 + 100' },
        { n: 4294967296, m: 'max uint 32' },
        { n: 4294967296 - 100, m: 'max uint 32 - 100' },
        { n: 4294967296 - 1, m: 'max uint 32 - 1' },
        { n: 4294967296 + 100, m: 'max uint 32 + 100' },
        { n: 4294967296 + 1, m: 'max uint 32 + 1' },
        { n: 4294967296 * 10 + 1, m: 'max uint 32 * 10 + 1' },
        // {n: 9223372036854775807, m: 'max uint64'},
        // {n: 9223372036854775807 + 1, m: 'max uint64 - 1'},
        // {n: 9223372036854775807 - 1, m: 'max uint64 + 2'},
    ];
    for (const b of bla) {
        const long = Long.fromNumber(b.n);
        console.log(b.n, long.toNumber(), long, b.m);
    }
});
test('basic number double', () => {
    const object = { position: 149943944399 };
    const expectedSize =
            4 //size uint32
            + 1 // type (number)
            + 'position\0'.length
            + (
                8 //double, 64bit
            )
            + 1 //object null
    ;
    const expectedSizeNull =
            4 //size uint32
            + 1 // type (number)
            + 'position\0'.length
            + (
                0 //undefined
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    expect(calculateObjectSize({ position: null })).toBe(expectedSizeNull);
    expect(calculateObjectSize({ position: undefined })).toBe(5);
    const schema = t.schema({
        position: t.number,
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
    expect(getBSONSerializer(schema)({ position: null }).byteLength).toBe(expectedSizeNull);
    expect(getBSONSerializer(schema)({ position: undefined }).byteLength).toBe(expectedSizeNull); //explicitely annotataed undefined is included
    expect(getBSONSerializer(schema)({}).byteLength).toBe(5);
    expect(getBSONSerializer(schema)({ position: null }).byteLength).toEqual(expectedSizeNull);
    expect(getBSONSerializer(schema)({ position: undefined }).byteLength).toEqual(expectedSizeNull); //explicitely annotataed undefined is included
    expect(getBSONSerializer(schema)({}).byteLength).toEqual(5);
    expect(getBSONSerializer(schema)({ position: null })).toEqual(serialize({ position: null }));
    expect(getBSONSerializer(schema)({})).toEqual(serialize({}));
    expect(getBSONSerializer(schema)({})).toEqual(serialize({ position: undefined })); //official bson drops undefined values
});
test('basic boolean', () => {
    const object = { valid: true };
    const expectedSize =
            4 //size uint32
            + 1 // type (boolean)
            + 'valid\0'.length
            + (
                1 //boolean
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        valid: t.boolean,
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('basic date', () => {
    const object = { created: new Date };
    const expectedSize =
            4 //size uint32
            + 1 // type (date)
            + 'created\0'.length
            + (
                8 //date
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        created: t.date,
    });
    const serializer = getBSONSerializer(schema);
    const deserializer = getBSONDecoder(schema);
    // expect(serializer(object).byteLength).toBe(expectedSize);
    // expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    // expect(serializer(object)).toEqual(serialize(object));
    expect(serializer({created: new Date('2900-10-12T00:00:00.000Z') })).toEqual(serialize({ created: new Date('2900-10-12T00:00:00.000Z') }));
    expect(serializer({created: new Date('1900-10-12T00:00:00.000Z') })).toEqual(serialize({ created: new Date('1900-10-12T00:00:00.000Z') }));
    expect(serializer({created: new Date('1000-10-12T00:00:00.000Z') })).toEqual(serialize({ created: new Date('1000-10-12T00:00:00.000Z') }));
    expect(deserializer(serializer({created: new Date('2900-10-12T00:00:00.000Z') }))).toEqual({ created: new Date('2900-10-12T00:00:00.000Z') });
    expect(deserializer(serializer({created: new Date('1900-10-12T00:00:00.000Z') }))).toEqual({ created: new Date('1900-10-12T00:00:00.000Z') });
    expect(deserializer(serializer({created: new Date('1000-10-12T00:00:00.000Z') }))).toEqual({ created: new Date('1000-10-12T00:00:00.000Z') });
});
test('basic binary', () => {
    const object = { binary: new Uint16Array(32) };
    const expectedSize =
            4 //size uint32
            + 1 // type (date)
            + 'binary\0'.length
            + (
                4 //size of binary, uin32
                + 1 //sub type
                + 32 * 2 //size of data
            )
            + 1 //object null
    ;
    expect(new Uint16Array(32).byteLength).toBe(32 * 2);
    //this doesn't support typed arrays
    // expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        binary: t.type(Uint16Array),
    });
    expect(schema.getProperty('binary').type).toBe('Uint16Array');
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    //doesnt support typed arrays
    // expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
    expect(getBSONDecoder(schema)(getBSONSerializer(schema)(object))).toEqual(object);
});
test('basic arrayBuffer', () => {
    const arrayBuffer = new ArrayBuffer(5);
    const view = new Uint8Array(arrayBuffer);
    view[0] = 22;
    view[1] = 44;
    view[2] = 55;
    view[3] = 66;
    view[4] = 77;
    const object = { binary: arrayBuffer };
    const expectedSize =
            4 //size uint32
            + 1 // type (date)
            + 'binary\0'.length
            + (
                4 //size of binary, uin32
                + 1 //sub type
                + 5 //size of data
            )
            + 1 //object null
    ;
    // expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        binary: t.type(ArrayBuffer),
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONDecoder(schema)(getBSONSerializer(schema)(object))).toEqual(object);
    // expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('basic Buffer', () => {
    const object = { binary: new Uint8Array(32) };
    const expectedSize =
            4 //size uint32
            + 1 // type (date)
            + 'binary\0'.length
            + (
                4 //size of binary, uin32
                + 1 //sub type
                + 32 //size of data
            )
            + 1 //object null
    ;
    // expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        binary: t.type(Uint8Array),
    });
    expect(schema.getProperty('binary').type).toBe('Uint8Array');
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONDecoder(schema)(getBSONSerializer(schema)(object))).toEqual(object);
    Buffer.alloc(2);
    Buffer.alloc(200);
    Buffer.alloc(20000);
    expect(getBSONDecoder(schema)(getBSONSerializer(schema)({
        binary: Buffer.alloc(44)
    }))).toEqual({
        binary: new Uint8Array(44)
    });
});
test('basic uuid', () => {
    const uuidRandomBinary = new Binary(
        Buffer.allocUnsafe(16),
        Binary.SUBTYPE_UUID
    );
    const object = { uuid: '75ed2328-89f2-4b89-9c49-1498891d616d' };
    const expectedSize =
            4 //size uint32
            + 1 // type (date)
            + 'uuid\0'.length
            + (
                4 //size of binary
                + 1 //sub type
                + 16 //content of uuid
            )
            + 1 //object null
    ;
    expect(calculateObjectSize({ uuid: uuidRandomBinary })).toBe(expectedSize);
    const schema = t.schema({
        uuid: t.uuid,
    });
    expect(getBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    const uuidPlain = Buffer.from([0x75, 0xed, 0x23, 0x28, 0x89, 0xf2, 0x4b, 0x89, 0x9c, 0x49, 0x14, 0x98, 0x89, 0x1d, 0x61, 0x6d]);
    const uuidBinary = new Binary(uuidPlain, 4);
    const objectBinary = {
        uuid: uuidBinary
    };
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(objectBinary));
    const bson = serialize(objectBinary);
    const parsed = parseObject(new ParserV2(bson));
    expect(parsed.uuid).toBe('75ed2328-89f2-4b89-9c49-1498891d616d');
});
test('basic objectId', () => {
    const object = { _id: '507f191e810c19729de860ea' };
    const expectedSize =
            4 //size uint32
            + 1 // type
            + '_id\0'.length
            + (
                12 //size of objectId
            )
            + 1 //object null
    ;
    const nativeBson = { _id: new OfficialObjectId('507f191e810c19729de860ea') };
    expect(calculateObjectSize(nativeBson)).toBe(expectedSize);
    const schema = t.schema({
        _id: t.mongoId,
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(nativeBson));
});
test('basic nested', () => {
    const object = { name: { anotherOne: 'Peter2' } };
    const expectedSize =
            4 //size uint32
            + 1 //type (object)
            + 'name\0'.length
            + (
                4 //size uint32
                + 1 //type (object)
                + 'anotherOne\0'.length
                + (
                    4 //string size uint32
                    + 'Peter2'.length + 1 //string content + null
                )
                + 1 //object null
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        name: {
            anotherOne: t.string,
        },
    });
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('basic map', () => {
    const object = { name: { anotherOne: 'Peter2' } };
    const expectedSize =
            4 //size uint32
            + 1 //type (object)
            + 'name\0'.length
            + (
                4 //size uint32
                + 1 //type (object)
                + 'anotherOne\0'.length
                + (
                    4 //string size uint32
                    + 'Peter2'.length + 1 //string content + null
                )
                + 1 //object null
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        name: t.map(t.string)
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('basic array', () => {
    const object = { name: ['Peter3'] };
    const expectedSize =
            4 //size uint32
            + 1 //type (array)
            + 'name\0'.length
            + (
                4 //size uint32 of array
                + 1 //type (string)
                + '0\0'.length //key
                + (
                    4 //string size uint32
                    + 'Peter3'.length + 1 //string content + null
                )
                + 1 //object null
            )
            + 1 //object null
    ;
    expect(calculateObjectSize(object)).toBe(expectedSize);
    const schema = t.schema({
        name: t.array(t.string),
    });
    expect(getBSONSerializer(schema)(object).byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('number', () => {
    const object = { name: 'Peter4', tags: ['a', 'b', 'c'], priority: 15, position: 149943944399, valid: true, created: new Date() };
    const schema = t.schema({
        name: t.string,
        tags: t.array(t.string),
        priority: t.number,
        position: t.number,
        valid: t.boolean,
        created: t.date,
    });
    expect(createBSONSizer(schema)(object)).toBe(calculateObjectSize(object));
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('all supported types', () => {
    const object = { name: 'Peter4', tags: ['a', 'b', 'c'], priority: 15, position: 149943944399, valid: true, created: new Date() };
    const schema = t.schema({
        name: t.string,
        tags: t.array(t.string),
        priority: t.number,
        position: t.number,
        valid: t.boolean,
        created: t.date,
    });
    expect(createBSONSizer(schema)(object)).toBe(calculateObjectSize(object));
    expect(getBSONSerializer(schema)(object)).toEqual(serialize(object));
});
test('string utf8', () => {
    const schema = t.schema({
        name: t.string,
        any: t.any,
    });
    const serialize = getBSONSerializer(schema);
    const parse = getBSONDecoder(schema);
    expect(parse(serialize({ name: 'Peter' }))).toEqual({ name: 'Peter' });
    expect(parse(serialize({ name: 'Peter✌️' }))).toEqual({ name: 'Peter✌️' });
    expect(parse(serialize({ name: '✌️' }))).toEqual({ name: '✌️' });
    expect(parse(serialize({ name: '🌉' }))).toEqual({ name: '🌉' });
    expect(parse(serialize({ name: 'πøˆ️' }))).toEqual({ name: 'πøˆ️' });
    expect(parse(serialize({ name: 'Ѓ' }))).toEqual({ name: 'Ѓ' });
    expect(parse(serialize({ name: '㒨' }))).toEqual({ name: '㒨' });
    expect(parse(serialize({ name: '﨣' }))).toEqual({ name: '﨣' });
    expect(parse(serialize({ any: { base: true } }))).toEqual({ any: { base: true } });
    expect(parse(serialize({ any: { '✌️': true } }))).toEqual({ any: { '✌️': true } });
    expect(parse(serialize({ any: { 'Ѓ': true } }))).toEqual({ any: { 'Ѓ': true } });
    expect(parse(serialize({ any: { 㒨: true } }))).toEqual({ any: { 㒨: true } });
    expect(parse(serialize({ any: { 﨣: true } }))).toEqual({ any: { 﨣: true } });
});
test('optional field', () => {
    const findSchema = t.schema({
        find: t.string,
        batchSize: t.number,
        limit: t.number.optional,
        skip: t.number.optional,
    });
    const findSerializer = getBSONSerializer(findSchema);
    const bson = findSerializer({
        find: 'user',
        batchSize: 1,
        limit: 1,
    });
    const bsonOfficial = serialize({
        find: 'user',
        batchSize: 1,
        limit: 1,
    });
    expect(bson).toEqual(bsonOfficial);
});
test('complex', () => {
    const findSchema = t.schema({
        find: t.string,
        batchSize: t.number,
        limit: t.number.optional,
        filter: t.any,
        projection: t.any,
        sort: t.any,
        skip: t.number.optional,
    });
    const findSerializer = getBSONSerializer(findSchema);
    const bson = findSerializer({
        find: 'user',
        batchSize: 1,
        limit: 1,
    });
    const bsonOfficial = serialize({
        find: 'user',
        batchSize: 1,
        limit: 1,
    });
    expect(bson).toEqual(bsonOfficial);
});
test('any objectId', () => {
    const schema = t.schema({
        _id: t.any,
        q: t.any,
    });
    {
        const doc = { _id: new ObjectId('507f191e810c19729de860ea') };
        const officialDoc = { _id: new OfficialObjectId('507f191e810c19729de860ea') };
        const bson = getBSONSerializer(schema)(doc);
        const bsonOfficial = serialize(officialDoc);
        expect(bson).toEqual(bsonOfficial);
        const parsed = deserialize(Buffer.from(bson));
        expect(parsed._id).toBeInstanceOf(OfficialObjectId);
        expect(parsed._id.toHexString()).toBe('507f191e810c19729de860ea');
        const parsed2 = getBSONDecoder(schema)(bson);
        expect(parsed2._id).toBe('507f191e810c19729de860ea');
    }
    {
        const doc = { q: { id: new ObjectId('507f191e810c19729de860ea') } };
        const officialDoc = { q: { id: new OfficialObjectId('507f191e810c19729de860ea') } };
        const bson = getBSONSerializer(schema)(doc);
        const bsonOfficial = serialize(officialDoc);
        expect(bson).toEqual(bsonOfficial);
        const parsed = deserialize(Buffer.from(bson));
        expect(parsed.q.id).toBeInstanceOf(OfficialObjectId);
        expect(parsed.q.id.toHexString()).toBe('507f191e810c19729de860ea');
        const parsed2 = getBSONDecoder(schema)(bson);
        expect(parsed2.q.id).toBe('507f191e810c19729de860ea');
    }
});
test('objectId string', () => {
    const schema = t.schema({
        id: t.mongoId,
    });
    {
        const doc = { id: '507f191e810c19729de860ea' };
        const bson = getBSONSerializer(schema)(doc);
        const bsonOfficial = serialize({ id: new OfficialObjectId('507f191e810c19729de860ea') });
        expect(bson).toEqual(bsonOfficial);
        const parsed = deserialize(Buffer.from(bson));
        expect(parsed.id).toBeInstanceOf(OfficialObjectId);
        expect(parsed.id.toHexString()).toBe('507f191e810c19729de860ea');
        const parsed2 = getBSONDecoder(schema)(bson);
        expect(parsed2.id).toBe('507f191e810c19729de860ea');
    }
});
test('model 1, missing `public`', () => {
    class User {
         ready?: boolean;
        .array(f.string) tags: string[] = [];
         priority: number = 0;
        constructor(
            .primary id: number,
             public name: string
        ) {
        }
    }
    const schema = getClassSchema(User);
    expect(schema.getMethodProperties('constructor').length).toBe(2);
    expect(schema.getPropertiesMap().size).toBe(5);
    {
        const user = new User(1, 'Peter ' + 1);
        user.ready = true;
        user.priority = 5;
        user.tags = ['a', 'b', 'c'];
        const bson = getBSONSerializer(User)(user);
        const size = getBSONSizer(User)(user);
        expect(size).toBe(calculateObjectSize(user));
        const s = getBSONDecoder(User);
        const o = s(bson);
        expect(o).toEqual(deserialize(Buffer.from(bson)));
    }
    {
        const user = {
            ready: true,
            priority: 5,
            tags: ['a', 'b', 'c'],
            id: null,
            name: 'Peter 1',
        };
        const bson = getBSONSerializer(User)(user);
        const s = getBSONDecoder(User);
        const o = s(bson);
        expect(o).not.toEqual(deserialize(Buffer.from(bson))); //because bson-js includes `id`, but we drop it since it's not assigned in the constructor
    }
});
test('decorated', () => {
    class DecoratedValue {
        constructor(
            .array(t.string).decorated
            public items: string[] = []
        ) {
        }
    }
    const object = { v: new DecoratedValue(['Peter3']) };
    const expectedSize =
            4 //size uint32
            + 1 //type (array)
            + 'v\0'.length
            + (
                4 //size uint32 of array
                + 1 //type (string)
                + '0\0'.length //key
                + (
                    4 //string size uint32
                    + 'Peter3'.length + 1 //string content + null
                )
                + 1 //object null
            )
            + 1 //object null
    ;
    expect(calculateObjectSize({ v: ['Peter3'] })).toBe(expectedSize);
    const schema = t.schema({
        v: t.type(DecoratedValue),
    });
    const bson = getBSONSerializer(schema)(object);
    const officialDeserialize = deserialize(Buffer.from(bson));
    console.log('officialDeserialize', officialDeserialize);
    expect(officialDeserialize.v).toEqual(['Peter3']);
    expect(bson.byteLength).toBe(expectedSize);
    expect(createBSONSizer(schema)(object)).toBe(expectedSize);
    expect(bson).toEqual(serialize({ v: ['Peter3'] }));
    const back = getBSONDecoder(schema)(bson);
    expect(back.v).toBeInstanceOf(DecoratedValue);
    expect(back.v.items).toEqual(['Peter3']);
    expect(back).toEqual(object);
});
test('reference', () => {
    class User {
        .primary id: number = 1;
        .array(User).backReference()
        managedUsers: User[] = [];
         name!: string;
        //self reference
        .optional.reference()
        manager?: User;
    }
    {
        const object = new User();
        object.name = 'Peter';
        (object as any).manager = null;
        const bson = getBSONSerializer(User)(object);
        const json = deserialize(bson);
        expect('manager' in json).toBe(true); //needs to be maintained in BSON since manager is optional. Only way to reset it.
        expect(json.manager).toBe(null); //needs to be maintained in BSON since manager is optional. Only way to reset it.
        const trip = getBSONDecoder(User)(bson);
        expect(trip.manager).toBe(undefined);
        expect('manager' in trip).toBe(false); //not part of the object since undefined/null
    }
    const updateSchema = t.schema({
        update: t.string,
        $db: t.string,
        updates: t.array({
            q: t.any,
            u: t.any,
            multi: t.boolean,
        })
    });
    {
        const object = {
            update: 'Nix',
            $db: 'admin',
            updates: [{
                q: { id: 213 },
                u: {
                    manager: null
                }
            }]
        };
        expect(getBSONSizer(updateSchema)(object)).toBe(calculateObjectSize(object));
        const bson = getBSONSerializer(updateSchema)(object);
        expect(getBSONDecoder(updateSchema)(bson)).toEqual(deserialize(Buffer.from(bson)));
    }
});
test('bson length', () => {
    const nonce = randomBytes(24);
    class SaslStartCommand extends t.class({
        saslStart: t.literal(1),
        $db: t.string,
        mechanism: t.string,
        payload: t.type(Uint8Array),
        autoAuthorize: t.literal(1),
        options: {
            skipEmptyExchange: t.literal(true)
        }
    }) {
    }
    const message = {
        saslStart: 1,
        '$db': 'admin',
        mechanism: 'SCRAM-SHA-1',
        payload: Buffer.concat([Buffer.from('n,,', 'utf8'), Buffer.from(`n=Peter,r=${nonce.toString('base64')}`, 'utf8')]),
        autoAuthorize: 1,
        options: { skipEmptyExchange: true }
    };
    expect(message.payload.byteLength).toBe(13 + nonce.toString('base64').length);
    const size = getBSONSizer(SaslStartCommand)(message);
    expect(size).toBe(calculateObjectSize(message));
    const bson = getBSONSerializer(SaslStartCommand)(message);
    expect(bson).toEqual(serialize(message));
});
test('arrayBuffer', () => {
    const schema = t.schema({
        name: t.string,
        secondId: t.mongoId,
        preview: t.type(ArrayBuffer),
    });
    const message = jsonSerializer.for(schema).deserialize({
        name: 'myName',
        secondId: '5bf4a1ccce060e0b38864c9e',
        preview: nodeBufferToArrayBuffer(Buffer.from('Baar', 'utf8'))
    });
    expect(Buffer.from(message.preview).toString('utf8')).toBe('Baar');
    const mongoMessage = {
        name: message.name,
        secondId: new OfficialObjectId(message.secondId),
        preview: new Binary(Buffer.from(message.preview)),
    };
    const size = getBSONSizer(schema)(message);
    expect(size).toBe(calculateObjectSize(mongoMessage));
    const bson = getBSONSerializer(schema)(message);
    expect(bson).toEqual(serialize(mongoMessage));
    const back = getBSONDecoder(schema)(bson);
    expect(Buffer.from(back.preview).toString('utf8')).toBe('Baar');
    expect(back.preview).toEqual(message.preview);
});
test('typed array', () => {
    const schema = t.schema({
        name: t.string,
        secondId: t.mongoId,
        preview: t.type(Uint16Array),
    });
    const message = jsonSerializer.for(schema).deserialize({
        name: 'myName',
        secondId: '5bf4a1ccce060e0b38864c9e',
        preview: new Uint16Array(nodeBufferToArrayBuffer(Buffer.from('LAA3AEIATQBYAA==', 'base64'))), //44, 55, 66, 77, 88
    });
    expect(message.preview).toBeInstanceOf(Uint16Array);
    expect(message.preview.byteLength).toBe(10);
    const mongoMessage = {
        name: message.name,
        secondId: new OfficialObjectId(message.secondId),
        preview: new Binary(Buffer.from(new Uint8Array(message.preview.buffer, message.preview.byteOffset, message.preview.byteLength))),
    };
    const size = getBSONSizer(schema)(message);
    expect(size).toBe(calculateObjectSize(mongoMessage));
    const bson = getBSONSerializer(schema)(message);
    expect(bson).toEqual(serialize(mongoMessage));
    const back = getBSONDecoder(schema)(bson);
    expect(back.preview).toEqual(message.preview);
});
test('typed any and undefined', () => {
    const schema = t.schema({
        data: t.any,
    });
    const message = jsonSerializer.for(schema).deserialize({
        data: {
            $set: {},
            $inc: undefined,
        },
    });
    // expect(getValueSize({ $inc: undefined })).toBe(calculateObjectSize({ $inc: undefined })); //official BSON does not include undefined values, but we do
    expect(getValueSize({ $inc: [undefined] })).toBe(calculateObjectSize({ $inc: [undefined] }));
    // const size = getBSONSizer(schema)(message);
    // expect(size).toBe(calculateObjectSize(message)); //official bson doesnt include undefined
    const bson = getBSONSerializer(schema)(message);
    // expect(bson).toEqual(serialize(message)); //official bson doesnt include undefined
    const back = getBSONDecoder(schema)(bson);
    expect(back.data.$set).toEqual({});
    expect(back.data.$inc).toEqual(undefined);
    expect('$inc' in back.data).toEqual(true);
});
test('test map map', () => {
    const schema = t.schema({
        data: t.map(t.map(t.string)),
    });
    const message = jsonSerializer.for(schema).deserialize({
        data: { foo: { bar: 'abc' } },
    });
    const size = getBSONSizer(schema)(message);
    expect(size).toBe(calculateObjectSize(message));
    const bson = getBSONSerializer(schema)(message);
    expect(bson).toEqual(serialize(message));
    expect(getBSONDecoder(schema)(bson)).toEqual(message);
});
test('test array array', () => {
    const schema = t.schema({
        data: t.array(t.array(t.string)),
    });
    const message = jsonSerializer.for(schema).deserialize({
        data: [['abc']],
    });
    const size = getBSONSizer(schema)(message);
    expect(size).toBe(calculateObjectSize(message));
    const bson = getBSONSerializer(schema)(message);
    expect(bson).toEqual(serialize(message));
    expect(getBSONDecoder(schema)(bson)).toEqual(message);
});
test('test array optional', () => {
    const schema = t.schema({
        data: t.array(t.date.optional),
    });
    {
        const message = { data: [new Date, undefined] };
        const size = getBSONSizer(schema)(message);
        expect(size).toBe(calculateObjectSize(message));
    }
});
test('test map optional 1', () => {
    const schema = t.schema({
        data: t.map(t.date.optional),
    });
    {
        const message = { data: { first: new Date, second: undefined } };
        // const size = getBSONSizer(schema)(message); //we maintain undefined as null, in contrary to BSON official
        // expect(size).toBe(calculateObjectSize(message));
        expect(getBSONDecoder(schema)(getBSONSerializer(schema)(message))).toEqual(message);
        // expect(getBSONSerializer(schema)(message)).toEqual(serialize(message));
    }
});
test('test map optional 2', () => {
    const schema = t.schema({
        data: t.map(t.date.optional),
    });
    {
        const message = { data: { first: new Date, second: undefined } };
        // const size = getBSONSizer(schema)(message);  //we maintain undefined as null, in contrary to BSON official
        // expect(size).toBe(calculateObjectSize(message));
        expect(getBSONDecoder(schema)(getBSONSerializer(schema)(message))).toEqual(message);
        // expect(getBSONSerializer(schema)(message)).toEqual(serialize(message));
    }
});
test('test union optional', () => {
    const schema = t.schema({
        data: t.union('foo', 'bar').optional
    });
    {
        const message = { data: 'foo' };
        const size = getBSONSizer(schema)(message);
        expect(size).toBe(calculateObjectSize(message));
        const serializer = getBSONSerializer(schema);
        const bson = serializer(message);
        expect(bson).toEqual(serialize(message));
        expect(getBSONDecoder(schema)(getBSONSerializer(schema)(message))).toEqual(message);
    }
    {
        const message = { data: undefined };
        // const size = getBSONSizer(schema)(message);
        // expect(size).toBe(calculateObjectSize(message)); //official bson does not include undefined, but we do
        const trip = getBSONDecoder(schema)(getBSONSerializer(schema)(message));
        expect('data' in trip).toBe(true);
        expect(trip.data).toEqual(undefined);
        // expect(getBSONSerializer(schema)(message)).toEqual(serialize(message));
    }
    {
        const message = { data: 'bar' };
        const size = getBSONSizer(schema)(message);
        expect(size).toBe(calculateObjectSize(message));
        expect(getBSONDecoder(schema)(getBSONSerializer(schema)(message))).toEqual(message);
        expect(getBSONSerializer(schema)(message)).toEqual(serialize(message));
    }
});
test('test object', () => {
    class MyModel {
        excluded = true;
         type: string = '';
        constructor(
             public name: string
        ) {
        }
    }
    const schema = t.schema({
        data: t.type(MyModel),
    });
    {
        const item = new MyModel('bar');
        item.type = 'foo';
        const message: ExtractClassType<typeof schema> = { data: item };
        expect(getBSONSizer(schema)(message)).not.toBe(calculateObjectSize(message)); //should bee different, since we do not include `excluded`, but official bson does
        const bson = getBSONSerializer(schema)(message);
        const backOfficial = deserialize(Buffer.from(bson));
        expect(backOfficial.data.excluded).toBe(undefined); //`excluded` should not be part of the BSON
        expect(bson).not.toEqual(serialize(message)); //should not be equal, since MyModel does not serialize `excluded`
        const back = getBSONDecoder(schema)(bson);
        expect(back.data).toBeInstanceOf(MyModel);
        expect(back.data.name).toBe('bar');
        expect(back.data.type).toBe('foo');
        expect(back).toEqual(message);
    }
});
test('test union deep object', () => {
    class MyModel {
        excluded = true;
        .literal('model') d: 'model' = 'model';
         type: string = '';
        constructor(
             public name: string
        ) {
        }
    }
    const schema = t.schema({
        data: t.union(t.string, MyModel),
    });
    {
        const message: ExtractClassType<typeof schema> = { data: 'peter' };
        expect(getBSONSizer(schema)(message)).toBe(calculateObjectSize(message));
        const bson = getBSONSerializer(schema)(message);
        expect(bson).toEqual(serialize(message));
        expect(getBSONDecoder(schema)(bson)).toEqual(message);
    }
    {
        const item = new MyModel('bar');
        item.type = 'foo';
        const message: ExtractClassType<typeof schema> = { data: item };
        expect(getBSONSizer(schema)(message)).not.toBe(calculateObjectSize(message)); //should bee different, since we do not include `excluded`, but official bson does
        const bson = getBSONSerializer(schema)(message);
        const backOfficial = deserialize(Buffer.from(bson));
        expect(backOfficial.data.excluded).toBe(undefined); //`excluded` should not be part of the BSON
        expect(bson).not.toEqual(serialize(message)); //should not be equal, since MyModel does not serialize `excluded`
        const back = getBSONDecoder(schema)(bson);
        expect(back.data).toBeInstanceOf(MyModel);
        expect((back.data as MyModel).name).toBe('bar');
        expect((back.data as MyModel).type).toBe('foo');
        expect(back).toEqual(message);
    }
});