OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / test / search-federation.test.js

109 lines · javascript ref:
// Tests for the Phase-1 federation/discovery engine (search-federation.cjs):
// the trust graph (hop→tier) and the dedup-and-rank merge across sources.
import test from 'node:test'
import assert from 'node:assert/strict'
import fed from '../backend/search-federation.cjs'
const { buildTrustGraph, trustRowsToEdges, resourceRowToCandidate, mergeFederated } = fed

const ME = 'rootME'
const A = 'curatorA'   // I follow A
const B = 'curatorB'   // A follows B (hop 2 from me)
const Z = 'strangerZ'  // no path

test('buildTrustGraph resolves hop distance + tier from self', () => {
  const g = buildTrustGraph(ME, [{ from: ME, to: A }, { from: A, to: B }], { maxFollowHops: 2 })
  assert.equal(g.hopOf(ME), 0)
  assert.equal(g.tierOf(ME), 'self')
  assert.equal(g.hopOf(A), 1)
  assert.equal(g.tierOf(A), 'followed')
  assert.equal(g.hopOf(B), 2)            // transitive within maxFollowHops
  assert.equal(g.tierOf(B), 'followed')
  assert.equal(g.hopOf(Z), Infinity)
  assert.equal(g.tierOf(Z), 'default')
})

test('maxFollowHops bounds the followed tier', () => {
  const g = buildTrustGraph(ME, [{ from: ME, to: A }, { from: A, to: B }], { maxFollowHops: 1 })
  assert.equal(g.tierOf(A), 'followed')  // hop 1
  assert.equal(g.tierOf(B), 'default')   // hop 2 > maxFollowHops 1
})

test('trustRowsToEdges uses ONLY verified provenance (memberkey + signed body)', () => {
  const edges = trustRowsToEdges([
    { memberkey: ME, json: { curatorRoot: A } }, // valid: sheets memberkey author + signed body
    { memberkey: A, json: { curatorRoot: B } },  // valid
    { from: A, curatorRoot: B },                   // forged: unsigned top-level → DROPPED
    { memberkey: ME, curatorRoot: A },             // curatorRoot not in signed body → DROPPED
    { json: { curatorRoot: A } },                  // no memberkey author → DROPPED
    { json: {} },                                  // incomplete → DROPPED
    null,                                          // null row → DROPPED (no throw)
  ])
  // matches the real sheets contract (row.memberkey); a Sybil cannot inject a
  // follow edge via attacker-controllable fields
  assert.deepEqual(edges, [{ from: ME, to: A }, { from: A, to: B }])
})

test('trustRowsToEdges hex-normalizes a Buffer memberkey', () => {
  const mk = Buffer.from('deadbeef', 'hex')
  const edges = trustRowsToEdges([{ memberkey: mk, json: { curatorRoot: A } }])
  assert.deepEqual(edges, [{ from: 'deadbeef', to: A }])
})

test('resourceRowToCandidate hex-normalizes a Buffer memberkey (deterministic dedup)', () => {
  const c = resourceRowToCandidate({ json: { name: 'X', driveKey: 'dk' }, memberkey: Buffer.from('aa', 'hex') })
  assert.equal(c.signerPubkey, 'aa', 'Buffer memberkey → hex, so the dedup tie-break is order-stable')
})

test('mergeFederated dedup winner is independent of source order (deterministic)', () => {
  const g = buildTrustGraph(ME, [{ from: ME, to: A }], { maxFollowHops: 2 })
  // same doc, same trustHop+tf from two followed sources → tie broken by signerPubkey
  const c1 = { docId: 'same', driveKey: 'd', path: '/', title: 'x', tf: 5, signerPubkey: 'aaa', contentHash: 'h1' }
  const c2 = { docId: 'same', driveKey: 'd', path: '/', title: 'x', tf: 5, signerPubkey: 'bbb', contentHash: 'h2' }
  const fwd = mergeFederated([{ rootPubkey: A, candidates: [c1] }, { rootPubkey: A, candidates: [c2] }], g)
  const rev = mergeFederated([{ rootPubkey: A, candidates: [c2] }, { rootPubkey: A, candidates: [c1] }], g)
  assert.equal(fwd[0].signerPubkey, rev[0].signerPubkey, 'same winner regardless of array order')
  assert.equal(fwd[0].signerPubkey, 'aaa') // lexicographically smaller signerPubkey wins
})

test('resourceRowToCandidate maps a descriptor row to a ranking candidate', () => {
  const c = resourceRowToCandidate({ json: { name: 'Keet', driveKey: 'dk', link: 'pear://keet', path: '/' }, memberkey: 'mk' }, 3)
  assert.equal(c.title, 'Keet')
  assert.equal(c.driveKey, 'dk')
  assert.equal(c.link, 'pear://keet')
  assert.equal(c.tf, 3)
  assert.equal(c.signerPubkey, 'mk')
  assert.match(c.docId, /^[0-9a-f]{16}$/)
})

test('mergeFederated dedups by doc keeping the best-trust copy', () => {
  const g = buildTrustGraph(ME, [{ from: ME, to: A }], { maxFollowHops: 2 })
  const doc = { docId: 'same', driveKey: 'd1', path: '/', title: 'Shared', tf: 5, publishedAt: 0 }
  const out = mergeFederated([
    { rootPubkey: Z, candidates: [{ ...doc }] },   // default (hop ∞)
    { rootPubkey: A, candidates: [{ ...doc }] },    // followed (hop 1) — should win
  ], g)
  assert.equal(out.length, 1, 'same doc deduped')
  assert.equal(out[0].tier, 'followed')
  assert.equal(out[0].trustHop, 1)
})

test('mergeFederated ranks a followed source above a default source', () => {
  const g = buildTrustGraph(ME, [{ from: ME, to: A }], { maxFollowHops: 2 })
  const mk = (docId, driveKey) => ({ docId, driveKey, path: '/', title: docId, tf: 5, publishedAt: 0 })
  const out = mergeFederated([
    { rootPubkey: Z, candidates: [mk('zdoc', 'dz')] },  // default
    { rootPubkey: A, candidates: [mk('adoc', 'da')] },  // followed
  ], g)
  assert.equal(out[0].docId, 'adoc', 'followed-curator result ranks first')
  assert.equal(out[1].docId, 'zdoc')
})

test('mergeFederated includes your own (self/hop-0) results at the top tier', () => {
  const g = buildTrustGraph(ME, [], { maxFollowHops: 2 })
  const out = mergeFederated([
    { rootPubkey: ME, candidates: [{ docId: 'mine', driveKey: 'dm', path: '/', title: 'Mine', tf: 5 }] },
  ], g)
  assert.equal(out[0].tier, 'self')
  assert.equal(out[0].trustHop, 0)
})