// @pdsjs/core/car tests - CAR file parser import { describe, expect, it } from 'vitest'; import { getCarHeader, iterateCarBlocks, parseCarFile, readVarint, } from '../packages/core/src/car.js'; import { buildCarFile, cborEncodeDagCbor, cidToString, createCid, } from '../packages/core/src/repo.js'; describe('readVarint', () => { it('should decode single-byte varints', () => { // 0x00 = 0 expect(readVarint(new Uint8Array([0x00]), 0)).toEqual([0, 1]); // 0x01 = 1 expect(readVarint(new Uint8Array([0x01]), 0)).toEqual([1, 1]); // 0x7f = 127 expect(readVarint(new Uint8Array([0x7f]), 0)).toEqual([127, 1]); }); it('should decode multi-byte varints', () => { // 0x80 0x01 = 128 expect(readVarint(new Uint8Array([0x80, 0x01]), 0)).toEqual([128, 2]); // 0xff 0x01 = 255 expect(readVarint(new Uint8Array([0xff, 0x01]), 0)).toEqual([255, 2]); // 0xac 0x02 = 300 expect(readVarint(new Uint8Array([0xac, 0x02]), 0)).toEqual([300, 2]); }); it('should decode varint at offset', () => { const bytes = new Uint8Array([0xff, 0x7f, 0xac, 0x02, 0x00]); expect(readVarint(bytes, 2)).toEqual([300, 4]); }); it('should throw on unterminated varint', () => { expect(() => readVarint(new Uint8Array([0x80]), 0)).toThrow( 'Unexpected end of varint', ); }); }); describe('parseCarFile', () => { it('should roundtrip with buildCarFile - single block', async () => { // Create a test block const record = { $type: 'app.bsky.feed.post', text: 'Hello World' }; const recordBytes = cborEncodeDagCbor(record); const recordCid = cidToString(await createCid(recordBytes)); // Build CAR file const car = buildCarFile(recordCid, [ { cid: recordCid, data: recordBytes }, ]); // Parse it back const parsed = parseCarFile(car); expect(parsed.roots).toHaveLength(1); expect(parsed.roots[0]).toBe(recordCid); expect(parsed.blocks.size).toBe(1); expect(parsed.blocks.has(recordCid)).toBe(true); expect(parsed.blocks.get(recordCid)).toEqual(recordBytes); }); it('should roundtrip with buildCarFile - multiple blocks', async () => { // Create multiple test blocks const block1 = cborEncodeDagCbor({ text: 'Block 1' }); const cid1 = cidToString(await createCid(block1)); const block2 = cborEncodeDagCbor({ text: 'Block 2' }); const cid2 = cidToString(await createCid(block2)); const block3 = cborEncodeDagCbor({ text: 'Block 3' }); const cid3 = cidToString(await createCid(block3)); // Build CAR file with first block as root const car = buildCarFile(cid1, [ { cid: cid1, data: block1 }, { cid: cid2, data: block2 }, { cid: cid3, data: block3 }, ]); // Parse it back const parsed = parseCarFile(car); expect(parsed.roots).toHaveLength(1); expect(parsed.roots[0]).toBe(cid1); expect(parsed.blocks.size).toBe(3); expect(parsed.blocks.get(cid1)).toEqual(block1); expect(parsed.blocks.get(cid2)).toEqual(block2); expect(parsed.blocks.get(cid3)).toEqual(block3); }); it('should throw on invalid CAR version', () => { // Create a malformed CAR with version 2 const invalidHeader = cborEncodeDagCbor({ version: 2, roots: [] }); const headerLenBytes = new Uint8Array([invalidHeader.length]); const car = new Uint8Array(headerLenBytes.length + invalidHeader.length); car.set(headerLenBytes, 0); car.set(invalidHeader, headerLenBytes.length); expect(() => parseCarFile(car)).toThrow('Unsupported CAR version: 2'); }); }); describe('iterateCarBlocks', () => { it('should iterate blocks in order', async () => { // Create test blocks const block1 = cborEncodeDagCbor({ index: 1 }); const cid1 = cidToString(await createCid(block1)); const block2 = cborEncodeDagCbor({ index: 2 }); const cid2 = cidToString(await createCid(block2)); // Build CAR file const car = buildCarFile(cid1, [ { cid: cid1, data: block1 }, { cid: cid2, data: block2 }, ]); // Iterate blocks const blocks = []; for (const block of iterateCarBlocks(car)) { blocks.push(block); } expect(blocks).toHaveLength(2); expect(blocks[0].cid).toBe(cid1); expect(blocks[0].data).toEqual(block1); expect(blocks[1].cid).toBe(cid2); expect(blocks[1].data).toEqual(block2); }); }); describe('getCarHeader', () => { it('should extract header without parsing blocks', async () => { // Create a large block (to verify we don't parse it) const largeData = cborEncodeDagCbor({ data: 'x'.repeat(10000) }); const largeCid = cidToString(await createCid(largeData)); // Build CAR file const car = buildCarFile(largeCid, [{ cid: largeCid, data: largeData }]); // Get header only const header = getCarHeader(car); expect(header.version).toBe(1); expect(header.roots).toHaveLength(1); expect(header.roots[0]).toBe(largeCid); }); });