// The git extension against a fake host: the write guard reads the config // record from the public repo or a space, and smart HTTP mounts only when // asked for. import { cborEncodeDagCbor } from '@pdsjs/core/repo'; import { describe, expect, it, vi } from 'vitest'; import { createGitExtension, createGitGuardExtension, } from '../src/extension.js'; const DID = 'did:plc:gitextension'; const SPACE = 'at://did:plc:gitextension/dev.pdsjs.space/abc'; const CONFIG = 'dev.pdsjs.git.config'; /** * @param {Object} [options] * @param {boolean} [options.withSpaces] */ function createHost({ withSpaces = false } = {}) { const records = new Map([ [ `at://${DID}/${CONFIG}/repo`, cborEncodeDagCbor({ protectedBranches: ['main'] }), ], ]); const spaceRecords = new Map([ [`${CONFIG}/repo`, cborEncodeDagCbor({ protectedBranches: ['release'] })], ]); const host = { actorStorage: { getRecord: vi.fn(async (/** @type {string} */ uri) => { const value = records.get(uri); return value ? { value } : null; }), }, blobs: { get: vi.fn(async () => null) }, spaceStorage: withSpaces ? { getSpaceRecord: vi.fn( async ( /** @type {string} */ _space, /** @type {string} */ collection, /** @type {string} */ rkey, ) => { const value = spaceRecords.get(`${collection}/${rkey}`); return value ? { cid: 'bafy', value } : null; }, ), } : undefined, readOnly: false, getDid: async () => DID, xrpc: vi.fn(async () => new Response('{}')), createCommit: vi.fn(), accountApi: vi.fn(), }; return /** @type {import('@pdsjs/core/ports').ExtensionHost} */ ( /** @type {unknown} */ (host) ); } /** @param {string} branch @param {string} sha */ const repo = (branch, sha) => ({ name: 'repo', createdAt: '2026-01-01T00:00:00.000Z', refs: [{ name: `refs/heads/${branch}`, sha }], bundles: [], }); const emptyRepo = { name: 'repo', createdAt: '2026-01-01T00:00:00.000Z', refs: [], bundles: [], }; describe('createGitExtension', () => { it('guards repo and config writes without any option', () => { const extension = createGitExtension()(createHost()); expect(extension.name).toBe('git'); expect(extension.writeGuard?.collections).toEqual([ 'dev.pdsjs.git.repo', CONFIG, ]); expect(extension.handle).toBeUndefined(); expect(extension.prefixes).toBeUndefined(); }); it('registers the XRPC methods, the bundle-chain reads with a browser', () => { const routes = Object.keys(createGitExtension()(createHost()).routes ?? {}); expect(routes).toContain('/xrpc/dev.pdsjs.git.listRepos'); expect(routes).toContain('/xrpc/dev.pdsjs.git.getTree'); const noBrowse = Object.keys( createGitExtension({ browse: false })(createHost()).routes ?? {}, ); expect(noBrowse).toContain('/xrpc/dev.pdsjs.git.listRepos'); expect(noBrowse).not.toContain('/xrpc/dev.pdsjs.git.getTree'); }); it('serves smart HTTP under /git when asked', async () => { const host = createHost(); const extension = createGitExtension({ http: true })(host); expect(extension.prefixes).toEqual(['/git']); const miss = await extension.handle?.( new Request('https://pds.example.com/other'), new URL('https://pds.example.com/other'), ); expect(miss).toBeNull(); }); it('reads the public config record to refuse a protected ref deletion', async () => { const host = createHost(); const guard = createGitExtension()(host).writeGuard; await expect( guard?.check({ space: null, collection: 'dev.pdsjs.git.repo', rkey: 'repo', prevValue: repo('main', 'a'.repeat(40)), nextValue: emptyRepo, }), ).rejects.toThrow(/protected/); expect(host.actorStorage.getRecord).toHaveBeenCalledWith( `at://${DID}/${CONFIG}/repo`, ); }); it('reads a space config record through the host space storage', async () => { const host = createHost({ withSpaces: true }); const guard = createGitExtension()(host).writeGuard; await expect( guard?.check({ space: SPACE, collection: 'dev.pdsjs.git.repo', rkey: 'repo', prevValue: repo('release', 'a'.repeat(40)), nextValue: emptyRepo, }), ).rejects.toThrow(/protected/); expect(host.spaceStorage?.getSpaceRecord).toHaveBeenCalledWith( SPACE, CONFIG, 'repo', ); }); it('holds no rules for a space write when spaces are off', async () => { const guard = createGitExtension()(createHost()).writeGuard; await expect( guard?.check({ space: SPACE, collection: 'dev.pdsjs.git.repo', rkey: 'repo', prevValue: repo('release', 'a'.repeat(40)), nextValue: emptyRepo, }), ).resolves.toBeUndefined(); }); }); describe('createGitGuardExtension', () => { it('carries the write guard and nothing else', () => { const host = createHost(); const extension = createGitGuardExtension()(host); expect(extension.name).toBe('git'); expect(extension.writeGuard).toBeDefined(); expect(extension.routes).toBeUndefined(); expect(extension.handle).toBeUndefined(); expect(extension.prefixes).toBeUndefined(); expect(extension.collections).toContain('dev.pdsjs.git.repo'); }); it('enforces branch protection at the record write', async () => { const host = createHost(); const guard = createGitGuardExtension()(host).writeGuard; await expect( guard?.check({ space: null, collection: 'dev.pdsjs.git.repo', rkey: 'repo', prevValue: repo('main', 'a'.repeat(40)), nextValue: emptyRepo, }), ).rejects.toThrow(/protected/); expect(host.actorStorage.getRecord).toHaveBeenCalledWith( `at://${DID}/${CONFIG}/repo`, ); }); });