Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301import assert from 'node:assert/strict';import { test } from 'node:test';import { handleRequest, type IndexStub, type RouterEnv } from '../src/router.ts';import { upstream, type Upstream } from '../src/upstream.ts';import { memoryCache } from '../src/gateway/index.ts';
function fakeEnv(stub: Partial<IndexStub> = {}): RouterEnv { const index: IndexStub = { getRecent: async () => ({ projects: [] }), getTrending: async () => ({ projects: [] }), getKnown: async (dids) => dids.filter((d) => d === 'did:plc:alice'), ...stub, }; return { INDEX: { getByName: () => index }, APP_ORIGIN: 'https://nibling.run', SLINGSHOT_URL: 'https://slingshot.microcosm.blue', CONSTELLATION_URL: 'https://constellation.microcosm.blue', fetch: upstream(async () => new Response('{}', { status: 404 })), cache: memoryCache(Date.now), waitUntil: () => {}, };}
test('getKnown with 2 DIDs returns only the known one', async () => { const res = await handleRequest( new Request('https://api.nibling.run/xrpc/dev.nibling.actor.getKnown?dids=did:plc:alice,did:plc:bob'), fakeEnv(), ); assert.equal(res.status, 200); const body = await res.json(); assert.deepStrictEqual(body, { dids: ['did:plc:alice'] }); assert.equal(res.headers.get('access-control-allow-origin'), '*');});
test('getKnown with no dids is a 400 InvalidRequest', async () => { const res = await handleRequest(new Request('https://api.nibling.run/xrpc/dev.nibling.actor.getKnown'), fakeEnv()); assert.equal(res.status, 400); assert.deepStrictEqual(await res.json(), { error: 'InvalidRequest', message: 'dids is required' });});
test('an unknown route is a 404', async () => { const res = await handleRequest(new Request('https://api.nibling.run/not-a-real-route'), fakeEnv()); assert.equal(res.status, 404); assert.deepStrictEqual(await res.json(), { error: 'NotFound', message: 'no route for this path' });});
test('getRecent forwards limit, cursor, and tag to the index', async () => { let received: unknown; const res = await handleRequest( new Request('https://api.nibling.run/xrpc/dev.nibling.feed.getRecent?limit=5&cursor=abc&tag=game'), fakeEnv({ getRecent: async (options) => { received = options; return { projects: [] }; }, }), ); assert.equal(res.status, 200); assert.deepStrictEqual(received, { limit: 5, cursor: 'abc', tag: 'game' });});
test('oauth client-metadata.json is served with the exact D19 scope string', async () => { const res = await handleRequest(new Request('https://api.nibling.run/oauth/client-metadata.json'), fakeEnv()); assert.equal(res.status, 200); const body = (await res.json()) as { scope: string; client_id: string }; assert.equal(body.client_id, 'https://nibling.run/oauth/client-metadata.json'); assert.equal( body.scope, 'atproto repo:dev.nibling.project repo:dev.nibling.feed.like repo:dev.nibling.feed.comment repo:dev.nibling.graph.follow repo:dev.nibling.actor.profile blob:*/* rpc:org.tangled.git.receivePack?aud=*', );});
const ALICE_DID = 'did:plc:alice';// A real TID (13 chars, valid charset), so `isTid` tells it apart from a slug. The old fixture's// '3la1' is only 4 chars and was never a valid TID.const PROJECT_TID = '3la2bcdefghij';const PROJECT_URI = `at://${ALICE_DID}/dev.nibling.project/${PROJECT_TID}`;const PROJECT_VALUE = { $type: 'dev.nibling.project', name: 'Alice <Project>', slug: 'alice-project', files: [], createdAt: '2026-11-01T00:00:00.000Z',};
/** Slingshot and the fake PDS it names, as they answer today: `resolveMiniDoc?identifier=`, * `getRecordByUri?at_uri=` (a request with `uri=` gets HTTP 400, as the live service does), and * `com.atproto.repo.listRecords` on the PDS itself for the slug fallback. */function fakeSlingshot(calls: string[], pds = 'https://pds.example.com'): Upstream { return upstream(async (input: string | URL | Request) => { const url = new URL(typeof input === 'string' ? input : input.toString()); calls.push(url.pathname + url.search); if (url.pathname === '/xrpc/blue.microcosm.identity.resolveMiniDoc') { const identifier = url.searchParams.get('identifier'); if (identifier !== 'alice.test' && identifier !== ALICE_DID) return new Response('{}', { status: 400 }); return Response.json({ did: ALICE_DID, handle: 'alice.test', pds }); } if (url.pathname === '/xrpc/blue.microcosm.repo.getRecordByUri') { if (url.searchParams.get('at_uri') !== PROJECT_URI) return new Response('{}', { status: 400 }); return Response.json({ uri: PROJECT_URI, cid: 'bafy-project1', value: PROJECT_VALUE }); } if (url.pathname === '/xrpc/com.atproto.repo.listRecords') { if (url.searchParams.get('repo') !== ALICE_DID || url.searchParams.get('collection') !== 'dev.nibling.project') { return new Response('{}', { status: 400 }); } return Response.json({ records: [{ uri: PROJECT_URI, cid: 'bafy-project1', value: PROJECT_VALUE }] }); } return new Response('{}', { status: 404 }); });}
function cardEnv(calls: string[], pds?: string): RouterEnv { return { ...fakeEnv(), fetch: fakeSlingshot(calls, pds) };}
test('/alice.test/<slug> resolves through listRecords on the PDS and serves og:title and og:url', async () => { const calls: string[] = []; const res = await handleRequest(new Request('https://api.nibling.run/alice.test/alice-project'), cardEnv(calls)); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true); assert.equal( html.includes('<meta property="og:url" content="https://nibling.run/alice.test/alice-project">'), true, ); assert.deepStrictEqual(calls, [ '/xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=alice.test', `/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(ALICE_DID)}&collection=dev.nibling.project&limit=100`, ]);});
test('/alice.test/<tid>/ with a trailing slash reads the record directly', async () => { const calls: string[] = []; const res = await handleRequest(new Request(`https://api.nibling.run/alice.test/${PROJECT_TID}/`), cardEnv(calls)); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true); assert.deepStrictEqual(calls, [ '/xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=alice.test', `/xrpc/blue.microcosm.repo.getRecordByUri?at_uri=${encodeURIComponent(PROJECT_URI)}`, ]);});
test('the rkey URL of a record that has a slug gives og:url with the slug', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/alice.test/${PROJECT_TID}`), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal( html.includes('<meta property="og:url" content="https://nibling.run/alice.test/alice-project">'), true, );});
test('/actor/<name> is a 404 when nothing resolves as that rkey or slug', async () => { const res = await handleRequest(new Request('https://api.nibling.run/alice.test/missing'), cardEnv([])); assert.equal(res.status, 404);});
test('/<did>/<tid> reads the same project as /<handle>/<tid>', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/${ALICE_DID}/${PROJECT_TID}`), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true); assert.equal( html.includes(`<meta property="og:url" content="https://nibling.run/${ALICE_DID}/alice-project">`), true, );});
test('/at://<did>/dev.nibling.project/<tid> reads the same project as /<did>/<tid>', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/at://${ALICE_DID}/dev.nibling.project/${PROJECT_TID}`), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true); assert.equal( html.includes(`<meta property="og:url" content="https://nibling.run/${ALICE_DID}/alice-project">`), true, );});
test('/at:/<did>/dev.nibling.project/<tid> (the single-slash form a layer that collapses `//` hands us) reads the same project', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/at:/${ALICE_DID}/dev.nibling.project/${PROJECT_TID}`), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true);});
test('the percent-encoded at-uri form (slashes encoded too) reads the same project', async () => { const encoded = encodeURIComponent(`at://${ALICE_DID}/dev.nibling.project/${PROJECT_TID}`); const res = await handleRequest(new Request(`https://api.nibling.run/${encoded}`), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true);});
test('/at://<actor>/dev.nibling.project/<tid>/embed serves the embed the same way', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/at://alice.test/dev.nibling.project/${PROJECT_TID}/embed`), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('src="https://nibling.run/alice.test/alice-project/embed"'), true);});
test('a two-segment path whose first segment is neither a handle nor a DID stays a 404, not a project lookup', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/not-a-did/${PROJECT_TID}`), cardEnv([])); assert.equal(res.status, 404); assert.deepStrictEqual(await res.json(), { error: 'NotFound', message: 'no route for this path' });});
test('/oembed accepts a bare-DID page url the same way as a handle one', async () => { const res = await handleRequest( new Request(`https://api.nibling.run/oembed?url=${encodeURIComponent(`https://nibling.run/${ALICE_DID}/${PROJECT_TID}`)}`), cardEnv([]), ); assert.equal(res.status, 200); const body = (await res.json()) as { type: string; title: string }; assert.equal(body.title, 'Alice <Project>');});
test('/alice.test/<slug>/embed serves the iframe at the slug-based embed URL', async () => { const res = await handleRequest(new Request('https://api.nibling.run/alice.test/alice-project/embed'), cardEnv([])); assert.equal(res.status, 200); const html = await res.text(); assert.equal(html.includes('<meta property="og:title" content="Alice <Project>">'), true); assert.equal(html.includes('src="https://nibling.run/alice.test/alice-project/embed"'), true);});
test('/@alice.test/<name> and its embed are a 301 to the address without the @', async () => { for (const [path, location] of [ ['/@alice.test/alice-project', '/alice.test/alice-project'], ['/@alice.test/alice-project/embed/?ref=x', '/alice.test/alice-project/embed/?ref=x'], [`/@${ALICE_DID}/${PROJECT_TID}`, `/${ALICE_DID}/${PROJECT_TID}`], ]) { const calls: string[] = []; const res = await handleRequest(new Request(`https://api.nibling.run${path}`), cardEnv(calls)); assert.equal(res.status, 301, path); assert.equal(res.headers.get('location'), `https://api.nibling.run${location}`, path); assert.deepStrictEqual(calls, [], path); }});
test('/oembed still reads a page url spelled with the old @', async () => { const res = await handleRequest( new Request(`https://api.nibling.run/oembed?url=${encodeURIComponent('https://nibling.run/@alice.test/alice-project')}`), cardEnv([]), ); assert.equal(res.status, 200);});
test('/embed/alice.test/<tid> (the deleted old form) is now a 404', async () => { const res = await handleRequest(new Request(`https://api.nibling.run/embed/alice.test/${PROJECT_TID}`), cardEnv([])); assert.equal(res.status, 404);});
test('an unknown slug is a 404', async () => { const res = await handleRequest(new Request('https://api.nibling.run/alice.test/no-such-slug'), cardEnv([])); assert.equal(res.status, 404);});
test('a non-public PDS url is not fetched for slug listing', async () => { const calls: string[] = []; const res = await handleRequest( new Request('https://api.nibling.run/alice.test/alice-project'), cardEnv(calls, 'http://pds.example.com'), ); assert.equal(res.status, 404); assert.deepStrictEqual(calls, ['/xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=alice.test']);});
test('/oembed returns rich JSON with the exact title', async () => { const res = await handleRequest( new Request(`https://api.nibling.run/oembed?url=${encodeURIComponent('https://nibling.run/alice.test/alice-project')}`), cardEnv([]), ); assert.equal(res.status, 200); const body = (await res.json()) as { type: string; title: string }; assert.equal(body.type, 'rich'); assert.equal(body.title, 'Alice <Project>');});
test('/oembed with a malformed url is a 400, not a 500', async () => { const res = await handleRequest(new Request('https://api.nibling.run/oembed?url=x'), cardEnv([])); assert.equal(res.status, 400); assert.deepStrictEqual(await res.json(), { error: 'InvalidRequest', message: 'url must point at a project page' });});
test('getKnown with more than 100 DIDs is a 400', async () => { const dids = Array.from({ length: 101 }, (_, i) => `did:plc:user${i}`).join(','); const res = await handleRequest(new Request(`https://api.nibling.run/xrpc/dev.nibling.actor.getKnown?dids=${dids}`), fakeEnv()); assert.equal(res.status, 400); assert.deepStrictEqual(await res.json(), { error: 'InvalidRequest', message: 'dids accepts at most 100' });});
test('getKnown with exactly 100 DIDs is answered', async () => { const dids = ['did:plc:alice', ...Array.from({ length: 99 }, (_, i) => `did:plc:user${i}`)].join(','); const res = await handleRequest(new Request(`https://api.nibling.run/xrpc/dev.nibling.actor.getKnown?dids=${dids}`), fakeEnv()); assert.equal(res.status, 200); assert.deepStrictEqual(await res.json(), { dids: ['did:plc:alice'] });});