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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
131 lines · 3.6 KB Raw History
import { PersonalDataServer } from '@pdsjs/core';
import { describe, expect, test, vi } from 'vitest';

/**
 * @param {Array<import('../src/ports.js').ExtensionFactory>} extensions
 * @param {(at: number) => void} [scheduleWakeup]
 * @param {(work: Promise<unknown>) => void} [waitUntil]
 */
function server(extensions, scheduleWakeup, waitUntil) {
  return new PersonalDataServer({
    actorStorage: /** @type {any} */ ({}),
    sharedStorage: /** @type {any} */ ({}),
    blobs: /** @type {any} */ ({}),
    jwtSecret: 'test-secret',
    hostname: 'pds.example.com',
    password: 'pw',
    extensions,
    scheduleWakeup,
    waitUntil,
  });
}

describe('extension ticks', () => {
  test('answers the earliest time any extension asks to run again', async () => {
    const pds = server([
      () => ({ name: 'a', tick: async () => 3000 }),
      () => ({ name: 'b', tick: async () => 1000 }),
      () => ({ name: 'c', tick: async () => null }),
      () => ({ name: 'd' }),
    ]);
    expect(await pds.runExtensionTicks()).toBe(1000);
  });

  test('answers null when nothing is due', async () => {
    const pds = server([() => ({ name: 'a', tick: async () => null })]);
    expect(await pds.runExtensionTicks()).toBeNull();
  });

  test('runs the others when one tick fails', async () => {
    const error = vi.spyOn(console, 'error').mockImplementation(() => {});
    const ran = vi.fn(async () => 5);
    const pds = server([
      () => ({
        name: 'broken',
        tick: async () => {
          throw new Error('boom');
        },
      }),
      () => ({ name: 'fine', tick: ran }),
    ]);
    expect(await pds.runExtensionTicks()).toBe(5);
    expect(ran).toHaveBeenCalled();
    error.mockRestore();
  });

  test('passes a wakeup request through to the platform', async () => {
    /** @type {number[]} */
    const asked = [];
    /** @type {import('../src/ports.js').ExtensionHost|undefined} */
    let host;
    server(
      [
        (h) => {
          host = h;
          return { name: 'a' };
        },
      ],
      (at) => {
        asked.push(at);
      },
    );
    await host?.scheduleWakeup(1234);
    expect(asked).toEqual([1234]);
  });

  test('takes a wakeup request without a platform hook', async () => {
    /** @type {import('../src/ports.js').ExtensionHost|undefined} */
    let host;
    server([
      (h) => {
        host = h;
        return { name: 'a' };
      },
    ]);
    await expect(host?.scheduleWakeup(1234)).resolves.toBeUndefined();
  });

  test('hands background work to the platform, settled', async () => {
    const error = vi.spyOn(console, 'error').mockImplementation(() => {});
    /** @type {Promise<unknown>[]} */
    const kept = [];
    /** @type {import('../src/ports.js').ExtensionHost|undefined} */
    let host;
    server(
      [
        (h) => {
          host = h;
          return { name: 'a' };
        },
      ],
      undefined,
      (work) => {
        kept.push(work);
      },
    );
    host?.background(Promise.reject(new Error('boom')));
    expect(kept).toHaveLength(1);
    await expect(kept[0]).resolves.toBeUndefined();
    expect(error).toHaveBeenCalledWith(
      'Background work failed:',
      expect.any(Error),
    );
    error.mockRestore();
  });

  test('runs background work without a platform hook', async () => {
    /** @type {import('../src/ports.js').ExtensionHost|undefined} */
    let host;
    server([
      (h) => {
        host = h;
        return { name: 'a' };
      },
    ]);
    const ran = vi.fn();
    host?.background(Promise.resolve().then(ran));
    await Promise.resolve();
    await Promise.resolve();
    expect(ran).toHaveBeenCalled();
  });
});