| … | … | @@ -26,7 +26,7 @@ function fakePeer ({ networks = ['bsky'], rows = [], profile = null, unknown = f |
| 26 | 26 | } |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | | function fakeSink ({ has = [], haveNull = false } = {}) { |
| 29 | function fakeSink ({ has = [], haveNull = false, getFails = false } = {}) { |
| 30 | 30 | const written = [] |
| 31 | 31 | const records = new Map() |
| 32 | 32 | const client = { |
| … | … | @@ -36,7 +36,7 @@ function fakeSink ({ has = [], haveNull = false } = {}) { |
| 36 | 36 | return { records: has.map((rkey) => ({ collection: 'app.bsky.feed.post', rkey })) } |
| 37 | 37 | } |
| 38 | 38 | if (cmd === 'apply-write') { written.push(args); records.set(args.collection + '/' + args.rkey, args.record); return { ok: true } } |
| 39 | | if (cmd === 'get-record') return records.get(args.collection + '/' + args.rkey) || null |
| 39 | if (cmd === 'get-record') { if (getFails) throw new Error('cannot read'); return records.get(args.collection + '/' + args.rkey) || null } |
| 40 | 40 | throw new Error('unexpected ' + cmd) |
| 41 | 41 | } |
| 42 | 42 | } |
| … | … | @@ -72,7 +72,7 @@ test('★★ a post already in the repo is not sent twice', async (t) => { |
| 72 | 72 | const r = await projectCycle({ peer: fakePeer({ rows: [row(name, 'hello')] }), bsky: sink, send: true }) |
| 73 | 73 | t.is(r.skipped, 1, 'recognised as already there') |
| 74 | 74 | t.is(r.sent, 0) |
| 75 | | t.is(written.length, 0) |
| 75 | t.is(written.filter((w) => w.collection === 'app.bsky.feed.post').length, 0, 'no post re-sent (the feed link may be written once)') |
| 76 | 76 | }) |
| 77 | 77 | |
| 78 | 78 | test('★★ a public post is sent, under a TID rkey', async (t) => { |
| … | … | @@ -80,10 +80,12 @@ test('★★ a public post is sent, under a TID rkey', async (t) => { |
| 80 | 80 | const { sink, written } = fakeSink() |
| 81 | 81 | const r = await projectCycle({ peer: fakePeer({ rows: [row(name, 'hello')] }), bsky: sink, send: true }) |
| 82 | 82 | t.is(r.sent, 1) |
| 83 | | t.is(written.length, 1) |
| 84 | | t.is(written[0].collection, 'app.bsky.feed.post') |
| 85 | | t.is(written[0].rkey, tidFor(name), 'the AppView silently drops posts whose rkey is not a TID') |
| 86 | | t.is(written[0].record.text, 'hello') |
| 83 | // The DID's feed-link record (net.favela.feed/self, I2a) is also written on this first pass; count POSTS. |
| 84 | const posts = written.filter((w) => w.collection === 'app.bsky.feed.post') |
| 85 | t.is(posts.length, 1) |
| 86 | t.is(posts[0].collection, 'app.bsky.feed.post') |
| 87 | t.is(posts[0].rkey, tidFor(name), 'the AppView silently drops posts whose rkey is not a TID') |
| 88 | t.is(posts[0].record.text, 'hello') |
| 87 | 89 | }) |
| 88 | 90 | |
| 89 | 91 | test('★★ a dry run writes nothing but still reports what it would do', async (t) => { |
| … | … | @@ -174,3 +176,41 @@ test('★★★ with bsky switched off, no edge is projected either', async (t) |
| 174 | 176 | t.absent(r.edges.sent, 'consent gates edges exactly as it gates posts') |
| 175 | 177 | t.is(written.length, 0) |
| 176 | 178 | }) |
| 179 | |
| 180 | // ── the DID names its favela feed back (hyper-social #82, I2a / D4a) ─────────────────────────────────────────── |
| 181 | // |
| 182 | // ⛔ TWO-SIDED, OR NOT AT ALL. The identity's reach already names its DID (credential). This is the other half: the |
| 183 | // DID's own repo, signed by the DID key, names the favela feed. A gateway binds a DID to an identity only when both |
| 184 | // agree, so nobody can attach someone else's DID to their feed. Published content, so it waits for bsky consent |
| 185 | // exactly like the profile; reconciled against what the repo holds, never written on a failed read. |
| 186 | const FEED_CLAIM = 'net.favela.feed' |
| 187 | const claimOf = (records) => records.get(FEED_CLAIM + '/self') || null |
| 188 | |
| 189 | test('★★ with bsky on, the DID repo gets net.favela.feed/self naming this feed', async (t) => { |
| 190 | const { sink, written, records } = fakeSink() |
| 191 | await projectCycle({ peer: fakePeer(), bsky: sink, send: true }) |
| 192 | const c = claimOf(records) |
| 193 | t.ok(c, 'written') |
| 194 | t.alike({ type: c.$type, feed: c.feed }, { type: FEED_CLAIM, feed: 'f'.repeat(64) }) |
| 195 | t.ok(written.some((w) => w.collection === FEED_CLAIM && w.rkey === 'self')) |
| 196 | }) |
| 197 | |
| 198 | test('the same claim is not rewritten', async (t) => { |
| 199 | const { sink, written } = fakeSink() |
| 200 | await projectCycle({ peer: fakePeer(), bsky: sink, send: true }) |
| 201 | const n = written.filter((w) => w.collection === FEED_CLAIM).length |
| 202 | await projectCycle({ peer: fakePeer(), bsky: sink, send: true }) |
| 203 | t.is(written.filter((w) => w.collection === FEED_CLAIM).length, n, 'a second pass writes nothing') |
| 204 | }) |
| 205 | |
| 206 | test('⛔ no claim without bsky consent, on a dry run, or when the repo cannot be read', async (t) => { |
| 207 | const off = fakeSink() |
| 208 | await projectCycle({ peer: fakePeer({ networks: ['fediverse'] }), bsky: off.sink, send: true }) |
| 209 | t.absent(claimOf(off.records), 'bsky off') |
| 210 | const dry = fakeSink() |
| 211 | await projectCycle({ peer: fakePeer(), bsky: dry.sink, send: false }) |
| 212 | t.absent(claimOf(dry.records), 'dry run') |
| 213 | const blind = fakeSink({ getFails: true }) |
| 214 | await projectCycle({ peer: fakePeer(), bsky: blind.sink, send: true }) |
| 215 | t.absent(blind.written.some((w) => w.collection === FEED_CLAIM), 'could not read is not absent') |
| 216 | }) |