@cloudpss/ubjson
Version:
Opinionated UBJSON encoder/decoder for CloudPSS.
398 lines (335 loc) • 12.5 kB
JavaScript
/**
* 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');
});