UNPKG

strictencode

Version:

Deterministic binary encoding for RGB protocol compliance - JavaScript implementation of StrictEncode

369 lines (309 loc) 13.8 kB
/** * @fileoverview Tests for StrictEncoder class */ import { StrictEncoder } from '../index.js'; describe('StrictEncoder', () => { let encoder; beforeEach(() => { encoder = new StrictEncoder(); }); describe('constructor', () => { test('creates empty encoder', () => { expect(encoder.length()).toBe(0); expect(encoder.toHex()).toBe(''); expect(encoder.toBytes()).toEqual(new Uint8Array(0)); }); }); describe('encodeU8', () => { test('encodes valid u8 values', () => { expect(encoder.encodeU8(0).toHex()).toBe('00'); encoder.reset(); expect(encoder.encodeU8(255).toHex()).toBe('ff'); encoder.reset(); expect(encoder.encodeU8(123).toHex()).toBe('7b'); }); test('throws on invalid values', () => { expect(() => encoder.encodeU8(-1)).toThrow('Invalid u8 value'); expect(() => encoder.encodeU8(256)).toThrow('Invalid u8 value'); expect(() => encoder.encodeU8(1.5)).toThrow('Invalid u8 value'); expect(() => encoder.encodeU8('123')).toThrow('Invalid u8 value'); }); test('chains correctly', () => { const result = encoder.encodeU8(1).encodeU8(2).encodeU8(3); expect(result).toBe(encoder); expect(encoder.toHex()).toBe('010203'); }); }); describe('encodeU16', () => { test('encodes in little-endian format', () => { expect(encoder.encodeU16(0).toHex()).toBe('0000'); encoder.reset(); expect(encoder.encodeU16(65535).toHex()).toBe('ffff'); encoder.reset(); expect(encoder.encodeU16(256).toHex()).toBe('0001'); // 0x0100 in little-endian encoder.reset(); expect(encoder.encodeU16(1000).toHex()).toBe('e803'); // 0x03e8 in little-endian }); test('throws on invalid values', () => { expect(() => encoder.encodeU16(-1)).toThrow('Invalid u16 value'); expect(() => encoder.encodeU16(65536)).toThrow('Invalid u16 value'); expect(() => encoder.encodeU16(1.5)).toThrow('Invalid u16 value'); }); }); describe('encodeU32', () => { test('encodes in little-endian format', () => { expect(encoder.encodeU32(0).toHex()).toBe('00000000'); encoder.reset(); expect(encoder.encodeU32(4294967295).toHex()).toBe('ffffffff'); encoder.reset(); expect(encoder.encodeU32(1000000).toHex()).toBe('40420f00'); // Test vector from spec }); test('throws on invalid values', () => { expect(() => encoder.encodeU32(-1)).toThrow('Invalid u32 value'); expect(() => encoder.encodeU32(4294967296)).toThrow('Invalid u32 value'); }); }); describe('encodeU64', () => { test('encodes in little-endian format', () => { expect(encoder.encodeU64(0n).toHex()).toBe('0000000000000000'); encoder.reset(); expect(encoder.encodeU64(1000000).toHex()).toBe('40420f0000000000'); // Test vector from spec encoder.reset(); expect(encoder.encodeU64('1000000').toHex()).toBe('40420f0000000000'); encoder.reset(); expect(encoder.encodeU64(1000000n).toHex()).toBe('40420f0000000000'); }); test('handles large numbers', () => { const maxU64 = 18446744073709551615n; expect(encoder.encodeU64(maxU64).toHex()).toBe('ffffffffffffffff'); }); test('throws on invalid values', () => { expect(() => encoder.encodeU64(-1)).toThrow('Invalid u64 value'); expect(() => encoder.encodeU64('18446744073709551616')).toThrow('Invalid u64 value'); }); }); describe('encodeBool', () => { test('encodes boolean values correctly', () => { expect(encoder.encodeBool(true).toHex()).toBe('01'); encoder.reset(); expect(encoder.encodeBool(false).toHex()).toBe('00'); }); test('handles truthy/falsy values', () => { encoder.encodeBool(1); encoder.encodeBool(0); encoder.encodeBool('true'); encoder.encodeBool(''); encoder.encodeBool({}); encoder.encodeBool(null); expect(encoder.toHex()).toBe('010001000100'); }); }); describe('encodeLeb128', () => { test('encodes small values in single byte', () => { expect(encoder.encodeLeb128(0).toHex()).toBe('00'); encoder.reset(); expect(encoder.encodeLeb128(7).toHex()).toBe('07'); encoder.reset(); expect(encoder.encodeLeb128(127).toHex()).toBe('7f'); }); test('encodes multi-byte values', () => { expect(encoder.encodeLeb128(128).toHex()).toBe('8001'); encoder.reset(); expect(encoder.encodeLeb128(200).toHex()).toBe('c801'); // Test vector from spec encoder.reset(); expect(encoder.encodeLeb128(300).toHex()).toBe('ac02'); }); test('encodes large values', () => { expect(encoder.encodeLeb128(16384).toHex()).toBe('808001'); }); test('throws on invalid values', () => { expect(() => encoder.encodeLeb128(-1)).toThrow('Invalid LEB128 value'); expect(() => encoder.encodeLeb128(1.5)).toThrow('Invalid LEB128 value'); }); }); describe('encodeString', () => { test('encodes ASCII strings', () => { expect(encoder.encodeString('').toHex()).toBe('00'); // Empty string encoder.reset(); expect(encoder.encodeString('RGB').toHex()).toBe('03524742'); // Test vector from spec encoder.reset(); expect(encoder.encodeString('NIATCKR').toHex()).toBe('074e494154434b52'); // Test vector from spec }); test('encodes UTF-8 strings', () => { encoder.encodeString('Hello 🌈'); const hex = encoder.toHex(); expect(hex.startsWith('0a')).toBe(true); // Length 10 bytes for "Hello 🌈" }); test('handles long strings with multi-byte LEB128', () => { const longString = 'A'.repeat(200); encoder.encodeString(longString); const hex = encoder.toHex(); expect(hex.startsWith('c801')).toBe(true); // LEB128(200) = c801 expect(hex.slice(4)).toBe('41'.repeat(200)); // 200 'A' characters }); test('throws on non-string input', () => { expect(() => encoder.encodeString(123)).toThrow('Invalid string'); expect(() => encoder.encodeString(null)).toThrow('Invalid string'); }); }); describe('encodeOption', () => { test('encodes None values', () => { encoder.encodeOption(null, () => {}); expect(encoder.toHex()).toBe('00'); encoder.reset(); encoder.encodeOption(undefined, () => {}); expect(encoder.toHex()).toBe('00'); }); test('encodes Some values', () => { encoder.encodeOption('test', function(value) { this.encodeString(value); }); expect(encoder.toHex()).toBe('010474657374'); // 01 + encodeString("test") }); test('throws when encoderFunc is missing for Some', () => { expect(() => encoder.encodeOption('test')).toThrow('encoderFunc must be a function'); }); }); describe('encodeVec', () => { test('encodes empty array', () => { encoder.encodeVec([], () => {}); expect(encoder.toHex()).toBe('00'); // Length 0 }); test('encodes string array', () => { encoder.encodeVec(['RGB', '20'], function(item) { this.encodeString(item); }); expect(encoder.toHex()).toBe('0203524742023230'); // 02 + "RGB" + "20" }); test('encodes number array', () => { encoder.encodeVec([1, 2, 3], function(item) { this.encodeU8(item); }); expect(encoder.toHex()).toBe('03010203'); // 03 + 01 + 02 + 03 }); test('throws on non-array input', () => { expect(() => encoder.encodeVec('not array', () => {})).toThrow('Invalid Vec'); }); test('throws when encoderFunc is missing', () => { expect(() => encoder.encodeVec([1, 2])).toThrow('encoderFunc must be a function'); }); }); describe('encodeHashMap', () => { test('encodes empty map', () => { encoder.encodeHashMap({}, () => {}); expect(encoder.toHex()).toBe('00'); // Empty Vec }); test('sorts by key and encodes values', () => { const map = { 2001: 'terms', 2000: 'spec', 2002: 'amount' }; encoder.encodeHashMap(map, function(value) { this.encodeString(value); }); const hex = encoder.toHex(); expect(hex.startsWith('03')).toBe(true); // 3 items // Values should be in key order: spec(2000), terms(2001), amount(2002) expect(hex.includes('7370656')).toBe(true); // "spec" hex expect(hex.includes('7465726d73')).toBe(true); // "terms" hex expect(hex.includes('616d6f756e74')).toBe(true); // "amount" hex }); test('handles Map objects', () => { const map = new Map(); map.set(100, 'first'); map.set(50, 'second'); encoder.encodeHashMap(map, function(value) { this.encodeString(value); }); const hex = encoder.toHex(); expect(hex.startsWith('02')).toBe(true); // 2 items }); test('throws on invalid keys', () => { expect(() => encoder.encodeHashMap({ 'invalid': 'value' }, () => {})).toThrow('Invalid HashMap key'); expect(() => encoder.encodeHashMap({ '-1': 'value' }, () => {})).toThrow('Invalid HashMap key'); }); test('throws on invalid input', () => { expect(() => encoder.encodeHashMap('not object', () => {})).toThrow('Invalid HashMap'); expect(() => encoder.encodeHashMap(null, () => {})).toThrow('Invalid HashMap'); }); }); describe('utility methods', () => { test('toBytes returns copy', () => { encoder.encodeU8(123); const bytes1 = encoder.toBytes(); const bytes2 = encoder.toBytes(); expect(bytes1).toEqual(bytes2); expect(bytes1).not.toBe(bytes2); // Different objects bytes1[0] = 0; // Modify copy expect(encoder.toBytes()[0]).toBe(123); // Original unchanged }); test('reset clears buffer', () => { encoder.encodeU8(123).encodeString('test'); expect(encoder.length()).toBeGreaterThan(0); encoder.reset(); expect(encoder.length()).toBe(0); expect(encoder.toHex()).toBe(''); }); test('clone creates independent copy', () => { encoder.encodeU8(123); const clone = encoder.clone(); expect(clone.toHex()).toBe(encoder.toHex()); expect(clone).not.toBe(encoder); clone.encodeU8(200); expect(clone.length()).not.toBe(encoder.length()); }); test('length returns correct byte count', () => { expect(encoder.length()).toBe(0); encoder.encodeU8(123); expect(encoder.length()).toBe(1); encoder.encodeU32(456); expect(encoder.length()).toBe(5); encoder.encodeString('test'); expect(encoder.length()).toBe(10); // 5 + 1(len) + 4(chars) }); }); describe('chaining', () => { test('all methods support chaining', () => { const result = encoder .encodeU8(1) .encodeU16(2) .encodeU32(3) .encodeU64(4n) .encodeBool(true) .encodeLeb128(5) .encodeString('test') .encodeOption(null, () => {}) .encodeVec([], () => {}) .encodeHashMap({}, () => {}) .reset(); expect(result).toBe(encoder); }); }); describe('edge cases', () => { test('handles maximum values', () => { expect(() => { encoder.encodeU8(255) .encodeU16(65535) .encodeU32(4294967295) .encodeU64(18446744073709551615n); }).not.toThrow(); }); test('handles empty inputs', () => { expect(() => { encoder.encodeString('') .encodeVec([], () => {}) .encodeHashMap({}, () => {}) .encodeOption(null, () => {}); }).not.toThrow(); }); test('preserves data integrity', () => { // Encode known test vector from spec encoder.encodeString('NIATCKR') .encodeString('NIA asset name') .encodeU8(8) .encodeOption(null, () => {}); expect(encoder.toHex()).toBe('074e494154434b520e4e4941206173736574206e616d650800'); }); }); });