Added utils package for parsing helpers
This commit is contained in:
224
src/utils.test.ts
Normal file
224
src/utils.test.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
F32,
|
||||
F64,
|
||||
isBytes,
|
||||
assertBytes,
|
||||
bytesToHex,
|
||||
hexToBytes,
|
||||
bytesToBase64,
|
||||
base64ToBytes,
|
||||
decodeBytes,
|
||||
encodeBytes,
|
||||
} from './utils';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
describe('utils', () => {
|
||||
const staticHexVectors = [
|
||||
{ bytes: Uint8Array.from([]), hex: '' },
|
||||
{ bytes: Uint8Array.from([0xbe]), hex: 'be' },
|
||||
{ bytes: Uint8Array.from([0xca, 0xfe]), hex: 'cafe' },
|
||||
{ bytes: Uint8Array.from(new Array(1024).fill(0x69)), hex: '69'.repeat(1024) },
|
||||
];
|
||||
|
||||
const buf = new ArrayBuffer(8);
|
||||
const v8 = new Uint8Array(buf);
|
||||
for (let i = 0; i < v8.length; i++) v8[i] = i + 1;
|
||||
|
||||
it('U8 from ArrayBuffer', () => {
|
||||
const a8 = U8(buf);
|
||||
expect(a8).toBeInstanceOf(Uint8Array);
|
||||
expect(Array.from(a8)).toEqual([1,2,3,4,5,6,7,8]);
|
||||
});
|
||||
|
||||
it('U8 from Uint8Array view', () => {
|
||||
const sub = new Uint8Array(buf, 2, 4);
|
||||
const s8 = U8(sub);
|
||||
expect(Array.from(s8)).toEqual([3,4,5,6]);
|
||||
});
|
||||
|
||||
it('U16 from ArrayBuffer', () => {
|
||||
const a16 = U16(buf);
|
||||
expect(a16).toBeInstanceOf(Uint16Array);
|
||||
expect(a16.length).toBe(Math.floor(buf.byteLength / 2));
|
||||
});
|
||||
|
||||
it('U16 from Uint8Array view', () => {
|
||||
const sub = new Uint8Array(buf, 2, 4);
|
||||
const s16 = U16(sub);
|
||||
expect(s16.length).toBe(Math.floor(sub.byteLength / 2));
|
||||
});
|
||||
|
||||
it('U32 from ArrayBuffer', () => {
|
||||
const a32 = U32(buf);
|
||||
expect(a32).toBeInstanceOf(Uint32Array);
|
||||
expect(a32.length).toBe(Math.floor(buf.byteLength / 4));
|
||||
});
|
||||
|
||||
it('U32 from Uint8Array view', () => {
|
||||
const sub = new Uint8Array(buf, 4, 4);
|
||||
const s32 = U32(sub);
|
||||
expect(s32.length).toBe(Math.floor(sub.byteLength / 4));
|
||||
});
|
||||
|
||||
it('I8 from ArrayBuffer and view', () => {
|
||||
const buf = new ArrayBuffer(4);
|
||||
const dv = new DataView(buf);
|
||||
dv.setInt8(0, -5);
|
||||
dv.setInt8(1, 120);
|
||||
dv.setInt8(2, -128);
|
||||
dv.setInt8(3, 127);
|
||||
|
||||
const a = I8(buf);
|
||||
expect(Array.from(a)).toEqual([-5, 120, -128, 127]);
|
||||
|
||||
const sub = new Int8Array(buf, 1, 2);
|
||||
const s = I8(sub);
|
||||
expect(Array.from(s)).toEqual([120, -128]);
|
||||
});
|
||||
|
||||
it('I16 from ArrayBuffer and misaligned view throws', () => {
|
||||
const buf = new ArrayBuffer(6);
|
||||
const dv = new DataView(buf);
|
||||
dv.setInt16(0, -12345, true);
|
||||
dv.setInt16(2, 12345, true);
|
||||
dv.setInt16(4, 32767, true);
|
||||
|
||||
const a16 = I16(buf);
|
||||
expect(a16.length).toBe(3);
|
||||
expect(a16[0]).toBe(-12345);
|
||||
expect(a16[1]).toBe(12345);
|
||||
expect(a16[2]).toBe(32767);
|
||||
|
||||
const view = new Uint8Array(buf, 1, 4);
|
||||
expect(() => I16(view)).toThrow();
|
||||
});
|
||||
|
||||
it('I32 from ArrayBuffer and misaligned view throws', () => {
|
||||
const buf = new ArrayBuffer(12);
|
||||
const dv = new DataView(buf);
|
||||
dv.setInt32(0, -0x1234567, true);
|
||||
dv.setInt32(4, 0x1234567, true);
|
||||
|
||||
const a32 = I32(buf);
|
||||
expect(a32[0]).toBe(-0x1234567);
|
||||
expect(a32[1]).toBe(0x1234567);
|
||||
|
||||
const view = new Uint8Array(buf, 1, 8);
|
||||
expect(() => I32(view)).toThrow();
|
||||
});
|
||||
|
||||
it('F32/F64 from ArrayBuffer', () => {
|
||||
const buf = new ArrayBuffer(16);
|
||||
const dv = new DataView(buf);
|
||||
dv.setFloat32(0, 3.14, true);
|
||||
dv.setFloat32(4, -2.5, true);
|
||||
dv.setFloat64(8, 1.23456789e3, true);
|
||||
|
||||
const f32 = F32(buf);
|
||||
expect(f32[0]).toBeCloseTo(3.14, 5);
|
||||
expect(f32[1]).toBeCloseTo(-2.5, 5);
|
||||
|
||||
const f64 = F64(buf);
|
||||
expect(f64[1]).toBeCloseTo(1.23456789e3, 8);
|
||||
});
|
||||
|
||||
it('isBytes', () => {
|
||||
expect(isBytes(new Uint8Array([1,2]))).toBe(true);
|
||||
expect(isBytes(new Uint16Array([1,2]) as unknown)).toBe(false);
|
||||
expect(isBytes(new ArrayBuffer(2) as unknown)).toBe(false);
|
||||
})
|
||||
|
||||
it('assertBytes', () => {
|
||||
// assertBytes accepts a Uint8Array
|
||||
expect(() => assertBytes(new Uint8Array([1,2]))).not.toThrow();
|
||||
// assertBytes with length mismatch throws
|
||||
expect(() => assertBytes(new Uint8Array([1,2]), 3)).toThrow();
|
||||
// assertBytes with wrong type throws
|
||||
expect(() => assertBytes(new Uint16Array([1,2]) as any)).toThrow();
|
||||
});
|
||||
|
||||
it('hexToBytes / bytesToHex roundtrip', () => {
|
||||
for (const v of staticHexVectors) {
|
||||
expect(hexToBytes(v.hex)).toEqual(v.bytes);
|
||||
expect(hexToBytes(v.hex.toUpperCase())).toEqual(v.bytes);
|
||||
expect(bytesToHex(v.bytes)).toEqual(v.hex);
|
||||
// encode -> decode
|
||||
const h = bytesToHex(v.bytes);
|
||||
expect(hexToBytes(h)).toEqual(v.bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it('base64 encodings roundtrip and variants', () => {
|
||||
const hello = new Uint8Array([72,101,108,108,111]); // 'Hello'
|
||||
const b64 = bytesToBase64(hello, 'base64');
|
||||
expect(base64ToBytes(b64)).toEqual(hello);
|
||||
|
||||
const b64raw = bytesToBase64(hello, 'base64raw');
|
||||
expect(base64ToBytes(b64raw)).toEqual(hello);
|
||||
|
||||
const b64url = bytesToBase64(hello, 'base64url');
|
||||
expect(base64ToBytes(b64url)).toEqual(hello);
|
||||
|
||||
const b64urlraw = bytesToBase64(hello, 'base64urlraw');
|
||||
expect(base64ToBytes(b64urlraw)).toEqual(hello);
|
||||
});
|
||||
|
||||
it('encodeBytes / decodeBytes round robin across encodings', () => {
|
||||
const bytes = Uint8Array.from([0xde,0xad,0xbe,0xef]);
|
||||
const encodings = ['hex','base64','base64raw','base64url','base64urlraw'] as const;
|
||||
for (const enc of encodings) {
|
||||
const s = encodeBytes(bytes, enc as any);
|
||||
const out = decodeBytes(s, enc as any);
|
||||
expect(out).toEqual(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws on invalid inputs for hex/base64 decoders', () => {
|
||||
expect(() => hexToBytes('z')).toThrow();
|
||||
expect(() => base64ToBytes('??')).toThrow();
|
||||
expect(() => decodeBytes('abc', 'unsupported' as any)).toThrow();
|
||||
});
|
||||
|
||||
it('edge cases: offsets, odd lengths, url base64, empty and large roundtrips', () => {
|
||||
// U16/U32 with subarray offset
|
||||
const buf = new ArrayBuffer(6);
|
||||
const v = new Uint8Array(buf);
|
||||
v.set([1,2,3,4,5,6]);
|
||||
const view = new Uint8Array(buf, 1, 4); // [2,3,4,5]
|
||||
const u8 = U8(view);
|
||||
expect(Array.from(u8)).toEqual([2,3,4,5]);
|
||||
// Unaligned views cannot be reinterpreted as 16-bit/32-bit arrays on some
|
||||
// platforms; U16/U32 will throw for misaligned offsets. Assert that behavior.
|
||||
expect(() => U16(view)).toThrow();
|
||||
|
||||
// hexToBytes with odd length should throw
|
||||
expect(() => hexToBytes('f')).toThrow();
|
||||
|
||||
// base64 url-safe without padding should be accepted
|
||||
const sample = new Uint8Array([0x01,0x02,0x03,0xff]);
|
||||
const raw = bytesToBase64(sample, 'base64raw');
|
||||
// url variant
|
||||
const url = bytesToBase64(sample, 'base64urlraw');
|
||||
expect(base64ToBytes(raw)).toEqual(sample);
|
||||
expect(base64ToBytes(url)).toEqual(sample);
|
||||
|
||||
// empty inputs
|
||||
expect(bytesToHex(new Uint8Array([]))).toBe('');
|
||||
expect(bytesToBase64(new Uint8Array([]), 'base64')).toBe('');
|
||||
|
||||
// large random roundtrip
|
||||
const large = new Uint8Array(10000);
|
||||
for (let i = 0; i < large.length; i++) large[i] = i & 0xff;
|
||||
const h = bytesToHex(large);
|
||||
expect(hexToBytes(h)).toEqual(large);
|
||||
const b64 = bytesToBase64(large, 'base64raw');
|
||||
expect(base64ToBytes(b64)).toEqual(large);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user