UNPKG

@cloudpss/ubjson

Version:

Opinionated UBJSON encoder/decoder for CloudPSS.

398 lines (335 loc) 12.5 kB
/** * Tests from https://bitbucket.org/shelacek/ubjson */ import { firstValueFrom, of, map, reduce, Subject, toArray as rxjsToArray } from 'rxjs'; import { encode } from '../../dist/rxjs/index.js'; import { decode, encode as encodeRef } from '../../dist/index.js'; import { toArray } from '../.utils.js'; /** * 包装为 promise * @param {unknown} value 要编码的值 * @returns {Promise<Buffer>} */ async function encodeAsync(value) { return firstValueFrom( of(value).pipe( encode(), rxjsToArray(), map((data) => Buffer.concat(data)), ), ); } test('encode function', async () => { await expect(async () => encodeAsync(function () { // noop }), ).rejects.toThrow(); }); test('encode bigint', async () => { expect(toArray(await encodeAsync(1n))).toEqual(toArray('U', 1)); expect(toArray(await encodeAsync(0x1876_9876_1234_1234n))).toEqual( toArray('L', 0x18, 0x76, 0x98, 0x76, 0x12, 0x34, 0x12, 0x34), ); await expect(encodeAsync(0x8234_5678_90ab_cdefn)).rejects.toThrow(/BigInt value out of range:/); }); test('encode symbol', async () => { await expect(async () => encodeAsync(Symbol('sym'))).rejects.toThrow(); }); test('encode undefined', async () => { expect(toArray(await encodeAsync(undefined))).toEqual(toArray('N')); }); test('encode null', async () => { expect(toArray(await encodeAsync(null))).toEqual(toArray('Z')); }); test('encode true', async () => { expect(toArray(await encodeAsync(true))).toEqual(toArray('T')); }); test('encode false', async () => { expect(toArray(await encodeAsync(false))).toEqual(toArray('F')); }); test('encode int8', async () => { expect(toArray(await encodeAsync(-1))).toEqual(toArray('i', 255)); }); test('encode uint8', async () => { expect(toArray(await encodeAsync(200))).toEqual(toArray('U', 200)); }); test('encode int16', async () => { expect(toArray(await encodeAsync(0x1234))).toEqual(toArray('I', 0x12, 0x34)); }); test('encode int32', async () => { expect(toArray(await encodeAsync(0x1234_5678))).toEqual(toArray('l', 0x12, 0x34, 0x56, 0x78)); }); test('encode float32', async () => { expect(toArray(await encodeAsync(1.003_906_25))).toEqual(toArray('d', 0x3f, 0x80, 0x80, 0x00)); }); test('encode float32 (too large integer)', async () => { expect(toArray(await encodeAsync(2_147_483_648))).toEqual(toArray('d', 0x4f, 0x00, 0x00, 0x00)); }); test('encode float64', async () => { expect(toArray(await encodeAsync(100_000.003_906_25))).toEqual( toArray('D', 0x40, 0xf8, 0x6a, 0x00, 0x10, 0x00, 0x00, 0x00), ); }); test('encode char', async () => { expect(toArray(await encodeAsync('a'))).toEqual(toArray('C', 'a')); }); test('encode char 127', async () => { expect(toArray(await encodeAsync('\u007F'))).toEqual(toArray('C', '\u007F')); }); test('encode char 257', async () => { expect(toArray(await encodeAsync('\u0123'))).toEqual(toArray('S', 'i', 2, 196, 163)); }); test('encode char emoji', async () => { expect(toArray(await encodeAsync('💖'))).toEqual(toArray('S', 'i', 4, 240, 159, 146, 150)); }); test('encode string', async () => { expect(toArray(await encodeAsync('ubjson'))).toEqual(toArray('S', 'i', 6, 'u', 'b', 'j', 's', 'o', 'n')); }); test('encode huge string', async () => { const str = 'ubjson💖💖💖'.repeat(10000); expect(toArray(await encodeAsync(str))).toEqual(toArray(encodeRef(str))); }); test('encode string (no encodeInto)', async () => { // eslint-disable-next-line @typescript-eslint/unbound-method const { encodeInto } = TextEncoder.prototype; // @ts-expect-error 移除 encodeInto 以测试兼容性 TextEncoder.prototype.encodeInto = undefined; expect(toArray(await encodeAsync('ubjson'))).toEqual(toArray('S', 'i', 6, 'u', 'b', 'j', 's', 'o', 'n')); TextEncoder.prototype.encodeInto = encodeInto; }); test('encode array', async () => { expect(toArray(await encodeAsync([1, 2, 3, -1]))).toEqual(toArray('[', 'U', 1, 'U', 2, 'U', 3, 'i', 255, ']')); }); test('encode array (empty)', async () => { expect(toArray(await encodeAsync([]))).toEqual(toArray('[', ']')); }); test('encode array (undefined)', async () => { expect(toArray(await encodeAsync([undefined]))).toEqual(toArray('[', 'Z', ']')); }); test('encode array (spares)', async () => { const array = Array.from({ length: 3 }); array[1] = true; expect(toArray(await encodeAsync(array))).toEqual(toArray('[', 'Z', 'T', 'Z', ']')); }); test('encode array (mixed)', async () => { expect(toArray(await encodeAsync([1, 'a', true]))).toEqual(toArray('[', 'U', 1, 'C', 'a', 'T', ']')); }); test('encode array (int8)', async () => { expect(toArray(await encodeAsync([-1, 2, 3]))).toEqual(toArray('[', 'i', 255, 'U', 2, 'U', 3, ']')); }); test('encode array (int16)', async () => { expect(toArray(await encodeAsync([255, -1]))).toEqual(toArray('[', 'U', 0xff, 'i', 0xff, ']')); }); test('encode array (only null values)', async () => { expect(toArray(await encodeAsync([null, null, null]))).toEqual(toArray('[', 'Z', 'Z', 'Z', ']')); }); test('encode N-D array', async () => { expect( decode( await encodeAsync([ [1, 2, 3], [4, 5, 6], ]), ), ).toEqual([ [1, 2, 3], [4, 5, 6], ]); }); test('encode array of objects', async () => { expect( decode( await encodeAsync([ { a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }, ]), ), ).toEqual([ { a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }, ]); }); test('encode array of objects of arrays', async () => { expect( decode( await encodeAsync([ { a: [1, 2], b: [3, 4] }, { c: [5, 6], d: [7, 8] }, ]), ), ).toEqual([ { a: [1, 2], b: [3, 4] }, { c: [5, 6], d: [7, 8] }, ]); }); test('encode array (int8 typed array)', async () => { expect(toArray(await encodeAsync(Int8Array.from([18, -2])))).toEqual( toArray('[', '$', 'i', '#', 'i', 2, 0x12, 0xfe), ); }); test('encode array (uint8 typed array)', async () => { expect(toArray(await encodeAsync(Uint8Array.from([18, 254])))).toEqual( toArray('[', '$', 'U', '#', 'i', 2, 0x12, 0xfe), ); }); test('encode array (int16 typed array)', async () => { expect(toArray(await encodeAsync(Int16Array.from([4660, -292])))).toEqual( toArray('[', '$', 'I', '#', 'i', 2, 0x12, 0x34, 0xfe, 0xdc), ); }); test('encode array (int32 typed array)', async () => { expect(toArray(await encodeAsync(Int32Array.from([305_419_896, -19_088_744])))).toEqual( toArray('[', '$', 'l', '#', 'i', 2, 0x12, 0x34, 0x56, 0x78, 0xfe, 0xdc, 0xba, 0x98), ); }); test('encode array (float32 typed array)', async () => { expect(toArray(await encodeAsync(Float32Array.from([0.25, 0.125])))).toEqual( toArray('[', '$', 'd', '#', 'i', 2, 0x3e, 0x80, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00), ); }); test('encode array (float64 typed array)', async () => { expect(toArray(await encodeAsync(Float64Array.from([0.25, 0.125])))).toEqual( toArray( '[', '$', 'D', '#', 'i', 2, 0x3f, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ), ); }); test('encode array (uint8clamped typed array)', async () => { await expect(async () => encodeAsync(new Uint8ClampedArray())).rejects.toThrow(); }); test('encode array (uint16 typed array)', async () => { await expect(async () => encodeAsync(new Uint16Array())).rejects.toThrow(); }); test('encode array (uint32 typed array)', async () => { await expect(async () => encodeAsync(new Uint32Array())).rejects.toThrow(); }); test('encode array (uint64 typed array)', async () => { await expect(async () => encodeAsync(new BigUint64Array())).rejects.toThrow(); }); test('encode array (int64 typed array)', async () => { expect(toArray(await encodeAsync(new BigInt64Array()))).toEqual(toArray('[', '$', 'L', '#', 'i', 0)); }); test('encode object', async () => { expect(toArray(await encodeAsync({ a: 1, b: 2, c: 3 }))).toEqual( toArray('{', 'i', 1, 'a', 'U', 1, 'i', 1, 'b', 'U', 2, 'i', 1, 'c', 'U', 3, '}'), ); }); test('encode object (empty)', async () => { expect(toArray(await encodeAsync({}))).toEqual(toArray('{', '}')); }); test('encode object (empty key)', async () => { expect(toArray(await encodeAsync({ '': '' }))).toEqual(toArray('{', 'i', 0, 'S', 'i', 0, '}')); }); test('encode object (mixed)', async () => { expect(toArray(await encodeAsync({ a: 1, b: 'a', c: true }))).toEqual( toArray('{', 'i', 1, 'a', 'U', 1, 'i', 1, 'b', 'C', 'a', 'i', 1, 'c', 'T', '}'), ); }); test('encode object (only null values)', async () => { expect(toArray(await encodeAsync({ a: null, b: null, c: null }))).toEqual( toArray('{', 'i', 1, 'a', 'Z', 'i', 1, 'b', 'Z', 'i', 1, 'c', 'Z', '}'), ); }); test('encode object (skip prototype)', async () => { const obj = /** @type {Record<string, unknown>} */ (Object.create({ a: 2, x: 'xx' })); obj['a'] = 1; obj['b'] = 'a'; obj['c'] = true; expect(toArray(await encodeAsync(obj))).toEqual( toArray('{', 'i', 1, 'a', 'U', 1, 'i', 1, 'b', 'C', 'a', 'i', 1, 'c', 'T', '}'), ); }); test('encode object (skip symbol)', async () => { const obj = { [Symbol()]: true, a: 1 }; expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', 'i', 1, 'a', 'U', 1, '}')); }); test('encode object (skip non-enumerable)', async () => { const obj = {}; Object.defineProperty(obj, 'a', { value: 1, configurable: true, writable: true }); expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}')); }); test('encode object (include getter)', async () => { const obj = {}; Object.defineProperty(obj, 'a', { get: () => 1, enumerable: true }); expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', 'i', 1, 'a', 'U', 1, '}')); }); test('encode object (skip undefined)', async () => { const obj = { a: undefined }; expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}')); }); test('encode object (skip function)', async () => { const obj = { a: () => 1, }; expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}')); }); test('encode object (include null)', async () => { const obj = { a: null }; expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', 'i', 1, 'a', 'Z', '}')); }); test('encode huge typed array (16K)', async () => { const obj = new Uint8Array(16 * 1024); expect(toArray((await encodeAsync(obj)).slice(0, 8))).toEqual(toArray('[', '$', 'U', '#', 'I', 0x40, 0x00, 0)); }); test('encode huge typed array (~128M)', async () => { const obj = new Uint8Array(128 * 1024 * 1024 - 10); expect(toArray((await encodeAsync(obj)).slice(0, 10))).toEqual( toArray('[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf6, 0), ); }); test('encode huge data (~128M)', async () => { const obj = [new Uint8Array(128 * 1024 * 1024 - 20)]; expect(toArray((await encodeAsync(obj)).subarray(0, 11))).toEqual( toArray('[', '[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xec, 0), ); }); test('encode stream', async () => { const stream = new Subject(); const result = firstValueFrom( stream.pipe( encode(), reduce((a, b) => Buffer.concat([a, b])), ), ); stream.next(undefined); stream.next(true); stream.next(false); stream.complete(); expect(toArray(await result)).toEqual(toArray('N', 'T', 'F')); }); test('encode stream input error [error]', async () => { const stream = new Subject(); const result = firstValueFrom( stream.pipe( encode(), reduce((a, b) => Buffer.concat([a, b])), ), ); stream.next(undefined); stream.next(true); stream.next(false); stream.error(new Error('123456')); await expect(result).rejects.toThrow('123456'); });