OverviewFilesCommitsIssuesDocsReleasesPeers

favela-gateway / test / lens.test.js

246 lines · javascript ref:
// The lens: the loser-club bsky custom feed, served by the gateway in place of favela-web (hyper-social #80 G2).
//
// favela-web's rules, kept (feedgen.js): a community's feed is its room members' app.bsky.feed.post records,
// newest createdAt first; ONLY TID rkeys (one bad rkey — loser-club's own 'self.hello' — makes bsky reject
// the whole response); cursor a string or ABSENT, never null. What changes: posts come from the repos the
// membrane already fronts, indexed per member only when that member's head moves — never on bsky's request.

import test from 'brittle'
import { createLens } from '../lib/lens.js'

const ROOM = '91ce4dbcb6b4fe0cfa489582cb9931923c05e0d14cf0a5c939d8334edefe21ed'
const LC = 'did:plc:wqnwulql7ndcz6xmtvaovl6u'
const WJ = 'did:plc:wj62ebbusln6gxo5azo6ogzo'
const E2 = 'did:plc:e2jevhuurrhlm36ufk7fklpg'
const FEED = `at://${LC}/app.bsky.feed.generator/loser-club`

// TIDs in increasing time order.
const T = ['3mr2222222222', '3mr3333333333', '3mr4444444444', '3mr5555555555', '3mr6666666666', '3mr7777777777']
const post = (rkey, createdAt) => ({ uri: 'x/' + rkey, value: { $type: 'app.bsky.feed.post', text: rkey, createdAt } })

function fakeMembrane ({ members = [WJ, E2], records, revs }) {
  const listeners = []
  const labelListeners = []
  const state = { members, records, revs, listCalls: 0, watchers: [] }
  const membrane = {
    state,
    extensions: { xrpc: new Map(), ws: new Map() },
    relay: { seq: { core: { on: (ev, fn) => { if (ev === 'append') listeners.push(fn) }, off: () => {} } } },
    appendNow: () => { for (const fn of listeners) fn() },
    // The router's onLabels: fires when a DID's front first opens a label core (atproto-did-peer).
    labelsAppear: (did) => { for (const fn of labelListeners) fn(did, { labels: {} }) },
    metrics: () => ({ dids: Object.keys(state.revs).map((did) => ({ did, rev: state.revs[did] })) }),
    peers: {
      onLabels: (fn) => { labelListeners.push(fn); return () => { const i = labelListeners.indexOf(fn); if (i >= 0) labelListeners.splice(i, 1) } },
      isFronted: (did) => did in state.revs,
      front: async (did) => ({
        listRecords: async (col, { limit = 100, cursor = null } = {}) => {
          state.listCalls++
          const all = (state.records[did] || []).filter(() => col === 'app.bsky.feed.post')
          const start = cursor ? all.findIndex((r) => r.uri === cursor) + 1 : 0
          const page = all.slice(start, start + limit)
          return { records: page, cursor: start + limit < all.length ? page[page.length - 1].uri : undefined }
        }
      })
    },
    communityRooms: async () => ({
      members: async () => state.members,
      communityDid: async () => LC,
      curation: async () => ({ curator: 'roster', params: {} }),
      watch: async (roomKey, onChange) => { state.watchers.push(onChange); await onChange(state.members); return async () => {} }
    })
  }
  return membrane
}

const baseRecords = () => ({
  [WJ]: [post(T[0], '2026-09-01T00:00:00Z'), post(T[3], '2026-09-04T00:00:00Z'), post('self.hello', '2026-09-09T00:00:00Z')],
  [E2]: [post(T[1], '2026-09-02T00:00:00Z'), post(T[4], '2026-09-05T00:00:00Z')]
})

async function started (opts = {}) {
  const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' }, ...opts })
  const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5 })
  await lens.start()
  return { m, lens }
}

const skeleton = (m, q) => m.extensions.xrpc.get('app.bsky.feed.getFeedSkeleton')({ query: new URLSearchParams(q) })

test('★ the skeleton: members\' posts, newest createdAt first, TID rkeys only', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  const r = await skeleton(m, { feed: FEED })
  t.is(r.status, 200)
  t.alike(r.body.feed.map((f) => f.post), [
    `at://${E2}/app.bsky.feed.post/${T[4]}`,
    `at://${WJ}/app.bsky.feed.post/${T[3]}`,
    `at://${E2}/app.bsky.feed.post/${T[1]}`,
    `at://${WJ}/app.bsky.feed.post/${T[0]}`
  ], 'self.hello is not a TID, so it is not in the feed')
  t.absent('cursor' in r.body, 'exhausted: cursor ABSENT, never null')
})

test('★ pagination: pages concatenate to the whole feed, and only the last lacks a cursor', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  const p1 = (await skeleton(m, { feed: FEED, limit: '3' })).body
  t.is(p1.feed.length, 3)
  t.is(typeof p1.cursor, 'string', 'a string cursor while more remain')
  const p2 = (await skeleton(m, { feed: FEED, limit: '3', cursor: p1.cursor })).body
  t.is(p2.feed.length, 1)
  t.absent('cursor' in p2)
  t.ok(!/\u0000/.test(p1.cursor), 'the cursor is opaque text (favela-web\'s carried NUL bytes)')
})

test('limit: default 50, at most 100', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  t.is((await skeleton(m, { feed: FEED, limit: '1000' })).body.feed.length, 4, 'clamped, never an error')
})

test('⛔ an unknown feed is UnsupportedAlgorithm (400), as the feed-generator contract says', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  const r = await skeleton(m, { feed: `at://${LC}/app.bsky.feed.generator/nope` })
  t.is(r.status, 400)
  t.is(r.body.error, 'UnsupportedAlgorithm')
})

test('describeFeedGenerator names the community DID (the lexicon requires `did`)', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  const r = await m.extensions.xrpc.get('app.bsky.feed.describeFeedGenerator')({ query: new URLSearchParams() })
  t.alike(r.body, { did: LC, feeds: [{ uri: FEED }] })
})

test('⛔ requests never rebuild the index; a member whose head MOVED is re-read on the next relay append', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  const before = m.state.listCalls
  await skeleton(m, { feed: FEED }); await skeleton(m, { feed: FEED })
  t.is(m.state.listCalls, before, 'two skeleton requests, zero repo reads')

  m.state.records[WJ].push(post(T[5], '2026-09-06T00:00:00Z'))
  m.appendNow() // an append whose head did not move for anyone: nothing to re-read
  await new Promise((resolve) => setTimeout(resolve, 40))
  t.is(m.state.listCalls, before, 'no head moved: no re-read')

  m.state.revs[WJ] = 'r2'
  m.appendNow()
  await new Promise((resolve) => setTimeout(resolve, 60))
  t.ok(m.state.listCalls > before, 'wj62\'s head moved: re-read')
  t.is((await skeleton(m, { feed: FEED })).body.feed[0].post, `at://${WJ}/app.bsky.feed.post/${T[5]}`, 'and its new post leads the feed')
})

test('a member removed from the room leaves the feed', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  m.state.members = [WJ]
  for (const w of m.state.watchers) await w([WJ])
  const r = (await skeleton(m, { feed: FEED })).body
  t.ok(r.feed.every((f) => f.post.includes(WJ)), 'only wj62 remains')
})

test('a member the membrane does not front is reported, not silently missing', async (t) => {
  const { m, lens } = await started({ members: [WJ, E2, 'did:plc:stranger'] })
  t.teardown(() => lens.stop())
  const x = lens.metrics()
  t.alike(x.feeds[0].unfronted, ['did:plc:stranger'])
  t.is(x.feeds[0].members, 3)
  t.is(x.feeds[0].posts, 4, 'indexed posts for the fronted members')
  t.is(x.feeds[0].communityDid, LC)
  t.is(x.feeds[0].curation, 'roster')
})

test('labels: queryLabels answers an empty, valid set; subscribeLabels is served', async (t) => {
  const { m, lens } = await started()
  t.teardown(() => lens.stop())
  const r = await m.extensions.xrpc.get('com.atproto.label.queryLabels')({ query: new URLSearchParams({ uriPatterns: '*' }) })
  t.alike(r.body, { labels: [] })
  t.ok(m.extensions.ws.has('/xrpc/com.atproto.label.subscribeLabels'))
})

test('stop removes every registered method — a stopped lens serves nothing', async (t) => {
  const { m, lens } = await started()
  await lens.stop()
  t.is(m.extensions.xrpc.size, 0)
  t.is(m.extensions.ws.size, 0)
})

// ── labels (member labels, #80 step 4): the lens hosts the label surfaces and drives the projector ───────
function fakeLabels () {
  const s = { watched: [], started: 0, stopped: 0 }
  return {
    s,
    start: async () => { s.started++ },
    stop: async () => { s.stopped++ },
    watch: (did, front) => { s.watched.push({ did, front }) },
    metrics: () => ({ kept: 3 }),
    xrpc: { 'com.atproto.label.queryLabels': async () => ({ status: 200, body: { labels: ['real'] } }) },
    ws: { '/xrpc/com.atproto.label.subscribeLabels': () => {} }
  }
}
function fakeProjector () {
  const applied = []
  return { applied, apply: async (f) => { applied.push(f); return {} }, metrics: () => ({ feeds: { 'loser-club': { added: 2 } } }), close: async () => {} }
}

test('★ with labels injected: the real queryLabels is served, the community DID\'s front is watched', async (t) => {
  const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' } })
  const labels = fakeLabels()
  const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5, labels })
  await lens.start()
  t.teardown(() => lens.stop())
  t.is(labels.s.started, 1)
  const r = await m.extensions.xrpc.get('com.atproto.label.queryLabels')({ query: new URLSearchParams() })
  t.alike(r.body.labels, ['real'], 'the labels module answers, not the empty placeholder')
  await new Promise((resolve) => setTimeout(resolve, 20))
  t.ok(labels.s.watched.some((w) => w.did === LC && w.front), 'the community DID\'s front is handed to the labels module')
  t.alike(lens.metrics().labels, { kept: 3 })
})

test('★ with a projector: every roster change is applied, with the community DID', async (t) => {
  const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' } })
  const memberLabels = fakeProjector()
  const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5, memberLabels })
  await lens.start()
  t.teardown(() => lens.stop())
  t.alike(memberLabels.applied[0], { id: 'loser-club', communityDid: LC, members: [WJ, E2] })
  await m.state.watchers[0]([WJ])
  t.alike(memberLabels.applied[memberLabels.applied.length - 1].members, [WJ], 'a member leaving is applied too')
  t.alike(lens.metrics().memberLabels, { feeds: { 'loser-club': { added: 2 } } })
})

// ⛔ MEASURED 2026-10-14 on stortron: the projector's only run was at boot, while loser-club's hpp was not yet a
// labeler ("this DID is not a labeler"). hpp was restarted with `labeler: true` and began announcing a label
// core, and nothing re-ran — the roster had not changed, so the badges never appeared. A labeler coming online
// IS the event: the router's onLabels fires when the community DID's front first opens a label core.
test('⛔ the community DID gaining a label core re-runs the projector; another DID does not', async (t) => {
  const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' } })
  const memberLabels = fakeProjector()
  const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5, memberLabels })
  await lens.start()
  t.teardown(() => lens.stop())
  const before = memberLabels.applied.length
  t.is(before, 1, 'the boot run')

  m.labelsAppear(WJ)
  t.is(memberLabels.applied.length, before, 'a member DID\'s label core is not the labeler coming online')

  m.labelsAppear(LC)
  t.is(memberLabels.applied.length, before + 1, 'the community DID\'s label core re-runs the projector')
  t.alike(memberLabels.applied[before], { id: 'loser-club', communityDid: LC, members: [WJ, E2] }, 'with the current roster')
})

test('stop unsubscribes from onLabels — a stopped lens projects nothing', async (t) => {
  const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' } })
  const memberLabels = fakeProjector()
  const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5, memberLabels })
  await lens.start()
  await lens.stop()
  const n = memberLabels.applied.length
  m.labelsAppear(LC)
  t.is(memberLabels.applied.length, n)
})