import { buildCarFile, cborDecode, cborEncode, cborEncodeDagCbor, cidToString, createBlobCid, createCid, createTid, findBlobRefs, recordTimeOf, sniffMimeType, varint, } from '@pdsjs/core/repo'; import { describe, expect, test } from 'vitest'; 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, ]); expect(encoded).toEqual(expected); }); test('encodes null', () => { const encoded = cborEncode(null); expect(encoded).toEqual(new Uint8Array([0xf6])); }); test('encodes byte strings past the engine argument limit', () => { // A commit event carries a whole CAR file as one byte string. Spreading // it into push() throws RangeError past about 125k arguments, so a large // record used to crash createCommit. const big = new Uint8Array(300_000).fill(7); const encoded = cborEncode({ blocks: big }); const decoded = /** @type {{blocks: Uint8Array}} */ (cborDecode(encoded)); expect(decoded.blocks.length).toBe(300_000); expect(decoded.blocks[299_999]).toBe(7); }); test('encodes booleans', () => { expect(cborEncode(true)).toEqual(new Uint8Array([0xf5])); expect(cborEncode(false)).toEqual(new Uint8Array([0xf4])); }); test('encodes small integers', () => { expect(cborEncode(0)).toEqual(new Uint8Array([0x00])); expect(cborEncode(1)).toEqual(new Uint8Array([0x01])); expect(cborEncode(23)).toEqual(new Uint8Array([0x17])); }); test('encodes integers >= 24', () => { expect(cborEncode(24)).toEqual(new Uint8Array([0x18, 0x18])); expect(cborEncode(255)).toEqual(new Uint8Array([0x18, 0xff])); }); test('encodes negative integers', () => { expect(cborEncode(-1)).toEqual(new Uint8Array([0x20])); expect(cborEncode(-10)).toEqual(new Uint8Array([0x29])); }); test('encodes strings', () => { const encoded = cborEncode('hello'); // 0x65 = text string of length 5 expect(encoded).toEqual( 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 expect(encoded).toEqual(new Uint8Array([0x43, 1, 2, 3])); }); test('encodes arrays', () => { const encoded = cborEncode([1, 2, 3]); // 0x83 = array of length 3 expect(encoded).toEqual(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 }); expect(encoded1).toEqual(encoded2); // First key should be 'a' (0x61) expect(encoded1[1]).toBe(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); expect(decoded).toBe(twoTo31); // 2^32 - 1 (max unsigned 32-bit) const maxU32 = 4294967295; const encoded2 = cborEncode(maxU32); const decoded2 = cborDecode(encoded2); expect(decoded2).toBe(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); expect(encoded[0]).toBe(0x1a); // type 0 | info 26 expect(encoded[1]).toBe(0x80); expect(encoded[2]).toBe(0x00); expect(encoded[3]).toBe(0x00); expect(encoded[4]).toBe(0x00); }); }); describe('CBOR Decoding', () => { test('decodes what encode produces (roundtrip)', () => { const original = { hello: 'world', num: 42 }; const encoded = cborEncode(original); const decoded = cborDecode(encoded); expect(decoded).toEqual(original); }); test('decodes null', () => { const encoded = cborEncode(null); const decoded = cborDecode(encoded); expect(decoded).toBe(null); }); test('decodes booleans', () => { expect(cborDecode(cborEncode(true))).toBe(true); expect(cborDecode(cborEncode(false))).toBe(false); }); test('decodes integers', () => { expect(cborDecode(cborEncode(0))).toBe(0); expect(cborDecode(cborEncode(42))).toBe(42); expect(cborDecode(cborEncode(255))).toBe(255); expect(cborDecode(cborEncode(-1))).toBe(-1); expect(cborDecode(cborEncode(-10))).toBe(-10); }); test('decodes strings', () => { expect(cborDecode(cborEncode('hello'))).toBe('hello'); expect(cborDecode(cborEncode(''))).toBe(''); }); test('decodes arrays', () => { expect(cborDecode(cborEncode([1, 2, 3]))).toEqual([1, 2, 3]); expect(cborDecode(cborEncode([]))).toEqual([]); }); test('decodes nested structures', () => { const original = { arr: [1, { nested: true }], str: 'test' }; const decoded = cborDecode(cborEncode(original)); expect(decoded).toEqual(original); }); }); describe('CID Generation', () => { test('createCid uses dag-cbor codec', async () => { const data = cborEncode({ test: 'data' }); const cid = await createCid(data); expect(cid.length).toBe(36); // 2 prefix + 2 multihash header + 32 hash expect(cid[0]).toBe(0x01); // CIDv1 expect(cid[1]).toBe(0x71); // dag-cbor expect(cid[2]).toBe(0x12); // sha-256 expect(cid[3]).toBe(0x20); // 32 bytes }); test('createBlobCid uses raw codec', async () => { const data = new Uint8Array([0xff, 0xd8, 0xff, 0xe0]); // JPEG magic bytes const cid = await createBlobCid(data); expect(cid.length).toBe(36); expect(cid[0]).toBe(0x01); // CIDv1 expect(cid[1]).toBe(0x55); // raw codec expect(cid[2]).toBe(0x12); // sha-256 expect(cid[3]).toBe(0x20); // 32 bytes }); test('same bytes produce different CIDs with different codecs', async () => { const data = new Uint8Array([1, 2, 3, 4]); const dagCborCid = cidToString(await createCid(data)); const rawCid = cidToString(await createBlobCid(data)); expect(dagCborCid).not.toBe(rawCid); }); test('cidToString returns base32lower with b prefix', async () => { const data = cborEncode({ test: 'data' }); const cid = await createCid(data); const cidStr = cidToString(cid); expect(cidStr[0]).toBe('b'); expect(cidStr).toMatch(/^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)); expect(cid1).toBe(cid2); }); test('different input produces different CID', async () => { const cid1 = cidToString(await createCid(cborEncode({ a: 1 }))); const cid2 = cidToString(await createCid(cborEncode({ a: 2 }))); expect(cid1).not.toBe(cid2); }); }); describe('TID Generation', () => { test('creates 13-character TIDs', () => { const tid = createTid(); expect(tid.length).toBe(13); }); test('uses valid base32-sort characters', () => { const tid = createTid(); expect(tid).toMatch(/^[234567abcdefghijklmnopqrstuvwxyz]+$/); }); test('generates monotonically increasing TIDs', () => { const tid1 = createTid(); const tid2 = createTid(); const tid3 = createTid(); expect(tid1 < tid2).toBe(true); expect(tid2 < tid3).toBe(true); }); test('generates unique TIDs', () => { const tids = new Set(); for (let i = 0; i < 100; i++) { tids.add(createTid()); } expect(tids.size).toBe(100); }); }); describe('CAR File Builder', () => { test('varint encodes small numbers', () => { expect(varint(0)).toEqual(new Uint8Array([0])); expect(varint(1)).toEqual(new Uint8Array([1])); expect(varint(127)).toEqual(new Uint8Array([127])); }); test('varint encodes multi-byte numbers', () => { // 128 = 0x80 -> [0x80 | 0x00, 0x01] = [0x80, 0x01] expect(varint(128)).toEqual(new Uint8Array([0x80, 0x01])); // 300 = 0x12c -> [0xac, 0x02] expect(varint(300)).toEqual(new Uint8Array([0xac, 0x02])); }); 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 }]); expect(car instanceof Uint8Array).toBe(true); expect(car.length > 0).toBe(true); // First byte should be varint of header length expect(car[0] > 0).toBe(true); }); }); describe('MIME Type Sniffing', () => { test('detects JPEG', () => { const bytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10]); expect(sniffMimeType(bytes)).toBe('image/jpeg'); }); test('detects PNG', () => { const bytes = new Uint8Array([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]); expect(sniffMimeType(bytes)).toBe('image/png'); }); test('detects GIF', () => { const bytes = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]); expect(sniffMimeType(bytes)).toBe('image/gif'); }); test('detects WebP', () => { const bytes = new Uint8Array([ 0x52, 0x49, 0x46, 0x46, // RIFF 0x00, 0x00, 0x00, 0x00, // size (ignored) 0x57, 0x45, 0x42, 0x50, // WEBP ]); expect(sniffMimeType(bytes)).toBe('image/webp'); }); test('detects MP4', () => { const bytes = new Uint8Array([ 0x00, 0x00, 0x00, 0x18, // size 0x66, 0x74, 0x79, 0x70, // ftyp 0x69, 0x73, 0x6f, 0x6d, // isom brand ]); expect(sniffMimeType(bytes)).toBe('video/mp4'); }); test('detects AVIF', () => { const bytes = new Uint8Array([ 0x00, 0x00, 0x00, 0x1c, // size 0x66, 0x74, 0x79, 0x70, // ftyp 0x61, 0x76, 0x69, 0x66, // avif brand ]); expect(sniffMimeType(bytes)).toBe('image/avif'); }); test('detects HEIC', () => { const bytes = new Uint8Array([ 0x00, 0x00, 0x00, 0x18, // size 0x66, 0x74, 0x79, 0x70, // ftyp 0x68, 0x65, 0x69, 0x63, // heic brand ]); expect(sniffMimeType(bytes)).toBe('image/heic'); }); test('returns null for unknown', () => { const bytes = new Uint8Array([0x00, 0x01, 0x02, 0x03]); expect(sniffMimeType(bytes)).toBe(null); }); }); describe('Blob Ref Detection', () => { test('finds blob ref in simple object', () => { const record = { $type: 'app.bsky.feed.post', text: 'Hello', embed: { $type: 'app.bsky.embed.images', images: [ { image: { $type: 'blob', ref: { $link: 'bafkreiabc123' }, mimeType: 'image/jpeg', size: 1234, }, alt: 'test image', }, ], }, }; const refs = findBlobRefs(record); expect(refs).toEqual(['bafkreiabc123']); }); test('finds a blob ref decoded from a CAR (raw CID bytes)', async () => { // An imported record carries its blob ref as the raw CID bytes cborDecode // hands back for a tag-42 link, not `{$link}`. Both must be detected, or an // imported account's blobs never transfer. const cidBytes = await createBlobCid(new Uint8Array([1, 2, 3])); const record = { $type: 'app.bsky.actor.profile', avatar: { $type: 'blob', ref: cidBytes, mimeType: 'image/jpeg', size: 3, }, }; expect(findBlobRefs(record)).toEqual([cidToString(cidBytes)]); }); test('finds multiple blob refs', () => { const record = { images: [ { image: { $type: 'blob', ref: { $link: 'cid1' }, mimeType: 'image/png', size: 100, }, }, { image: { $type: 'blob', ref: { $link: 'cid2' }, mimeType: 'image/png', size: 200, }, }, ], }; const refs = findBlobRefs(record); expect(refs).toEqual(['cid1', 'cid2']); }); test('returns empty array when no blobs', () => { const record = { text: 'Hello world', count: 42 }; const refs = findBlobRefs(record); expect(refs).toEqual([]); }); test('handles null and primitives', () => { expect(findBlobRefs(null)).toEqual([]); expect(findBlobRefs('string')).toEqual([]); expect(findBlobRefs(42)).toEqual([]); }); test('finds legacy blob refs (pre-$type format)', () => { const record = { embed: { image: { cid: 'bafkreilegacyblob', mimeType: 'image/jpeg' }, }, }; expect(findBlobRefs(record)).toEqual(['bafkreilegacyblob']); }); test('does not treat objects with extra keys as legacy blob refs', () => { const record = { subject: { cid: 'bafyreirecordcid', mimeType: 'image/jpeg', uri: 'at://x/y/z', }, strongRef: { cid: 'bafyreirecordcid', uri: 'at://x/y/z' }, }; expect(findBlobRefs(record)).toEqual([]); }); }); describe('Record Time Detection', () => { test('reads the conventional createdAt', () => { expect(recordTimeOf({ createdAt: '2026-05-07T14:32:34.621Z' })).toBe( Date.parse('2026-05-07T14:32:34.621Z'), ); }); test('accepts timestamp fields under other names', () => { expect(recordTimeOf({ playedTime: '2025-03-01T10:00:00Z' })).toBe( Date.parse('2025-03-01T10:00:00Z'), ); expect(recordTimeOf({ publishDate: '2024-06-05T00:00:00Z' })).toBe( Date.parse('2024-06-05T00:00:00Z'), ); }); test('the earliest moment wins, so updates never mask creation', () => { expect( recordTimeOf({ createdAt: '2026-05-07T14:32:34Z', updatedAt: '2026-05-07T18:44:29Z', }), ).toBe(Date.parse('2026-05-07T14:32:34Z')); }); test('ignores moments outside the plausible window', () => { // A birthday must not date an avatar; a scheduled future event must not // pin its poster to that day. expect(recordTimeOf({ birthDate: '1985-03-12T00:00:00Z' })).toBe(null); expect(recordTimeOf({ startsAt: '2999-01-01T00:00:00Z' })).toBe(null); expect( recordTimeOf({ birthDate: '1985-03-12T00:00:00Z', createdAt: '2026-05-07T14:32:34Z', }), ).toBe(Date.parse('2026-05-07T14:32:34Z')); }); test('ignores non-time fields and malformed values', () => { expect(recordTimeOf({ format: 'flat', text: 'hello' })).toBe(null); expect(recordTimeOf({ createdAt: 'yesterday' })).toBe(null); expect(recordTimeOf({ createdAt: 1746628354621 })).toBe(null); expect(recordTimeOf(null)).toBe(null); expect(recordTimeOf(['2026-05-07T14:32:34Z'])).toBe(null); }); }); describe('CBOR integers past 32 bits', () => { // knotless starts a ref's generation at the time in milliseconds. const ref = { $type: 'dev.knotless.ref', name: 'refs/heads/main', generation: 1793736300000, }; test('encode with an 8-byte head and read back', () => { for (const encode of [cborEncode, cborEncodeDagCbor]) { expect(encode(1793736300000)).toEqual( new Uint8Array([0x1b, 0x00, 0x00, 0x01, 0xa1, 0xa3, 0x03, 0xd5, 0xe0]), ); expect(encode(2 ** 32)).toEqual( new Uint8Array([0x1b, 0, 0, 0, 1, 0, 0, 0, 0]), ); expect(cborDecode(encode(-(2 ** 40)))).toBe(-(2 ** 40)); expect(cborDecode(encode(Number.MAX_SAFE_INTEGER))).toBe( Number.MAX_SAFE_INTEGER, ); } expect(cborDecode(cborEncodeDagCbor(ref))).toEqual(ref); }); test('a value an encoder cannot write is refused, not left out', () => { for (const encode of [cborEncode, cborEncodeDagCbor]) { for (const bad of [1.5, Number.NaN, 2 ** 53, 10n, [undefined]]) { expect(() => encode({ a: bad })).toThrow(/CBOR cannot encode/); } } }); test('a record stored cut short by an earlier encoder still reads', () => { // Before 8-byte heads, the encoders wrote nothing for such an integer. // A read gives 0 for the missing value. Refusing the read made every // listing of a collection holding one fail. const whole = cborEncodeDagCbor({ ...ref, generation: 0 }); const stored = whole.subarray(0, whole.length - 1); expect(cborDecode(stored)).toEqual({ ...ref, generation: 0 }); }); });