import { CID, cborEncodeDagCbor, cidToString, createBlobCid, createCid, } from '@pdsjs/core/repo'; import { describe, expect, it } from 'vitest'; import { createSiteBrowser } from '../src/browser.js'; const DID = 'did:plc:browsertest'; /** @param {Array<{name: string, files: Array<{path: string, size: number}>, createdAt?: string}>} sites */ async function storageWith(sites) { /** @type {Array<{uri: string, cid: string, value: Uint8Array}>} */ const rows = []; for (const site of sites) { const files = []; for (const file of site.files) { const data = new Uint8Array(file.size); files.push({ path: file.path, blob: { $type: 'blob', ref: new CID(await createBlobCid(data)), mimeType: 'application/octet-stream', size: file.size, }, }); } const value = cborEncodeDagCbor({ $type: 'dev.pdsjs.site.deploy', files, createdAt: site.createdAt ?? '2026-08-06T00:00:00.000Z', }); rows.push({ uri: `at://${DID}/dev.pdsjs.site.deploy/${site.name}`, cid: cidToString(await createCid(value)), value, }); } return /** @type {any} */ ({ listRecords: async ( /** @type {string} */ _collection, /** @type {string|null} */ cursor, /** @type {number} */ limit, ) => { const start = cursor ? Number(cursor) : 0; const records = rows.slice(start, start + limit); const next = start + records.length; return { records, cursor: next < rows.length ? String(next) : null }; }, getRecord: async (/** @type {string} */ uri) => rows.find((row) => row.uri === uri) ?? null, }); } describe('createSiteBrowser', () => { it('throws on missing ctx entries', () => { expect(() => createSiteBrowser(/** @type {any} */ ({}))).toThrow( /actorStorage/, ); }); it('lists sites with counts, sizes, urls, and servability', async () => { const browser = createSiteBrowser({ actorStorage: await storageWith([ { name: 'home', files: [ { path: 'index.html', size: 1000 }, { path: 'style.css', size: 500 }, ], }, { name: 'blog', files: [{ path: 'index.html', size: 250 }] }, { name: 'Bad_Name', files: [] }, ]), getDid: async () => DID, siteDomain: 'example.com', }); expect(browser.domain).toBe('example.com'); expect(browser.apexName).toBe('home'); const sites = await browser.listSites(); expect(sites).toHaveLength(3); const home = sites.find((site) => site.name === 'home'); expect(home).toMatchObject({ url: 'https://example.com', fileCount: 2, totalSize: 1500, deployedAt: '2026-08-06T00:00:00.000Z', servable: true, }); expect(home?.cid).toMatch(/^b/); expect(sites.find((site) => site.name === 'blog')).toMatchObject({ url: 'https://blog.example.com', servable: true, }); expect(sites.find((site) => site.name === 'Bad_Name')).toMatchObject({ servable: false, }); }); it("lists a site's files sorted by path, and null for a missing site", async () => { const browser = createSiteBrowser({ actorStorage: await storageWith([ { name: 'home', files: [ { path: 'style.css', size: 500 }, { path: 'index.html', size: 1000 }, ], }, ]), getDid: async () => DID, siteDomain: 'example.com', }); const files = await browser.listFiles('home'); expect(files?.map((file) => file.path)).toEqual([ 'index.html', 'style.css', ]); expect(files?.[0]).toMatchObject({ size: 1000, contentType: 'application/octet-stream', }); expect(files?.[0].cid).toMatch(/^b/); expect(await browser.listFiles('nope')).toBeNull(); }); it('pages through more than one listRecords call', async () => { const many = Array.from({ length: 150 }, (_, i) => ({ name: `site-${i}`, files: [], })); const browser = createSiteBrowser({ actorStorage: await storageWith(many), getDid: async () => DID, siteDomain: 'example.com', }); expect(await browser.listSites()).toHaveLength(150); }); });