// @pdsjs/readonly/build-car tests import { describe, expect, it } from 'vitest'; import { parseCarFile } from '../packages/core/src/car.js'; import { generateKeyPair } from '../packages/core/src/crypto.js'; import { loadRepositoryFromCar } from '../packages/core/src/loader.js'; import { cborDecode } from '../packages/core/src/repo.js'; import { buildRepositoryCar } from '../packages/readonly/src/build-car.js'; // Mock storage for loading the CAR function createMockStorage() { const blocks = new Map(); const records = new Map(); /** @type {any[]} */ const commits = []; const metadata = { did: null }; return { async getBlock(/** @type {any} */ cid) { return blocks.get(cid) || null; }, async putBlock(/** @type {any} */ cid, /** @type {any} */ data) { blocks.set(cid, data); }, async getRecord(/** @type {any} */ uri) { const rec = records.get(uri); return rec || null; }, async putRecord( /** @type {any} */ uri, /** @type {any} */ cid, /** @type {any} */ _collection, /** @type {any} */ _rkey, /** @type {any} */ value, ) { records.set(uri, { cid, value }); }, async listRecords() { return { records: [], cursor: null }; }, async listAllRecords() { return []; }, async deleteRecord() {}, async getLatestCommit() { return commits.length > 0 ? commits[commits.length - 1] : null; }, async putCommit( /** @type {any} */ seq, /** @type {any} */ cid, /** @type {any} */ rev, /** @type {any} */ prev, ) { commits.push({ seq, cid, rev, prev }); }, async getEvents() { return { events: [], cursor: 0 }; }, async putEvent() {}, async getBlob() { return null; }, async putBlob() {}, async deleteBlob() {}, async listBlobs() { return { cids: [], cursor: null }; }, async getOrphanedBlobs() { return []; }, async linkBlobToRecord() {}, async unlinkBlobsFromRecord() {}, async getDid() { return metadata.did; }, async setDid(/** @type {any} */ did) { metadata.did = did; }, async getHandle() { return null; }, async setHandle() {}, async getPrivateKey() { return null; }, async setPrivateKey() {}, async getKemKey() { return null; }, async setKemKey() {}, async getPreferences() { return []; }, async setPreferences() {}, _getRecords() { return records; }, _getCommits() { return commits; }, }; } describe('buildRepositoryCar', () => { it('should build a valid CAR with a single record', async () => { const { privateKey } = await generateKeyPair(); const did = 'did:plc:testbuild123'; const { carBytes, commitCid, rev } = await buildRepositoryCar({ did, privateKey, records: [ { collection: 'app.bsky.feed.post', rkey: '3abc123', record: { $type: 'app.bsky.feed.post', text: 'Hello World!', createdAt: '2024-01-01T00:00:00Z', }, }, ], }); expect(carBytes).toBeInstanceOf(Uint8Array); expect(carBytes.length).toBeGreaterThan(0); expect(commitCid).toBeDefined(); expect(rev).toBeDefined(); }); it('should build a valid CAR with multiple records', async () => { const { privateKey } = await generateKeyPair(); const did = 'did:plc:testmulti456'; const { carBytes, commitCid } = await buildRepositoryCar({ did, privateKey, records: [ { 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' }, }, ], }); expect(carBytes).toBeInstanceOf(Uint8Array); expect(commitCid).toBeDefined(); }); it('should produce a CAR that can be parsed', async () => { const { privateKey } = await generateKeyPair(); const did = 'did:plc:testparse789'; const { carBytes, commitCid } = await buildRepositoryCar({ did, privateKey, records: [ { collection: 'app.bsky.feed.post', rkey: '3test', record: { $type: 'app.bsky.feed.post', text: 'Test', createdAt: '2024-01-01T00:00:00Z', }, }, ], }); // Parse the CAR const parsed = parseCarFile(carBytes); expect(parsed.roots).toHaveLength(1); expect(parsed.roots[0]).toBe(commitCid); expect(parsed.blocks.size).toBeGreaterThan(0); // Verify commit structure const commitBytes = parsed.blocks.get(commitCid); expect(commitBytes).toBeDefined(); const commit = cborDecode(/** @type {Uint8Array} */ (commitBytes)); expect(commit.did).toBe(did); expect(commit.version).toBe(3); expect(commit.sig).toBeDefined(); expect(commit.sig).toBeInstanceOf(Uint8Array); expect(commit.sig.length).toBe(64); // ECDSA P-256 signature }); it('should produce a CAR that can be loaded into storage', async () => { const { privateKey } = await generateKeyPair(); const did = 'did:plc:testload101'; const { carBytes } = await buildRepositoryCar({ did, privateKey, records: [ { collection: 'app.bsky.feed.post', rkey: '3loadtest', record: { $type: 'app.bsky.feed.post', text: 'Load Test', createdAt: '2024-01-01T00:00:00Z', }, }, ], }); // Load into storage const storage = createMockStorage(); const result = await loadRepositoryFromCar( carBytes, /** @type {any} */ (storage), ); expect(result.did).toBe(did); expect(result.recordCount).toBe(1); expect(await storage.getDid()).toBe(did); // Verify record was stored const record = await storage.getRecord( `at://${did}/app.bsky.feed.post/3loadtest`, ); expect(record).not.toBeNull(); expect(record?.cid).toBeDefined(); // Verify commit was stored const commit = await storage.getLatestCommit(); expect(commit).not.toBeNull(); expect(commit?.cid).toBe(result.commitCid); }); it('should handle empty records gracefully', async () => { const { privateKey } = await generateKeyPair(); const did = 'did:plc:testempty'; // Even with no records, should produce valid CAR with just commit const { carBytes, commitCid } = await buildRepositoryCar({ did, privateKey, records: [], }); expect(carBytes).toBeInstanceOf(Uint8Array); expect(commitCid).toBeDefined(); const parsed = parseCarFile(carBytes); expect(parsed.roots[0]).toBe(commitCid); }); it('should produce deterministic MST structure', async () => { const { privateKey } = await generateKeyPair(); const did = 'did:plc:testdeterministic'; // Records in different order should produce same MST const records = [ { collection: 'c/b', rkey: 'z', record: { val: 3 } }, { collection: 'a/a', rkey: 'x', record: { val: 1 } }, { collection: 'b/a', rkey: 'y', record: { val: 2 } }, ]; const { carBytes: car1 } = await buildRepositoryCar({ did, privateKey, records: [...records], }); // Reverse order const { carBytes: car2 } = await buildRepositoryCar({ did, privateKey, records: [...records].reverse(), }); // Parse both and compare MST structure (not commits - revisions differ) const parsed1 = parseCarFile(car1); const parsed2 = parseCarFile(car2); // Both should have same number of blocks (records + MST nodes + commit) expect(parsed1.blocks.size).toBe(parsed2.blocks.size); }); });