// Orphan cleanup deletes blobs the link table says nothing points at. A space // record is a reference the table can be missing — spaces turned on after the // records existed, a link write that failed — so the guard that cross-checks // record content has to read space records as well as public ones. import { describe, expect, it } from 'vitest'; import { PersonalDataServer } from '../packages/core/src/pds.js'; import { createFakeSpaceStorage } from './helpers/fake-space-storage.js'; const DID = 'did:plc:reapaccount23456789012'; const SPACE = 'at://did:plc:authority/space/com.example.forum/general'; const BLOB_CID = 'bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; /** @param {string} cid */ function postWith(cid) { return { $type: 'com.example.post', image: { $type: 'blob', ref: { $link: cid }, mimeType: 'image/png' }, }; } /** * A PDS holding one blob that nothing in the public repo references, and one * space record that does. * @param {{withSpaces: boolean}} opts */ function createPds({ withSpaces }) { /** @type {string[]} */ const deleted = []; const spaceRecord = { space: SPACE, collection: 'com.example.post', rkey: 'a', value: postWith(BLOB_CID), }; const pds = new PersonalDataServer({ actorStorage: /** @type {any} */ ({ async getDid() { return DID; }, async listAllRecords() { return []; }, async getRecord() { return null; }, // The link table has never heard of the space record. async getOrphanedBlobs() { return [BLOB_CID]; }, async linkBlobToRecord() {}, async deleteBlob(/** @type {string} */ cid) { deleted.push(cid); }, }), sharedStorage: /** @type {any} */ ({}), blobs: /** @type {any} */ ({ async delete(/** @type {string} */ _did, /** @type {string} */ cid) { deleted.push(cid); }, }), spaceStorage: withSpaces ? createFakeSpaceStorage({ spaces: [{ uri: SPACE }], records: { [SPACE]: [{ ...spaceRecord, cid: 'bafyspace' }] }, }) : undefined, jwtSecret: 'test-secret', }); return { pds, deleted }; } describe('orphan reaping with permissioned data', () => { it('spares a blob only a space record references', async () => { const { pds, deleted } = createPds({ withSpaces: true }); await pds.reapOrphanedBlobs(DID, Date.now() + 1000); expect(deleted).toEqual([]); }); it('counts a space record among the referenced blobs', async () => { const { pds } = createPds({ withSpaces: true }); const refs = await pds.collectReferencedBlobs(); expect(refs).toEqual([ { cid: BLOB_CID, recordUri: `${SPACE}/${DID}/com.example.post/a`, recordTime: null, }, ]); }); it('reaps the same blob when the account holds no spaces', async () => { const { pds, deleted } = createPds({ withSpaces: false }); await pds.reapOrphanedBlobs(DID, Date.now() + 1000); expect(deleted).toContain(BLOB_CID); }); });