this repo has no description
1import { test, describe } from 'node:test' 2import assert from 'node:assert' 3import { 4 cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid, 5 generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes, 6 getKeyDepth, varint, base32Decode, buildCarFile 7} from '../src/pds.js' 8 9describe('CBOR Encoding', () => { 10 test('encodes simple map', () => { 11 const encoded = cborEncode({ hello: 'world', num: 42 }) 12 // Expected: a2 65 68 65 6c 6c 6f 65 77 6f 72 6c 64 63 6e 75 6d 18 2a 13 const expected = new Uint8Array([ 14 0xa2, 0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x65, 0x77, 0x6f, 0x72, 0x6c, 0x64, 15 0x63, 0x6e, 0x75, 0x6d, 0x18, 0x2a 16 ]) 17 assert.deepStrictEqual(encoded, expected) 18 }) 19 20 test('encodes null', () => { 21 const encoded = cborEncode(null) 22 assert.deepStrictEqual(encoded, new Uint8Array([0xf6])) 23 }) 24 25 test('encodes booleans', () => { 26 assert.deepStrictEqual(cborEncode(true), new Uint8Array([0xf5])) 27 assert.deepStrictEqual(cborEncode(false), new Uint8Array([0xf4])) 28 }) 29 30 test('encodes small integers', () => { 31 assert.deepStrictEqual(cborEncode(0), new Uint8Array([0x00])) 32 assert.deepStrictEqual(cborEncode(1), new Uint8Array([0x01])) 33 assert.deepStrictEqual(cborEncode(23), new Uint8Array([0x17])) 34 }) 35 36 test('encodes integers >= 24', () => { 37 assert.deepStrictEqual(cborEncode(24), new Uint8Array([0x18, 0x18])) 38 assert.deepStrictEqual(cborEncode(255), new Uint8Array([0x18, 0xff])) 39 }) 40 41 test('encodes negative integers', () => { 42 assert.deepStrictEqual(cborEncode(-1), new Uint8Array([0x20])) 43 assert.deepStrictEqual(cborEncode(-10), new Uint8Array([0x29])) 44 }) 45 46 test('encodes strings', () => { 47 const encoded = cborEncode('hello') 48 // 0x65 = text string of length 5 49 assert.deepStrictEqual(encoded, new Uint8Array([0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f])) 50 }) 51 52 test('encodes byte strings', () => { 53 const bytes = new Uint8Array([1, 2, 3]) 54 const encoded = cborEncode(bytes) 55 // 0x43 = byte string of length 3 56 assert.deepStrictEqual(encoded, new Uint8Array([0x43, 1, 2, 3])) 57 }) 58 59 test('encodes arrays', () => { 60 const encoded = cborEncode([1, 2, 3]) 61 // 0x83 = array of length 3 62 assert.deepStrictEqual(encoded, new Uint8Array([0x83, 0x01, 0x02, 0x03])) 63 }) 64 65 test('sorts map keys deterministically', () => { 66 const encoded1 = cborEncode({ z: 1, a: 2 }) 67 const encoded2 = cborEncode({ a: 2, z: 1 }) 68 assert.deepStrictEqual(encoded1, encoded2) 69 // First key should be 'a' (0x61) 70 assert.strictEqual(encoded1[1], 0x61) 71 }) 72}) 73 74describe('Base32 Encoding', () => { 75 test('encodes bytes to base32lower', () => { 76 const bytes = new Uint8Array([0x01, 0x71, 0x12, 0x20]) 77 const encoded = base32Encode(bytes) 78 assert.strictEqual(typeof encoded, 'string') 79 assert.match(encoded, /^[a-z2-7]+$/) 80 }) 81}) 82 83describe('CID Generation', () => { 84 test('creates CIDv1 with dag-cbor codec', async () => { 85 const data = cborEncode({ test: 'data' }) 86 const cid = await createCid(data) 87 88 assert.strictEqual(cid.length, 36) // 2 prefix + 2 multihash header + 32 hash 89 assert.strictEqual(cid[0], 0x01) // CIDv1 90 assert.strictEqual(cid[1], 0x71) // dag-cbor 91 assert.strictEqual(cid[2], 0x12) // sha-256 92 assert.strictEqual(cid[3], 0x20) // 32 bytes 93 }) 94 95 test('cidToString returns base32lower with b prefix', async () => { 96 const data = cborEncode({ test: 'data' }) 97 const cid = await createCid(data) 98 const cidStr = cidToString(cid) 99 100 assert.strictEqual(cidStr[0], 'b') 101 assert.match(cidStr, /^b[a-z2-7]+$/) 102 }) 103 104 test('same input produces same CID', async () => { 105 const data1 = cborEncode({ test: 'data' }) 106 const data2 = cborEncode({ test: 'data' }) 107 const cid1 = cidToString(await createCid(data1)) 108 const cid2 = cidToString(await createCid(data2)) 109 110 assert.strictEqual(cid1, cid2) 111 }) 112 113 test('different input produces different CID', async () => { 114 const cid1 = cidToString(await createCid(cborEncode({ a: 1 }))) 115 const cid2 = cidToString(await createCid(cborEncode({ a: 2 }))) 116 117 assert.notStrictEqual(cid1, cid2) 118 }) 119}) 120 121describe('TID Generation', () => { 122 test('creates 13-character TIDs', () => { 123 const tid = createTid() 124 assert.strictEqual(tid.length, 13) 125 }) 126 127 test('uses valid base32-sort characters', () => { 128 const tid = createTid() 129 assert.match(tid, /^[234567abcdefghijklmnopqrstuvwxyz]+$/) 130 }) 131 132 test('generates monotonically increasing TIDs', () => { 133 const tid1 = createTid() 134 const tid2 = createTid() 135 const tid3 = createTid() 136 137 assert.ok(tid1 < tid2, `${tid1} should be less than ${tid2}`) 138 assert.ok(tid2 < tid3, `${tid2} should be less than ${tid3}`) 139 }) 140 141 test('generates unique TIDs', () => { 142 const tids = new Set() 143 for (let i = 0; i < 100; i++) { 144 tids.add(createTid()) 145 } 146 assert.strictEqual(tids.size, 100) 147 }) 148}) 149 150describe('P-256 Signing', () => { 151 test('generates key pair with correct sizes', async () => { 152 const kp = await generateKeyPair() 153 154 assert.strictEqual(kp.privateKey.length, 32) 155 assert.strictEqual(kp.publicKey.length, 33) // compressed 156 assert.ok(kp.publicKey[0] === 0x02 || kp.publicKey[0] === 0x03) 157 }) 158 159 test('can sign data with generated key', async () => { 160 const kp = await generateKeyPair() 161 const key = await importPrivateKey(kp.privateKey) 162 const data = new TextEncoder().encode('test message') 163 const sig = await sign(key, data) 164 165 assert.strictEqual(sig.length, 64) // r (32) + s (32) 166 }) 167 168 test('different messages produce different signatures', async () => { 169 const kp = await generateKeyPair() 170 const key = await importPrivateKey(kp.privateKey) 171 172 const sig1 = await sign(key, new TextEncoder().encode('message 1')) 173 const sig2 = await sign(key, new TextEncoder().encode('message 2')) 174 175 assert.notDeepStrictEqual(sig1, sig2) 176 }) 177 178 test('bytesToHex and hexToBytes roundtrip', () => { 179 const original = new Uint8Array([0x00, 0x0f, 0xf0, 0xff, 0xab, 0xcd]) 180 const hex = bytesToHex(original) 181 const back = hexToBytes(hex) 182 183 assert.strictEqual(hex, '000ff0ffabcd') 184 assert.deepStrictEqual(back, original) 185 }) 186}) 187 188describe('MST Key Depth', () => { 189 test('returns a non-negative integer', () => { 190 const depth = getKeyDepth('app.bsky.feed.post/abc123') 191 assert.strictEqual(typeof depth, 'number') 192 assert.ok(depth >= 0) 193 }) 194 195 test('is deterministic for same key', () => { 196 const key = 'app.bsky.feed.post/test123' 197 const depth1 = getKeyDepth(key) 198 const depth2 = getKeyDepth(key) 199 assert.strictEqual(depth1, depth2) 200 }) 201 202 test('different keys can have different depths', () => { 203 // Generate many keys and check we get some variation 204 const depths = new Set() 205 for (let i = 0; i < 100; i++) { 206 depths.add(getKeyDepth(`collection/key${i}`)) 207 } 208 // Should have at least 1 unique depth (realistically more) 209 assert.ok(depths.size >= 1) 210 }) 211 212 test('handles empty string', () => { 213 const depth = getKeyDepth('') 214 assert.strictEqual(typeof depth, 'number') 215 assert.ok(depth >= 0) 216 }) 217 218 test('handles unicode strings', () => { 219 const depth = getKeyDepth('app.bsky.feed.post/émoji🎉') 220 assert.strictEqual(typeof depth, 'number') 221 assert.ok(depth >= 0) 222 }) 223}) 224 225describe('CBOR Decoding', () => { 226 test('decodes what encode produces (roundtrip)', () => { 227 const original = { hello: 'world', num: 42 } 228 const encoded = cborEncode(original) 229 const decoded = cborDecode(encoded) 230 assert.deepStrictEqual(decoded, original) 231 }) 232 233 test('decodes null', () => { 234 const encoded = cborEncode(null) 235 const decoded = cborDecode(encoded) 236 assert.strictEqual(decoded, null) 237 }) 238 239 test('decodes booleans', () => { 240 assert.strictEqual(cborDecode(cborEncode(true)), true) 241 assert.strictEqual(cborDecode(cborEncode(false)), false) 242 }) 243 244 test('decodes integers', () => { 245 assert.strictEqual(cborDecode(cborEncode(0)), 0) 246 assert.strictEqual(cborDecode(cborEncode(42)), 42) 247 assert.strictEqual(cborDecode(cborEncode(255)), 255) 248 assert.strictEqual(cborDecode(cborEncode(-1)), -1) 249 assert.strictEqual(cborDecode(cborEncode(-10)), -10) 250 }) 251 252 test('decodes strings', () => { 253 assert.strictEqual(cborDecode(cborEncode('hello')), 'hello') 254 assert.strictEqual(cborDecode(cborEncode('')), '') 255 }) 256 257 test('decodes arrays', () => { 258 assert.deepStrictEqual(cborDecode(cborEncode([1, 2, 3])), [1, 2, 3]) 259 assert.deepStrictEqual(cborDecode(cborEncode([])), []) 260 }) 261 262 test('decodes nested structures', () => { 263 const original = { arr: [1, { nested: true }], str: 'test' } 264 const decoded = cborDecode(cborEncode(original)) 265 assert.deepStrictEqual(decoded, original) 266 }) 267}) 268 269describe('CAR File Builder', () => { 270 test('varint encodes small numbers', () => { 271 assert.deepStrictEqual(varint(0), new Uint8Array([0])) 272 assert.deepStrictEqual(varint(1), new Uint8Array([1])) 273 assert.deepStrictEqual(varint(127), new Uint8Array([127])) 274 }) 275 276 test('varint encodes multi-byte numbers', () => { 277 // 128 = 0x80 -> [0x80 | 0x00, 0x01] = [0x80, 0x01] 278 assert.deepStrictEqual(varint(128), new Uint8Array([0x80, 0x01])) 279 // 300 = 0x12c -> [0xac, 0x02] 280 assert.deepStrictEqual(varint(300), new Uint8Array([0xac, 0x02])) 281 }) 282 283 test('base32 encode/decode roundtrip', () => { 284 const original = new Uint8Array([0x01, 0x71, 0x12, 0x20, 0xab, 0xcd]) 285 const encoded = base32Encode(original) 286 const decoded = base32Decode(encoded) 287 assert.deepStrictEqual(decoded, original) 288 }) 289 290 test('buildCarFile produces valid structure', async () => { 291 const data = cborEncode({ test: 'data' }) 292 const cid = await createCid(data) 293 const cidStr = cidToString(cid) 294 295 const car = buildCarFile(cidStr, [{ cid: cidStr, data }]) 296 297 assert.ok(car instanceof Uint8Array) 298 assert.ok(car.length > 0) 299 // First byte should be varint of header length 300 assert.ok(car[0] > 0) 301 }) 302})