OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room / test / membrane-labels.test.mjs

68 lines · javascript ref:
// serveMembrane `labels` — a labeler DID's signed label log, served beside its firehose (hyper-social #80,
// member labels step 2).
//
// The shared __label__ log lives in the DID's StateBase, which only co-owners replicate. A keyless
// consumer (the gateway's lens, answering bsky's queryLabels/subscribeLabels) reaches a DID through the
// membrane announce, so the labels must be projected into a plain hypercore and named there — the same
// pattern as the firehose. Without `labels` nothing changes: no core is opened and the announce carries
// labelKey:null, so a consumer can branch on the field.
//
// Run: bare test/membrane-labels.test.mjs
import '../lib/bare-globals.js'
import test from 'brittle'
import b4a from 'b4a'
import fs from 'bare-fs'
import { serveMembrane } from '../lib/serve-membrane.js'
import { LabelRoom } from '../lib/label-room.js'
import { openCorestore } from 'atproto-store/lib/corestore.js'
import { decodeLabel } from 'atproto-identity/lib/labels.js'

const DID = 'did:plc:membranelabels000000000'
const tmp = () => { const d = '/tmp/ar-ml-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
const fakeSwarm = () => ({ on () {}, off () {}, keyPair: { publicKey: b4a.alloc(32, 7) }, join () { return { flushed: async () => {} } } })
const fakeDevice = () => ({ repo: { listRecords: async () => [] }, async firehoseLog () { return [] } })

function mockSb () {
  const m = new Map()
  return {
    async set (k, v) { m.set(k, v) },
    async del (k) { m.delete(k) },
    async get (k) { return m.has(k) ? m.get(k) : null },
    async list (prefix) { const o = {}; for (const [k, v] of m) if (k.startsWith(prefix)) o[k] = v; return o }
  }
}

const KEY = b4a.from('0b1b6cd4c1ef3ea5a8b0f9c2c5e0d3e7a1c4f6b8d0e2f4a6c8e0b2d4f6a8c0e2', 'hex')

test('★ labels: the signed log is projected into a served core and named in the announce', async (t) => {
  const room = new LabelRoom(mockSb(), { src: DID, privateKey: KEY, holds: () => true })
  await room.ensure('did:plc:member', 'loser-club-member', { cts: '2026-10-13T00:00:00.000Z' })
  await room.tick()

  const m = await serveMembrane(fakeDevice(), { corestore: openCorestore(tmp()), swarm: fakeSwarm(), did: DID, labels: room })
  t.ok(m.labelKey, 'a label core is opened and its key exported')
  const a = m.announcement()
  t.is(a.labelKey, m.labelKey, 'the announce names it')
  t.is(a.labelLength, 0, 'nothing projected before the first syncLabels')

  t.is(await m.syncLabels(), 1, 'syncLabels appends the signed label')
  t.is(m.announcement().labelLength, 1, 'and the announce reports the new length')
  const l = decodeLabel(await m.labelCore.get(0))
  t.is(l.src, DID)
  t.is(l.uri, 'did:plc:member')
  t.is(l.val, 'loser-club-member')

  t.is(await m.syncLabels(), 0, 'idempotent: nothing new, nothing appended')
})

test('without labels nothing changes: no core, labelKey:null, syncLabels is a no-op', async (t) => {
  const m = await serveMembrane(fakeDevice(), { corestore: openCorestore(tmp()), swarm: fakeSwarm(), did: DID })
  t.is(m.labelKey, null)
  t.is(m.labelCore, null)
  const a = m.announcement()
  t.is(a.labelKey, null, 'the field exists, null')
  t.is(a.labelLength, null)
  t.is(await m.syncLabels(), 0)
  t.ok(a.firehoseKey, 'the existing announce fields are unchanged')
})