import { PersonalDataServer } from '@pdsjs/core'; import { describe, expect, test, vi } from 'vitest'; /** * @param {Array} extensions * @param {(at: number) => void} [scheduleWakeup] * @param {(work: Promise) => 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[]} */ 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(); }); });