import { test, describe } from 'node:test' import assert from 'node:assert' import { cborEncode, cborDecode, createCid, cidToString, cidToBytes, base32Encode, createTid, generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes, getKeyDepth, varint, base32Decode, buildCarFile } from '../src/pds.js' describe('CBOR Encoding', () => { test('encodes simple map', () => { const encoded = cborEncode({ hello: 'world', num: 42 }) // Expected: a2 65 68 65 6c 6c 6f 65 77 6f 72 6c 64 63 6e 75 6d 18 2a const expected = new Uint8Array([ 0xa2, 0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x65, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x63, 0x6e, 0x75, 0x6d, 0x18, 0x2a ]) assert.deepStrictEqual(encoded, expected) }) test('encodes null', () => { const encoded = cborEncode(null) assert.deepStrictEqual(encoded, new Uint8Array([0xf6])) }) test('encodes booleans', () => { assert.deepStrictEqual(cborEncode(true), new Uint8Array([0xf5])) assert.deepStrictEqual(cborEncode(false), new Uint8Array([0xf4])) }) test('encodes small integers', () => { assert.deepStrictEqual(cborEncode(0), new Uint8Array([0x00])) assert.deepStrictEqual(cborEncode(1), new Uint8Array([0x01])) assert.deepStrictEqual(cborEncode(23), new Uint8Array([0x17])) }) test('encodes integers >= 24', () => { assert.deepStrictEqual(cborEncode(24), new Uint8Array([0x18, 0x18])) assert.deepStrictEqual(cborEncode(255), new Uint8Array([0x18, 0xff])) }) test('encodes negative integers', () => { assert.deepStrictEqual(cborEncode(-1), new Uint8Array([0x20])) assert.deepStrictEqual(cborEncode(-10), new Uint8Array([0x29])) }) test('encodes strings', () => { const encoded = cborEncode('hello') // 0x65 = text string of length 5 assert.deepStrictEqual(encoded, new Uint8Array([0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f])) }) test('encodes byte strings', () => { const bytes = new Uint8Array([1, 2, 3]) const encoded = cborEncode(bytes) // 0x43 = byte string of length 3 assert.deepStrictEqual(encoded, new Uint8Array([0x43, 1, 2, 3])) }) test('encodes arrays', () => { const encoded = cborEncode([1, 2, 3]) // 0x83 = array of length 3 assert.deepStrictEqual(encoded, new Uint8Array([0x83, 0x01, 0x02, 0x03])) }) test('sorts map keys deterministically', () => { const encoded1 = cborEncode({ z: 1, a: 2 }) const encoded2 = cborEncode({ a: 2, z: 1 }) assert.deepStrictEqual(encoded1, encoded2) // First key should be 'a' (0x61) assert.strictEqual(encoded1[1], 0x61) }) test('encodes large integers >= 2^31 without overflow', () => { // 2^31 would overflow with bitshift operators (treated as signed 32-bit) const twoTo31 = 2147483648 const encoded = cborEncode(twoTo31) const decoded = cborDecode(encoded) assert.strictEqual(decoded, twoTo31) // 2^32 - 1 (max unsigned 32-bit) const maxU32 = 4294967295 const encoded2 = cborEncode(maxU32) const decoded2 = cborDecode(encoded2) assert.strictEqual(decoded2, maxU32) }) test('encodes 2^31 with correct byte format', () => { // 2147483648 = 0x80000000 // CBOR: major type 0 (unsigned int), additional info 26 (4-byte follows) const encoded = cborEncode(2147483648) assert.strictEqual(encoded[0], 0x1a) // type 0 | info 26 assert.strictEqual(encoded[1], 0x80) assert.strictEqual(encoded[2], 0x00) assert.strictEqual(encoded[3], 0x00) assert.strictEqual(encoded[4], 0x00) }) }) describe('Base32 Encoding', () => { test('encodes bytes to base32lower', () => { const bytes = new Uint8Array([0x01, 0x71, 0x12, 0x20]) const encoded = base32Encode(bytes) assert.strictEqual(typeof encoded, 'string') assert.match(encoded, /^[a-z2-7]+$/) }) }) describe('CID Generation', () => { test('creates CIDv1 with dag-cbor codec', async () => { const data = cborEncode({ test: 'data' }) const cid = await createCid(data) assert.strictEqual(cid.length, 36) // 2 prefix + 2 multihash header + 32 hash assert.strictEqual(cid[0], 0x01) // CIDv1 assert.strictEqual(cid[1], 0x71) // dag-cbor assert.strictEqual(cid[2], 0x12) // sha-256 assert.strictEqual(cid[3], 0x20) // 32 bytes }) test('cidToString returns base32lower with b prefix', async () => { const data = cborEncode({ test: 'data' }) const cid = await createCid(data) const cidStr = cidToString(cid) assert.strictEqual(cidStr[0], 'b') assert.match(cidStr, /^b[a-z2-7]+$/) }) test('same input produces same CID', async () => { const data1 = cborEncode({ test: 'data' }) const data2 = cborEncode({ test: 'data' }) const cid1 = cidToString(await createCid(data1)) const cid2 = cidToString(await createCid(data2)) assert.strictEqual(cid1, cid2) }) test('different input produces different CID', async () => { const cid1 = cidToString(await createCid(cborEncode({ a: 1 }))) const cid2 = cidToString(await createCid(cborEncode({ a: 2 }))) assert.notStrictEqual(cid1, cid2) }) }) describe('TID Generation', () => { test('creates 13-character TIDs', () => { const tid = createTid() assert.strictEqual(tid.length, 13) }) test('uses valid base32-sort characters', () => { const tid = createTid() assert.match(tid, /^[234567abcdefghijklmnopqrstuvwxyz]+$/) }) test('generates monotonically increasing TIDs', () => { const tid1 = createTid() const tid2 = createTid() const tid3 = createTid() assert.ok(tid1 < tid2, `${tid1} should be less than ${tid2}`) assert.ok(tid2 < tid3, `${tid2} should be less than ${tid3}`) }) test('generates unique TIDs', () => { const tids = new Set() for (let i = 0; i < 100; i++) { tids.add(createTid()) } assert.strictEqual(tids.size, 100) }) }) describe('P-256 Signing', () => { test('generates key pair with correct sizes', async () => { const kp = await generateKeyPair() assert.strictEqual(kp.privateKey.length, 32) assert.strictEqual(kp.publicKey.length, 33) // compressed assert.ok(kp.publicKey[0] === 0x02 || kp.publicKey[0] === 0x03) }) test('can sign data with generated key', async () => { const kp = await generateKeyPair() const key = await importPrivateKey(kp.privateKey) const data = new TextEncoder().encode('test message') const sig = await sign(key, data) assert.strictEqual(sig.length, 64) // r (32) + s (32) }) test('different messages produce different signatures', async () => { const kp = await generateKeyPair() const key = await importPrivateKey(kp.privateKey) const sig1 = await sign(key, new TextEncoder().encode('message 1')) const sig2 = await sign(key, new TextEncoder().encode('message 2')) assert.notDeepStrictEqual(sig1, sig2) }) test('bytesToHex and hexToBytes roundtrip', () => { const original = new Uint8Array([0x00, 0x0f, 0xf0, 0xff, 0xab, 0xcd]) const hex = bytesToHex(original) const back = hexToBytes(hex) assert.strictEqual(hex, '000ff0ffabcd') assert.deepStrictEqual(back, original) }) test('importPrivateKey rejects invalid key lengths', async () => { // Too short await assert.rejects( () => importPrivateKey(new Uint8Array(31)), /expected 32 bytes, got 31/ ) // Too long await assert.rejects( () => importPrivateKey(new Uint8Array(33)), /expected 32 bytes, got 33/ ) // Empty await assert.rejects( () => importPrivateKey(new Uint8Array(0)), /expected 32 bytes, got 0/ ) }) test('importPrivateKey rejects non-Uint8Array input', async () => { // Arrays have .length but aren't Uint8Array await assert.rejects( () => importPrivateKey([1, 2, 3]), /Invalid private key/ ) // Strings don't work either await assert.rejects( () => importPrivateKey('not bytes'), /Invalid private key/ ) // null/undefined await assert.rejects( () => importPrivateKey(null), /Invalid private key/ ) }) }) describe('MST Key Depth', () => { test('returns a non-negative integer', async () => { const depth = await getKeyDepth('app.bsky.feed.post/abc123') assert.strictEqual(typeof depth, 'number') assert.ok(depth >= 0) }) test('is deterministic for same key', async () => { const key = 'app.bsky.feed.post/test123' const depth1 = await getKeyDepth(key) const depth2 = await getKeyDepth(key) assert.strictEqual(depth1, depth2) }) test('different keys can have different depths', async () => { // Generate many keys and check we get some variation const depths = new Set() for (let i = 0; i < 100; i++) { depths.add(await getKeyDepth(`collection/key${i}`)) } // Should have at least 1 unique depth (realistically more) assert.ok(depths.size >= 1) }) test('handles empty string', async () => { const depth = await getKeyDepth('') assert.strictEqual(typeof depth, 'number') assert.ok(depth >= 0) }) test('handles unicode strings', async () => { const depth = await getKeyDepth('app.bsky.feed.post/émoji🎉') assert.strictEqual(typeof depth, 'number') assert.ok(depth >= 0) }) }) describe('CBOR Decoding', () => { test('decodes what encode produces (roundtrip)', () => { const original = { hello: 'world', num: 42 } const encoded = cborEncode(original) const decoded = cborDecode(encoded) assert.deepStrictEqual(decoded, original) }) test('decodes null', () => { const encoded = cborEncode(null) const decoded = cborDecode(encoded) assert.strictEqual(decoded, null) }) test('decodes booleans', () => { assert.strictEqual(cborDecode(cborEncode(true)), true) assert.strictEqual(cborDecode(cborEncode(false)), false) }) test('decodes integers', () => { assert.strictEqual(cborDecode(cborEncode(0)), 0) assert.strictEqual(cborDecode(cborEncode(42)), 42) assert.strictEqual(cborDecode(cborEncode(255)), 255) assert.strictEqual(cborDecode(cborEncode(-1)), -1) assert.strictEqual(cborDecode(cborEncode(-10)), -10) }) test('decodes strings', () => { assert.strictEqual(cborDecode(cborEncode('hello')), 'hello') assert.strictEqual(cborDecode(cborEncode('')), '') }) test('decodes arrays', () => { assert.deepStrictEqual(cborDecode(cborEncode([1, 2, 3])), [1, 2, 3]) assert.deepStrictEqual(cborDecode(cborEncode([])), []) }) test('decodes nested structures', () => { const original = { arr: [1, { nested: true }], str: 'test' } const decoded = cborDecode(cborEncode(original)) assert.deepStrictEqual(decoded, original) }) }) describe('CAR File Builder', () => { test('varint encodes small numbers', () => { assert.deepStrictEqual(varint(0), new Uint8Array([0])) assert.deepStrictEqual(varint(1), new Uint8Array([1])) assert.deepStrictEqual(varint(127), new Uint8Array([127])) }) test('varint encodes multi-byte numbers', () => { // 128 = 0x80 -> [0x80 | 0x00, 0x01] = [0x80, 0x01] assert.deepStrictEqual(varint(128), new Uint8Array([0x80, 0x01])) // 300 = 0x12c -> [0xac, 0x02] assert.deepStrictEqual(varint(300), new Uint8Array([0xac, 0x02])) }) test('base32 encode/decode roundtrip', () => { const original = new Uint8Array([0x01, 0x71, 0x12, 0x20, 0xab, 0xcd]) const encoded = base32Encode(original) const decoded = base32Decode(encoded) assert.deepStrictEqual(decoded, original) }) test('buildCarFile produces valid structure', async () => { const data = cborEncode({ test: 'data' }) const cid = await createCid(data) const cidStr = cidToString(cid) const car = buildCarFile(cidStr, [{ cid: cidStr, data }]) assert.ok(car instanceof Uint8Array) assert.ok(car.length > 0) // First byte should be varint of header length assert.ok(car[0] > 0) }) })