~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
113 lines · 3.9 KB Raw History
// The sites extension against a fake host: a domain turns on the host
// handler, browser and installer; without one only the installs routes
// mount; queries add their route and commit tap.
import {
  CID,
  cborEncodeDagCbor,
  cidToString,
  createBlobCid,
  createCid,
} from '@pdsjs/core/repo';
import { beforeEach, describe, expect, it } from 'vitest';
import { createSitesExtension } from '../src/extension.js';
import { clearManifestCache } from '../src/manifest.js';

const DID = 'did:plc:sitesextension';

/**
 * One-file site record for `name`, plus the blob it points at.
 * @param {string} name
 */
async function siteFixture(name) {
  const data = new TextEncoder().encode(`<h1>${name}</h1>`);
  const cidBytes = await createBlobCid(data);
  const record = cborEncodeDagCbor({
    $type: 'dev.pdsjs.site.deploy',
    files: [
      {
        path: 'index.html',
        blob: {
          $type: 'blob',
          ref: new CID(cidBytes),
          mimeType: 'text/html',
          size: data.length,
        },
      },
    ],
    createdAt: '2026-08-06T00:00:00.000Z',
  });
  return {
    uri: `at://${DID}/dev.pdsjs.site.deploy/${name}`,
    row: { cid: cidToString(await createCid(record)), value: record },
    blobCid: cidToString(cidBytes),
    data,
  };
}

/** @param {Awaited<ReturnType<typeof siteFixture>>[]} sites */
function createHost(sites) {
  const records = new Map(sites.map((site) => [site.uri, site.row]));
  const blobs = new Map(sites.map((site) => [site.blobCid, site.data]));
  const host = {
    actorStorage: {
      getRecord: async (/** @type {string} */ uri) => records.get(uri) ?? null,
      listRecords: async () => ({ records: [], cursor: null }),
      linkBlobToRecord: async () => {},
    },
    blobs: {
      get: async (/** @type {string} */ _did, /** @type {string} */ cid) => {
        const data = blobs.get(cid);
        return data ? { data, mimeType: 'text/html' } : null;
      },
    },
    readOnly: false,
    plcUrl: 'https://plc.directory',
    blobUploadLimit: 1024,
    getDid: async () => DID,
    xrpc: async () => new Response('{}'),
    createCommit: async () => ({ cid: 'bafy', rev: '3l' }),
    reapOrphanedBlobs: async () => {},
    accountApi: async () => new Response('{}'),
  };
  return /** @type {import('@pdsjs/core/ports').ExtensionHost} */ (
    /** @type {unknown} */ (host)
  );
}

beforeEach(() => clearManifestCache());

describe('createSitesExtension', () => {
  it('serves the apex from the named site when a domain is set', async () => {
    const www = await siteFixture('www');
    const extension = createSitesExtension({
      domain: 'example.com',
      apexName: 'www',
    })(createHost([www]));
    expect(extension.name).toBe('sites');
    expect(extension.features).toEqual({ sites: true });
    expect(Object.keys(extension.routes ?? {})).toEqual(
      expect.arrayContaining(['/account/api/sites', '/account/api/installs']),
    );
    const url = new URL('https://example.com/');
    const response = await extension.handleHost?.(new Request(url), url);
    expect(response?.status).toBe(200);
    expect(await response?.text()).toBe('<h1>www</h1>');
  });

  it('mounts only the installs routes without a domain', async () => {
    const extension = createSitesExtension()(createHost([]));
    expect(extension.features).toEqual({ sites: false });
    expect(extension.handleHost).toBeUndefined();
    expect(extension.onCommit).toBeUndefined();
    const routes = Object.keys(extension.routes ?? {});
    expect(routes).toContain('/account/api/installs');
    expect(routes).toContain('/account/api/sites');
    expect(routes).not.toContain('/xrpc/dev.pdsjs.query.run');
  });

  it('adds the query route and commit tap when asked', async () => {
    const extension = createSitesExtension({ queries: true })(createHost([]));
    expect(extension.routes?.['/xrpc/dev.pdsjs.query.run']).toBeDefined();
    expect(typeof extension.onCommit).toBe('function');
  });
});