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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
207 lines · 6.1 KB Raw History
import { describe, expect, it } from 'vitest';
import { planInstall, resolveConfig, settingsSchema } from '../src/install.js';

const DEV = 'did:plc:developer';

const blob = (/** @type {string} */ cid, mimeType = 'text/html') => ({
  $type: 'blob',
  ref: { $link: cid },
  mimeType,
  size: 10,
});

function fixtures(overrides = {}) {
  const site = {
    uri: `at://${DEV}/dev.pdsjs.site.deploy/photos`,
    cid: 'bafysite',
    value: {
      $type: 'dev.pdsjs.site.deploy',
      files: [
        {
          path: 'index.html',
          blob: blob('bafyindex'),
          cacheControl: 'public, max-age=300',
        },
      ],
      fallback: 'index.html',
      createdAt: '2026-08-11T00:00:00.000Z',
    },
  };
  const manifest = {
    uri: `at://${DEV}/dev.pdsjs.app.manifest/photos`,
    cid: 'bafymanifest',
    value: {
      $type: 'dev.pdsjs.app.manifest',
      name: 'Photos',
      site: { uri: site.uri, cid: site.cid },
      queries: [
        {
          name: 'grain-galleries',
          collection: 'social.grain.gallery',
          sort: { field: 'createdAt', direction: 'desc' },
        },
      ],
      settings: [
        {
          key: 'topo',
          label: 'Terrain header',
          type: 'boolean',
          default: true,
        },
        {
          key: 'rows',
          type: 'select',
          options: ['compact', 'roomy'],
          default: 'roomy',
        },
      ],
      createdAt: '2026-08-11T00:00:00.000Z',
      ...overrides,
    },
  };
  return { manifest, site };
}

describe('settingsSchema', () => {
  it('keeps typed fields and drops what it cannot render', () => {
    const schema = settingsSchema([
      { key: 'a', type: 'boolean' },
      { key: 'b', type: 'wat' },
      { type: 'string' },
      { key: 'c', type: 'select', options: ['x', 1, 'y'] },
    ]);
    expect(schema.map((f) => f.key)).toEqual(['a', 'c']);
    expect(schema[1].options).toEqual(['x', 'y']);
    expect(settingsSchema('nope')).toEqual([]);
  });
});

describe('resolveConfig', () => {
  const schema = settingsSchema([
    { key: 'topo', type: 'boolean', default: true },
    { key: 'rows', type: 'select', options: ['compact', 'roomy'] },
  ]);

  it('applies defaults and checked overrides', () => {
    expect(resolveConfig(schema)).toEqual({ topo: true });
    expect(resolveConfig(schema, { topo: false, rows: 'compact' })).toEqual({
      topo: false,
      rows: 'compact',
    });
  });

  it('rejects unknown keys and wrong types', () => {
    expect(() => resolveConfig(schema, { nope: 1 })).toThrow(/No such setting/);
    expect(() => resolveConfig(schema, { topo: 'yes' })).toThrow(
      /true or false/,
    );
    expect(() => resolveConfig(schema, { rows: 'wide' })).toThrow(/one of/);
  });
});

describe('planInstall', () => {
  it('plans copies, records, defs, and the settings snapshot', () => {
    const { manifest, site } = fixtures();
    const plan = planInstall({
      manifest,
      site,
      overrides: { topo: false },
      now: '2026-08-11T01:00:00.000Z',
    });
    expect(plan.localName).toBe('photos');
    expect(plan.copies).toEqual([
      { path: 'index.html', cid: 'bafyindex', contentType: 'text/html' },
    ]);
    expect(plan.siteRecord.fallback).toBe('index.html');
    expect(
      /** @type {Array<{cacheControl?: string}>} */ (plan.siteRecord.files)[0]
        .cacheControl,
    ).toBe('public, max-age=300');
    expect(plan.queryDefs).toHaveLength(1);
    expect(plan.queryDefs[0].rkey).toBe('grain-galleries');
    expect(plan.installRecord.manifest).toEqual({
      uri: manifest.uri,
      cid: 'bafymanifest',
    });
    expect(plan.installRecord.config).toEqual({ topo: false, rows: 'roomy' });
    expect(plan.installRecord.name).toBe('Photos');
    expect(
      /** @type {Array<{key: string}>} */ (plan.installRecord.settings).map(
        (f) => f.key,
      ),
    ).toEqual(['topo', 'rows']);
  });

  it('copies a declared icon into the install record', () => {
    const { manifest, site } = fixtures({
      icon: {
        $type: 'blob',
        ref: { $link: 'bafyicon' },
        mimeType: 'image/svg+xml',
        size: 400,
      },
    });
    const plan = planInstall({ manifest, site });
    expect(plan.icon).toEqual({
      cid: 'bafyicon',
      contentType: 'image/svg+xml',
    });
    expect(
      /** @type {{ref: {$link: string}}} */ (plan.installRecord.icon).ref.$link,
    ).toBe('bafyicon');
    expect(
      planInstall({ manifest: fixtures().manifest, site }).icon,
    ).toBeNull();
  });

  it("carries a prior install's valid settings through an update", () => {
    const { manifest, site } = fixtures();
    const plan = planInstall({
      manifest,
      site,
      existingConfig: {
        rows: 'compact',
        topo: false,
        gone: 'stale',
        rowsBad: 1,
      },
      overrides: { topo: true },
    });
    // kept: rows (still valid); dropped: unknown keys; override wins on topo
    expect(plan.installRecord.config).toEqual({ topo: true, rows: 'compact' });
  });

  it('prefers the suggested site name over the artifact record key', () => {
    const { manifest, site } = fixtures({ suggestedSite: 'pics' });
    expect(planInstall({ manifest, site }).localName).toBe('pics');
    expect(planInstall({ manifest, site, localName: 'mine' }).localName).toBe(
      'mine',
    );
  });

  it('rejects a drifted site pin and bad names', () => {
    const { manifest, site } = fixtures();
    expect(() =>
      planInstall({ manifest, site: { ...site, cid: 'bafyother' } }),
    ).toThrow(/changed since this manifest/);
    expect(() =>
      planInstall({ manifest, site, localName: 'Not_A_Label' }),
    ).toThrow(/DNS label/);
  });

  it('rejects a site record that is not the pinned one or holds nothing', () => {
    const { manifest, site } = fixtures();
    expect(() =>
      planInstall({
        manifest,
        site: { ...site, uri: `at://${DEV}/dev.pdsjs.site.deploy/other` },
      }),
    ).toThrow(/not the one the manifest names/);
    expect(() =>
      planInstall({
        manifest,
        site: { ...site, value: { ...site.value, files: [] } },
      }),
    ).toThrow(/no files/);
  });
});