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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
123 lines · 3.9 KB Raw History
// Writing a pack and a bundle, read back by the parsers that read git's own.
// A round trip is the whole test: a pack this writes has to index, and a
// bundle it writes has to parse, exactly as one git produced does.

import { describe, expect, it } from 'vitest';
import { parseBundle } from '../src/bundle.js';
import {
  indexPacks,
  objectId,
  parseCommit,
  parseTree,
} from '../src/objects.js';
import { writeBundle, writePack } from '../src/pack-write.js';

const encoder = new TextEncoder();

/** @param {string} type @param {Uint8Array} data */
const object = (type, data) => ({ type, data, sha: objectId(type, data) });

/**
 * One object's bytes out of an index, for a test that has just packed it and
 * knows it is there.
 * @param {Map<string, {data: Uint8Array}>} objects @param {string} sha
 */
const at = (objects, sha) =>
  /** @type {{data: Uint8Array}} */ (objects.get(sha)).data;

const blob = object('blob', encoder.encode('hello\n'));

/**
 * A tree holding one file, in git's own entry format.
 * @param {string} name @param {string} mode @param {string} sha
 */
function treeOf(name, mode, sha) {
  const prefix = encoder.encode(`${mode} ${name}\0`);
  const raw = Uint8Array.from(
    (sha.match(/../g) ?? []).map((/** @type {string} */ pair) =>
      Number.parseInt(pair, 16),
    ),
  );
  const data = new Uint8Array(prefix.length + raw.length);
  data.set(prefix, 0);
  data.set(raw, prefix.length);
  return object('tree', data);
}

const tree = treeOf('greeting.txt', '100644', blob.sha);
const commit = object(
  'commit',
  encoder.encode(
    [
      `tree ${tree.sha}`,
      'author Bob <bob@example.com> 1756000000 +0000',
      'committer Bob <bob@example.com> 1756000000 +0000',
      '',
      'say hello',
      '',
    ].join('\n'),
  ),
);

describe('writePack', () => {
  it('writes a pack the object index reads back whole', async () => {
    const pack = writePack([commit, tree, blob]);
    const objects = await indexPacks([pack]);

    expect(objects.get(commit.sha)?.type).toBe('commit');
    expect(objects.get(tree.sha)?.type).toBe('tree');
    expect(new TextDecoder().decode(objects.get(blob.sha)?.data)).toBe(
      'hello\n',
    );
    expect(parseCommit(at(objects, commit.sha)).tree).toBe(tree.sha);
    expect(parseTree(at(objects, tree.sha))[0].sha).toBe(blob.sha);
  });

  it('carries a size past the four bits the first byte holds', async () => {
    // 4096 bytes needs three header bytes, which is the continuation the
    // one-file case above never exercises.
    const big = object('blob', new Uint8Array(4096).fill(0x61));
    const objects = await indexPacks([writePack([big])]);
    expect(objects.get(big.sha)?.data.length).toBe(4096);
  });

  it('writes a pack of nothing that still indexes', async () => {
    expect((await indexPacks([writePack([])])).size).toBe(0);
  });

  it('refuses an object type a pack has no code for', () => {
    expect(() => writePack([{ type: 'ofs-delta', data: blob.data }])).toThrow(
      /cannot pack/,
    );
  });
});

describe('writeBundle', () => {
  it('writes a bundle the parser reads back, pack and all', async () => {
    const pack = writePack([commit, tree, blob]);
    const parsed = parseBundle(
      writeBundle({
        refs: [{ sha: commit.sha, name: 'refs/heads/main' }],
        prerequisites: ['c'.repeat(40)],
        pack,
      }),
    );

    expect(parsed.version).toBe(2);
    expect(parsed.refs).toEqual([{ sha: commit.sha, name: 'refs/heads/main' }]);
    expect(parsed.prerequisites).toEqual(['c'.repeat(40)]);
    expect((await indexPacks([parsed.pack])).get(commit.sha)?.type).toBe(
      'commit',
    );
  });

  it('writes a bundle with no prerequisites, which is a full one', () => {
    const parsed = parseBundle(
      writeBundle({
        refs: [{ sha: commit.sha, name: 'refs/heads/main' }],
        pack: writePack([commit, tree, blob]),
      }),
    );
    expect(parsed.prerequisites).toEqual([]);
  });
});