OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge / test / favela-feed.test.js

89 lines · javascript ref:
// favela-feed.test.js — reading a favela feed the bridge HOLDS, instead of a socket.
//
// ⛔ THE DEFECT THIS REPLACES. FavelaSource reads its peer over a UNIX SOCKET, which means the
// bridge has to run on the same box as the peer it projects. That is incompatible with the thing a
// bridge IS: a service peers connect to. PerUser exists so ONE bridge fronts MANY people — and it
// could never actually do that, because they would all have to share the bridge's machine.
//
// A favela feed is a hypercore on a discovery topic. The bridge can hold it read-only exactly as
// any follower does, and read the same records. Nothing about FavelaSource changes: it needs
// `reach()`, `ownPosts()` and `call('file-get')`, and this presents those three from a held feed.

import test from 'brittle'
import { FavelaFeed } from '../lib/favela-feed.js'

// A held Feed, standing in for favela-peer's. `reach` is a record at reach/self and posts are a
// collection — both read off the feed itself, which is why holding it is enough.
function heldFeed ({ networks = ['fediverse'], posts = [] } = {}) {
  return {
    key: Buffer.alloc(32, 1),
    async update () {},
    async get (collection, name) {
      if (collection === 'reach' && name === 'self') return { frontmatter: { networks, credential: null }, body: '' }
      return null
    },
    async list (collection) {
      if (collection !== 'post') return []
      return posts.map((p) => ({ key: 'post/' + p.name, collection: 'post', name: p.name, record: p }))
    }
  }
}

test('★★ reach comes off the held feed, not a socket', async (t) => {
  const f = new FavelaFeed({ feed: heldFeed({ networks: ['fediverse', 'bsky'] }) })
  t.alike(await f.reach(), { networks: ['fediverse', 'bsky'], credential: null })
})

// ⛔ Unknown consent is not consent. A feed whose reach record has not replicated yet must read as
// OFF, never as "carry on" — publishing under a guess is the failure this gate exists to stop.
test('⛔ a feed with no reach record reads as OFF', async (t) => {
  const bare = { key: Buffer.alloc(32), async update () {}, async get () { return null }, async list () { return [] } }
  t.alike((await new FavelaFeed({ feed: bare }).reach()).networks, [])
})

test('★★ ownPosts returns the shape FavelaSource already expects', async (t) => {
  const f = new FavelaFeed({
    feed: heldFeed({ posts: [{ name: '01k2', body: 'hello', frontmatter: { createdAt: '2026-09-08T00:00:00Z' } }] })
  })
  const { feed, rows } = await f.ownPosts()

  t.is(feed, '01'.repeat(32).slice(0, 64), 'the feed key as hex')
  t.is(rows.length, 1)
  t.is(rows[0].name, '01k2')
  t.is(rows[0].body, 'hello', 'body lifted out of the record, as posts-by returns it')
  t.is(rows[0].createdAt, '2026-09-08T00:00:00Z')
})

test('★ file-get describes a blob from the overlay', async (t) => {
  const f = new FavelaFeed({
    feed: heldFeed(),
    files: { async get (oid) { return oid === 'abc' ? { path: '/x', mimeType: 'image/png', size: 3 } : null } }
  })
  t.is((await f.call('file-get', { oid: 'abc' })).mimeType, 'image/png')
  t.is(await f.call('file-get', { oid: 'nope' }), null)
})

// ⛔ A bridge holds SOMEBODY ELSE'S feed. It must never be able to write to it — the whole keyless
// claim depends on that, and a bridge that could write would be authoring under their name.
test('⛔ it is read-only — there is no write surface at all', async (t) => {
  const f = new FavelaFeed({ feed: heldFeed() })
  for (const m of ['put', 'post', 'append', 'setReach', 'del']) {
    t.absent(typeof f[m] === 'function', 'no ' + m + '()')
  }
})

test('⛔ an unknown command is refused rather than guessed', async (t) => {
  const f = new FavelaFeed({ feed: heldFeed() })
  await t.exception(f.call('post', { body: 'x' }), /unsupported/i)
})

// ⛔ The held feed carries its owner's TOMBSTONES — the positive evidence a Delete needs.
test('★★ ownDeleted reads the held feed\'s tombstoned posts', async (t) => {
  const feed = { ...heldFeed(), async tombstones (c) { return c === 'post' ? ['01gone'] : [] } }
  t.alike(await new FavelaFeed({ feed }).ownDeleted(), { post: ['01gone'], reply: [] })
})

test('a held feed from before tombstones() existed reports none, not an error', async (t) => {
  t.alike(await new FavelaFeed({ feed: heldFeed() }).ownDeleted(), { post: [], reply: [] })
})