// favela-web labeler export (lens export §3.6, moderation half) — the atproto labeler emitter surfaces,
// validated against REAL atproto label crypto (atproto-identity). Run: node test/labeler-export.test.mjs
import { LabelerExport, toWireLabel, uriMatches } from '../lib/labeler-export.js'
import { buildLabel, signLabel, verifyLabel } from 'atproto-identity/lib/labels.js'
import { keyFromPrivate } from 'atproto-identity/lib/atproto-identity.js'
import b4a from 'b4a'
const assert = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } console.log(' ok -', m) }
const HOST = 'favela.example.net'
const LABELER_DID = 'did:web:' + HOST
const key = keyFromPrivate(b4a.from('11'.repeat(32), 'hex')) // { privateKey, publicKey, didKey }
// ── uriMatches ──
assert(uriMatches('at://did:plc:x/app.bsky.feed.post/1', '*') === true, 'uriMatches: * matches everything')
assert(uriMatches('at://did:plc:x/app.bsky.feed.post/1', 'at://did:plc:x/*') === true, 'uriMatches: suffix wildcard')
assert(uriMatches('at://did:plc:x/y/1', 'at://did:plc:z/*') === false, 'uriMatches: non-matching prefix → false')
assert(uriMatches('did:plc:x', 'did:plc:x') === true, 'uriMatches: exact')
// ── build real signed labels stamped with the labeler DID as src ──
const SUB1 = 'at://did:plc:alice/app.bsky.feed.post/aaa'
const SUB2 = 'at://did:plc:bob/app.bsky.feed.post/bbb'
const l1 = signLabel(buildLabel({ src: LABELER_DID, uri: SUB1, val: 'spam', cts: '2026-07-15T00:00:00.000Z' }), key.privateKey)
const l2 = signLabel(buildLabel({ src: LABELER_DID, uri: SUB2, val: '!hide', cts: '2026-07-15T00:01:00.000Z' }), key.privateKey)
const foreign = signLabel(buildLabel({ src: 'did:web:other.example', uri: SUB1, val: 'nsfw', cts: '2026-07-15T00:02:00.000Z' }), key.privateKey)
// ── toWireLabel: sig → { $bytes: base64 } round-trips to the raw signature ──
const w1 = toWireLabel(l1)
assert(w1.src === LABELER_DID && w1.val === 'spam' && w1.cts === l1.cts, 'toWireLabel carries src/val/cts')
assert(w1.sig && typeof w1.sig.$bytes === 'string', 'toWireLabel encodes sig as { $bytes: base64 }')
assert(b4a.equals(b4a.from(w1.sig.$bytes, 'base64'), b4a.from(l1.sig)), 'wire $bytes base64-decodes back to the exact signature')
assert(verifyLabel(l1, key.didKey), 'the signed label verifies against the labeler signing did:key (byte-exact atproto)')
// ── LabelerExport ──
try { new LabelerExport({}); assert(false, 'ctor should require config') } catch { assert(true, 'ctor requires { labelerDid, signingDidKey, hostname, source }') }
const source = { labels: async () => [l1, l2, foreign] }
const lx = new LabelerExport({ labelerDid: LABELER_DID, signingDidKey: key.didKey, hostname: HOST, source, labelValues: ['spam', '!hide'] })
// verificationMethod: the #atproto_label key bsky verifies our sigs against
const vm = lx.verificationMethod()
assert(vm.id === LABELER_DID + '#atproto_label' && vm.type === 'Multikey' && vm.controller === LABELER_DID, 'verificationMethod id/type/controller')
assert(vm.publicKeyMultibase === key.didKey.replace(/^did:key:/, '') && vm.publicKeyMultibase.startsWith('z'), 'publicKeyMultibase = did:key multibase (z…)')
// service entry
const svc = lx.service()
assert(svc.id === '#atproto_labeler' && svc.type === 'AtprotoLabeler' && svc.serviceEndpoint === `https://${HOST}`, '#atproto_labeler service → our HTTPS host')
// queryLabels: source filter (only OUR labels by default), uriPattern filter, and wire shape
const q1 = await lx.queryLabels({ uriPatterns: ['*'] })
assert(q1.labels.length === 2, 'queryLabels(*) returns only OUR two labels (foreign src excluded by default)')
assert(q1.labels.every((l) => l.src === LABELER_DID && l.sig && l.sig.$bytes), 'queryLabels returns wire labels (src=us, sig=$bytes)')
const q2 = await lx.queryLabels({ uriPatterns: ['at://did:plc:alice/*'] })
assert(q2.labels.length === 1 && q2.labels[0].uri === SUB1, 'queryLabels filters by uriPattern')
const qSrc = await lx.queryLabels({ uriPatterns: ['*'], sources: ['did:web:other.example'] })
assert(qSrc.labels.length === 1 && qSrc.labels[0].src === 'did:web:other.example', 'queryLabels honors an explicit sources filter')
// pagination
const p1 = await lx.queryLabels({ uriPatterns: ['*'], limit: 1 })
assert(p1.labels.length === 1 && p1.cursor === '1', 'queryLabels paginates (limit=1 → cursor)')
const p2 = await lx.queryLabels({ uriPatterns: ['*'], limit: 1, cursor: p1.cursor })
assert(p2.labels.length === 1 && p2.cursor === undefined, 'queryLabels second page, no further cursor')
// service record
const rec = lx.serviceRecord({ createdAt: '2026-07-15T00:00:00.000Z' })
assert(rec.$type === 'app.bsky.labeler.service' && rec.policies.labelValues.includes('spam') && rec.policies.labelValues.includes('!hide'), 'serviceRecord declares the labeler + its label values')
console.log('\nLABELER-EXPORT OK: identity (VM + service) + queryLabels wire shape + labeler.service record, byte-exact atproto crypto')