chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// @pdsjs/core/loader tests - Repository loader from CAR files
import { beforeEach, describe, expect, it } from 'vitest';
import {
getCarRepoInfo,
loadRepositoryFromCar,
validateCarFile,
} from '../packages/core/src/loader.js';
import { buildMst } from '../packages/core/src/mst.js';
import {
buildCarFile,
CID,
cborEncodeDagCbor,
cidToBytes,
cidToString,
createCid,
createTid,
} from '../packages/core/src/repo.js';
// In-memory storage for tests
function createMockStorage() {
/** @type {Map<string, Uint8Array>} */
const blocks = new Map();
/** @type {Map<string, {cid: string, value: Uint8Array}>} */
const records = new Map();
/** @type {Array<{seq: number, cid: string, rev: string, prev: string|null}>} */
const commits = [];
/** @type {{did: string|null, handle: string|null, privateKey: Uint8Array|null, kemKey: Uint8Array|null}} */
const metadata = { did: null, handle: null, privateKey: null, kemKey: null };
return {
// Block operations
async getBlock(/** @type {string} */ cid) {
return blocks.get(cid) || null;
},
async putBlock(/** @type {string} */ cid, /** @type {Uint8Array} */ data) {
blocks.set(cid, data);
},
// Record operations
async getRecord(/** @type {string} */ uri) {
const rec = records.get(uri);
return rec || null;
},
async putRecord(
/** @type {string} */ uri,
/** @type {string} */ cid,
/** @type {string} */ _collection,
/** @type {string} */ _rkey,
/** @type {any} */ value,
) {
records.set(uri, { cid, value });
},
async listRecords(
/** @type {string} */ collection,
/** @type {any} */ _cursor,
/** @type {number} */ limit,
) {
/** @type {any[]} */
const all = [];
for (const [uri, rec] of records) {
if (uri.includes(`/${collection}/`)) {
const parts = uri.split('/');
all.push({
uri,
cid: rec.cid,
value: rec.value,
rkey: parts[parts.length - 1],
});
}
}
return { records: all.slice(0, limit), cursor: null };
},
async listAllRecords() {
const all = [];
for (const [uri, rec] of records) {
const parts = uri.replace('at://', '').split('/');
const key = `${parts[1]}/${parts[2]}`;
all.push({ key, cid: rec.cid });
}
return all;
},
async deleteRecord(/** @type {string} */ uri) {
records.delete(uri);
},
// Commit operations
async getLatestCommit() {
return commits.length > 0 ? commits[commits.length - 1] : null;
},
async putCommit(
/** @type {number} */ seq,
/** @type {string} */ cid,
/** @type {string} */ rev,
/** @type {string | null} */ prev,
) {
commits.push({ seq, cid, rev, prev });
},
// Metadata operations
async getDid() {
return metadata.did;
},
async setDid(/** @type {string} */ did) {
metadata.did = did;
},
async getHandle() {
return metadata.handle;
},
async setHandle(/** @type {string} */ handle) {
metadata.handle = handle;
},
async getPrivateKey() {
return metadata.privateKey;
},
async setPrivateKey(/** @type {Uint8Array} */ key) {
metadata.privateKey = key;
},
async getKemKey() {
return metadata.kemKey ?? null;
},
async setKemKey(/** @type {Uint8Array} */ key) {
metadata.kemKey = key;
},
async getPreferences() {
return [];
},
async setPreferences() {},
// Events
async getEvents() {
return { events: [], cursor: 0 };
},
async putEvent() {},
// Blobs
async getBlob() {
return null;
},
async putBlob() {},
async deleteBlob() {},
async listBlobs() {
return { cids: [], cursor: null };
},
async getOrphanedBlobs() {
return [];
},
async linkBlobToRecord() {},
async unlinkBlobsFromRecord() {},
// Test helpers
_getBlockCount() {
return blocks.size;
},
_getRecordCount() {
return records.size;
},
_getCommits() {
return commits;
},
};
}
/**
* Build a test CAR file with records
* @param {string} did
* @param {Array<{collection: string, rkey: string, record: object}>} recordsToInclude
* @returns {Promise<Uint8Array>}
*/
async function buildTestCar(did, recordsToInclude) {
/** @type {Array<{cid: string, data: Uint8Array}>} */
const blocks = [];
/** @type {Map<string, Uint8Array>} */
const blockMap = new Map();
// Encode records and build MST entries
/** @type {Array<{key: string, cid: string}>} */
const mstEntries = [];
for (const { collection, rkey, record } of recordsToInclude) {
const recordBytes = cborEncodeDagCbor(record);
const recordCid = cidToString(await createCid(recordBytes));
blocks.push({ cid: recordCid, data: recordBytes });
blockMap.set(recordCid, recordBytes);
mstEntries.push({ key: `${collection}/${rkey}`, cid: recordCid });
}
// Sort entries for MST
mstEntries.sort((a, b) => a.key.localeCompare(b.key));
// Build MST
const mstRoot = await buildMst(mstEntries, async (cid, data) => {
blocks.push({ cid, data });
blockMap.set(cid, data);
});
// Build commit
const rev = createTid();
const commit = {
did,
version: 3,
rev,
prev: null,
data: mstRoot ? new CID(cidToBytes(mstRoot)) : null,
};
// Add signature field (empty for test)
const signedCommit = { ...commit, sig: new Uint8Array(64) };
const commitBytes = cborEncodeDagCbor(signedCommit);
const commitCid = cidToString(await createCid(commitBytes));
blocks.push({ cid: commitCid, data: commitBytes });
return buildCarFile(commitCid, blocks);
}
describe('loadRepositoryFromCar', () => {
/** @type {ReturnType<typeof createMockStorage>} */
let storage;
beforeEach(() => {
storage = createMockStorage();
});
it('should load empty repository', async () => {
const did = 'did:plc:test123';
const car = await buildTestCar(did, []);
const result = await loadRepositoryFromCar(
car,
/** @type {any} */ (storage),
);
expect(result.did).toBe(did);
expect(result.recordCount).toBe(0);
expect(result.blockCount).toBeGreaterThan(0);
expect(await storage.getDid()).toBe(did);
});
it('should load repository with single record', async () => {
const did = 'did:plc:test123';
const car = await buildTestCar(did, [
{
collection: 'app.bsky.feed.post',
rkey: '3abc123',
record: {
$type: 'app.bsky.feed.post',
text: 'Hello World',
createdAt: '2024-01-01T00:00:00Z',
},
},
]);
const result = await loadRepositoryFromCar(
car,
/** @type {any} */ (storage),
);
expect(result.did).toBe(did);
expect(result.recordCount).toBe(1);
const record = await storage.getRecord(
`at://${did}/app.bsky.feed.post/3abc123`,
);
expect(record).not.toBeNull();
expect(record?.cid).toBeDefined();
});
it('should load repository with multiple records', async () => {
const did = 'did:plc:test456';
const car = await buildTestCar(did, [
{
collection: 'app.bsky.feed.post',
rkey: '3post1',
record: {
$type: 'app.bsky.feed.post',
text: 'Post 1',
createdAt: '2024-01-01T00:00:00Z',
},
},
{
collection: 'app.bsky.feed.post',
rkey: '3post2',
record: {
$type: 'app.bsky.feed.post',
text: 'Post 2',
createdAt: '2024-01-02T00:00:00Z',
},
},
{
collection: 'app.bsky.actor.profile',
rkey: 'self',
record: { $type: 'app.bsky.actor.profile', displayName: 'Test User' },
},
]);
const result = await loadRepositoryFromCar(
car,
/** @type {any} */ (storage),
);
expect(result.did).toBe(did);
expect(result.recordCount).toBe(3);
// Verify all records exist
expect(
await storage.getRecord(`at://${did}/app.bsky.feed.post/3post1`),
).not.toBeNull();
expect(
await storage.getRecord(`at://${did}/app.bsky.feed.post/3post2`),
).not.toBeNull();
expect(
await storage.getRecord(`at://${did}/app.bsky.actor.profile/self`),
).not.toBeNull();
});
it('should create commit in storage', async () => {
const did = 'did:plc:test789';
const car = await buildTestCar(did, [
{
collection: 'app.bsky.feed.post',
rkey: '3test',
record: {
$type: 'app.bsky.feed.post',
text: 'Test',
createdAt: '2024-01-01T00:00:00Z',
},
},
]);
const result = await loadRepositoryFromCar(
car,
/** @type {any} */ (storage),
);
const commit = await storage.getLatestCommit();
expect(commit).not.toBeNull();
expect(commit?.cid).toBe(result.commitCid);
expect(commit?.rev).toBe(result.rev);
expect(commit?.seq).toBe(1);
});
});
describe('getCarRepoInfo', () => {
it('should extract DID and commit info', async () => {
const did = 'did:plc:infotest';
const car = await buildTestCar(did, [
{
collection: 'app.bsky.feed.post',
rkey: '3test',
record: {
$type: 'app.bsky.feed.post',
text: 'Test',
createdAt: '2024-01-01T00:00:00Z',
},
},
]);
const info = getCarRepoInfo(car);
expect(info.did).toBe(did);
expect(info.commitCid).toBeDefined();
expect(info.rev).toBeDefined();
});
});
describe('validateCarFile', () => {
it('should validate correct CAR file', async () => {
const did = 'did:plc:validtest';
const car = await buildTestCar(did, []);
const result = validateCarFile(car);
expect(result.valid).toBe(true);
expect(result.did).toBe(did);
expect(result.error).toBeUndefined();
});
it('should reject malformed CAR', () => {
const garbage = new Uint8Array([0xff, 0xff, 0xff]);
const result = validateCarFile(garbage);
expect(result.valid).toBe(false);
expect(result.error).toBeDefined();
});
});