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); }); });