chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { describe, expect, it } from 'vitest';
import {
CONSTELLATION_URL,
createConstellationDiscovery,
noDiscovery,
} from '../src/discovery.js';
const SUBJECT = 'at://did:plc:alice/dev.pdsjs.git.repo/demo-app';
/**
* A fake index answering from a script of pages, recording each request.
* @param {Array<{records?: unknown[], cursor?: unknown}|Response>} pages
*/
function fakeIndex(pages) {
/** @type {Array<{url: URL, init: RequestInit|undefined}>} */
const asked = [];
/** @type {typeof fetch} */
const send = async (input, init) => {
asked.push({ url: new URL(String(input)), init });
const page = pages.shift();
if (page instanceof Response) return page;
return Response.json(page ?? {});
};
return { asked, send };
}
describe('createConstellationDiscovery', () => {
it('asks for the collection on the subject.uri path, naming itself', async () => {
const index = fakeIndex([
{
records: [
{ did: 'did:plc:bob', collection: 'dev.pdsjs.git.pull', rkey: '3a' },
],
cursor: null,
},
]);
const discovery = createConstellationDiscovery({
fetch: index.send,
userAgent: 'test (@example.com)',
});
const found = await discovery.about(SUBJECT, 'dev.pdsjs.git.pull');
expect(found).toEqual([
{ did: 'did:plc:bob', collection: 'dev.pdsjs.git.pull', rkey: '3a' },
]);
const [{ url, init }] = index.asked;
expect(url.origin).toBe(CONSTELLATION_URL);
expect(url.pathname).toBe('/xrpc/blue.microcosm.links.getBacklinks');
expect(url.searchParams.get('subject')).toBe(SUBJECT);
expect(url.searchParams.get('source')).toBe(
'dev.pdsjs.git.pull:subject.uri',
);
expect(url.searchParams.get('limit')).toBe('100');
expect(new Headers(init?.headers).get('user-agent')).toBe(
'test (@example.com)',
);
});
it('asks on another path where the link is elsewhere in the record', async () => {
const index = fakeIndex([{ records: [] }]);
await createConstellationDiscovery({ fetch: index.send }).about(
SUBJECT,
'dev.pdsjs.git.repo',
'upstream',
);
expect(index.asked[0].url.searchParams.get('source')).toBe(
'dev.pdsjs.git.repo:upstream',
);
});
it('follows the cursor until the index says there is no more', async () => {
const index = fakeIndex([
{
records: [{ did: 'did:plc:a', collection: 'c', rkey: '1' }],
cursor: 'next',
},
{
records: [{ did: 'did:plc:b', collection: 'c', rkey: '2' }],
cursor: null,
},
]);
const discovery = createConstellationDiscovery({ fetch: index.send });
const found = await discovery.about(SUBJECT, 'c');
expect(found.map((one) => one.rkey)).toEqual(['1', '2']);
expect(index.asked).toHaveLength(2);
expect(index.asked[0].url.searchParams.has('cursor')).toBe(false);
expect(index.asked[1].url.searchParams.get('cursor')).toBe('next');
});
it('stops at the page limit rather than following a cursor forever', async () => {
const endless = Array.from({ length: 5 }, (_, at) => ({
records: [{ did: 'did:plc:a', collection: 'c', rkey: String(at) }],
cursor: 'more',
}));
const index = fakeIndex(endless);
const discovery = createConstellationDiscovery({
fetch: index.send,
maxPages: 3,
});
const found = await discovery.about(SUBJECT, 'c');
expect(found).toHaveLength(3);
expect(index.asked).toHaveLength(3);
});
it('drops an entry missing any part of its address', async () => {
const index = fakeIndex([
{
records: [
{ did: 'did:plc:a', collection: 'c', rkey: '1' },
{ did: 'did:plc:a', collection: 'c' },
{ collection: 'c', rkey: '2' },
null,
'nonsense',
],
},
]);
const discovery = createConstellationDiscovery({ fetch: index.send });
expect(await discovery.about(SUBJECT, 'c')).toEqual([
{ did: 'did:plc:a', collection: 'c', rkey: '1' },
]);
});
it('answers what it has when the index refuses or fails', async () => {
// A refusal on the second page keeps the first.
const refused = fakeIndex([
{
records: [{ did: 'did:plc:a', collection: 'c', rkey: '1' }],
cursor: 'next',
},
new Response('no', { status: 503 }),
]);
expect(
await createConstellationDiscovery({ fetch: refused.send }).about(
SUBJECT,
'c',
),
).toEqual([{ did: 'did:plc:a', collection: 'c', rkey: '1' }]);
// A network failure on the first page is an empty answer, not an error.
const down = createConstellationDiscovery({
fetch: async () => {
throw new TypeError('fetch failed');
},
});
expect(await down.about(SUBJECT, 'c')).toEqual([]);
// A body that is not JSON reads the same way.
const garbled = createConstellationDiscovery({
fetch: async () => new Response('<html>'),
});
expect(await garbled.about(SUBJECT, 'c')).toEqual([]);
});
it('asks the service it was given, without a trailing slash doubled', async () => {
const index = fakeIndex([{ records: [] }]);
const discovery = createConstellationDiscovery({
service: 'https://index.example/',
fetch: index.send,
});
await discovery.about(SUBJECT, 'c');
expect(
index.asked[0].url.href.startsWith('https://index.example/xrpc/'),
).toBe(true);
});
});
describe('createConstellationDiscovery, on the global fetch', () => {
it('carries the request on globalThis.fetch when given no carrier', async () => {
const real = globalThis.fetch;
/** @type {string[]} */
const asked = [];
globalThis.fetch = async (input) => {
asked.push(String(input));
return Response.json({ records: [], cursor: null });
};
try {
expect(await createConstellationDiscovery().about(SUBJECT, 'c')).toEqual(
[],
);
expect(asked[0]?.startsWith(`${CONSTELLATION_URL}/xrpc/`)).toBe(true);
} finally {
globalThis.fetch = real;
}
});
});
describe('noDiscovery', () => {
it('finds nothing', async () => {
expect(await noDiscovery().about(SUBJECT, 'dev.pdsjs.git.pull')).toEqual(
[],
);
});
});