chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// 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([]);
});
});