// The issue statements as the server reads them: every listed voice from // their own server, and whoever the discovery port names from theirs. import { describe, expect, it } from 'vitest'; import { createIssueReader } from '../src/issue-reader.js'; const ALICE = 'did:plc:alice'; const BOB = 'did:plc:bob'; const DAN = 'did:plc:dan'; const REPO = 'demo'; const SUBJECT = `at://${ALICE}/dev.pdsjs.git.repo/${REPO}`; /** @type {Record} */ const PEOPLE = { [ALICE]: { did: ALICE, handle: 'alice.test', pds: 'https://alice.test' }, [BOB]: { did: BOB, handle: 'bob.test', pds: 'https://bob.test' }, [DAN]: { did: DAN, handle: 'dan.test', pds: 'https://dan.test' }, }; /** * @param {string} did @param {string} rkey @param {Record} value */ const row = (did, rkey, value) => ({ uri: `at://${did}/dev.pdsjs.git.issue/${rkey}`, cid: `cid-${rkey}`, value: { subject: { uri: SUBJECT }, createdAt: '2026-08-01T00:00:00Z', ...value, }, }); /** * @param {Object} [options] * @param {Array<{did: string, collection: string, rkey: string}>} [options.about] * @param {string[]} [options.unreachable] */ function world({ about = [], unreachable = [] } = {}) { /** @type {Record} */ const records = { [ALICE]: [row(ALICE, 'a1', { body: 'Resolved.', disposition: 'resolved' })], [BOB]: [ row(BOB, 'b1', { title: 'It breaks', body: 'Steps.' }), // About another repository, which a voice's listing carries too. { ...row(BOB, 'b2', { title: 'Elsewhere' }), value: { subject: { uri: `at://${ALICE}/dev.pdsjs.git.repo/other` }, title: 'Elsewhere', }, }, ], [DAN]: [ row(DAN, 'd1', { title: 'From a stranger' }), { ...row(DAN, 'd2', { title: 'Lied about' }), value: { subject: { uri: `at://${ALICE}/dev.pdsjs.git.repo/other` }, title: 'Lied about', }, }, ], }; /** @type {typeof fetch} */ const send = async (input) => { const url = new URL(String(input)); const who = Object.values(PEOPLE).find((one) => one.pds === url.origin); if (!who || unreachable.includes(who.did)) { throw new TypeError('fetch failed'); } const held = /** @type {{uri: string}[]} */ (records[who.did] ?? []); if (url.pathname.endsWith('listRecords')) { return Response.json({ records: held, cursor: null }); } const rkey = url.searchParams.get('rkey'); const found = held.find((one) => one.uri.endsWith(`/${rkey}`)); return found ? Response.json(found) : new Response('no', { status: 404 }); }; const reader = createIssueReader({ resolveContributor: async (did) => PEOPLE[did] ?? null, readRecord: async (_did, collection, rkey) => { if (rkey !== REPO) return null; return collection === 'dev.pdsjs.git.repo' ? { defaultBranch: 'main' } : { collaborators: [BOB] }; }, discovery: { about: async (_subject, collection) => about.filter((entry) => entry.collection === collection), }, fetch: send, }); return reader; } describe('createIssueReader', () => { it('lists every voice the config names, about this repository only', async () => { const answer = await world().listIssues(ALICE, REPO); const statements = answer?.statements ?? []; expect(statements.map((one) => one.uri)).toEqual([ `at://${ALICE}/dev.pdsjs.git.issue/a1`, `at://${BOB}/dev.pdsjs.git.issue/b1`, ]); expect(statements[0].by).toEqual(PEOPLE[ALICE]); expect(statements[0].disposition).toBe('resolved'); expect(statements[1].indexed).toBeUndefined(); }); it('reads a stranger the port names from their own server, and takes the record over the index', async () => { const answer = await world({ about: [ { did: DAN, collection: 'dev.pdsjs.git.issue', rkey: 'd1' }, { did: DAN, collection: 'dev.pdsjs.git.issue', rkey: 'd2' }, { did: DAN, collection: 'dev.pdsjs.git.issue', rkey: 'missing' }, // A listed voice's record, read directly already. { did: BOB, collection: 'dev.pdsjs.git.issue', rkey: 'b1' }, ], }).listIssues(ALICE, REPO); const statements = answer?.statements ?? []; const fromDan = statements.filter((one) => one.by.did === DAN); expect(fromDan.map((one) => one.uri)).toEqual([ `at://${DAN}/dev.pdsjs.git.issue/d1`, ]); expect(fromDan[0]).toMatchObject({ title: 'From a stranger', indexed: true, }); // Bob's word appears once. expect(statements.filter((one) => one.uri.endsWith('/b1'))).toHaveLength(1); }); it('costs an unreachable voice their words and nothing else', async () => { const answer = await world({ unreachable: [BOB] }).listIssues(ALICE, REPO); expect(answer?.statements.map((one) => one.by.did)).toEqual([ALICE]); }); it('answers null for a repository this account does not hold', async () => { expect(await world().listIssues(ALICE, 'nothing')).toBeNull(); }); });