~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
155 lines · 4.9 KB Raw History
// @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);
  });
});