OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / test / encrypted-autobase-helper.test.js

106 lines · javascript ref:
// Tests for backend/encrypted-autobase-helper.cjs — the reusable encrypted
// multi-writer Autobase manager (I4). Real Autobase convergence over a direct
// corestore wire (no swarm), with a trivial append-log consumer applyOp.
import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import Corestore from 'corestore'
import b4a from 'b4a'
import helperMod from '../backend/encrypted-autobase-helper.cjs'
const { createEncryptedAutobaseManager, addWriterOp } = helperMod

const ENC = b4a.alloc(32, 0x5a)
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const tmps = []
function wireStores (a, b) {
  const s1 = a.replicate(true); const s2 = b.replicate(false)
  s1.on('error', () => {}); s2.on('error', () => {})
  s1.pipe(s2).pipe(s1)
  return () => { try { s1.destroy() } catch {} try { s2.destroy() } catch {} }
}
async function until (label, pred, ms = 8000) {
  const t0 = Date.now()
  while (Date.now() - t0 < ms) { if (await pred()) return true; await sleep(120) }
  throw new Error('timeout: ' + label)
}
// trivial consumer reducer: append each op to an ordered log
const applyOp = async (op, view) => {
  const head = await view.get('meta!count').catch(() => null)
  const count = head && Number.isFinite(head.value) ? head.value : 0
  await view.put('item!' + String(count).padStart(12, '0'), op)
  await view.put('meta!count', count + 1)
}
async function items (mgr) {
  await mgr.update()
  const out = []
  for await (const e of mgr.view.createReadStream({ gte: 'item!', lt: 'item!~' })) out.push(e.value)
  return out
}

async function withManagers (fn) {
  const opened = []
  const unwires = []
  const mk = async ({ encryptionKey = ENC, ...opts } = {}) => {
    const dir = await mkdtemp(join(tmpdir(), 'eab-')); tmps.push(dir)
    const store = new Corestore(dir)
    const mgr = await createEncryptedAutobaseManager(store, { applyOp, encryptionKey, ...opts }).ready()
    const dev = { mgr, store }
    opened.push(dev)
    return dev
  }
  const wire = (a, b) => unwires.push(wireStores(a.store, b.store))
  try { await fn({ mk, wire }) }
  finally {
    for (const u of unwires) { try { u() } catch {} }
    for (const { mgr } of opened) { try { await mgr.close() } catch {} }
    for (const { store } of opened) { try { await store.close() } catch {} }
  }
}

test('two encrypted managers converge after writer promotion', async () => {
  await withManagers(async ({ mk, wire }) => {
    const A = await mk({ bootstrap: null, namespace: 'A' })
    assert.equal(A.mgr.writable, true)
    await A.mgr.append({ v: 'a1' })

    const B = await mk({ bootstrap: A.mgr.key })
    wire(A, B)
    await until('B replicates a1', async () => (await items(B.mgr)).some((x) => x.v === 'a1'))
    assert.equal(B.mgr.writable, false) // read-only before invite

    await A.mgr.addWriter(B.mgr.localKey)
    await until('B writable', async () => { await B.mgr.update(); return B.mgr.writable })
    await B.mgr.append({ v: 'b1' })

    await until('converge', async () => {
      const a = (await items(A.mgr)).map((x) => x.v).sort()
      const b = (await items(B.mgr)).map((x) => x.v).sort()
      return a.length === 2 && JSON.stringify(a) === JSON.stringify(b)
    })
    assert.deepEqual((await items(A.mgr)).map((x) => x.v).sort(), ['a1', 'b1'])
  })
})

test('a manager without the encryption key replicates bytes but cannot read', async () => {
  await withManagers(async ({ mk, wire }) => {
    const A = await mk({ bootstrap: null, namespace: 'A' })
    await A.mgr.append({ v: 'secret' })
    const C = await mk({ bootstrap: A.mgr.key, encryptionKey: null }) // no key
    wire(A, C)
    for (let i = 0; i < 16; i++) { await C.mgr.update().catch(() => {}); await sleep(120) }
    const leaked = await items(C.mgr).catch(() => [])
    assert.equal(leaked.length, 0) // no plaintext without the key
  })
})

test('addWriterOp rejects a non-hex writer key', () => {
  assert.throws(() => addWriterOp('not-hex'), /64-char hex/)
  assert.equal(addWriterOp('aa'.repeat(32)).type, '__autobase_add_writer__')
})

test.after(async () => {
  for (const dir of tmps) { try { await rm(dir, { recursive: true, force: true }) } catch {} }
})