OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / feed-base.test.js

133 lines · javascript ref:
// The real Autobase binding. Everything above this file was provable against a Map; this is where
// it meets the linearizer, and the properties worth asserting are the ones a Map cannot show:
// a write is not state until it linearizes, and the view survives a restart because it is REBUILT
// from the ops rather than stored.

import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import { join } from '#node:path'
import Corestore from 'corestore'
import { RecordStore } from '../lib/record-store.js'
import { Feed } from '../lib/feed-base.js'
import { DELETED, ABSENT } from '../lib/log.js'
import { attest } from './helpers.js'

const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'favela-feed-'))
const rec = (body) => ({ frontmatter: { createdAt: '2026-09-06T00:00:00Z' }, body })

// Corestore 7.x is on the RocksDB storage engine, so storage is a directory, not RAM.
async function feed (storage = tmp()) {
  const f = new Feed(new Corestore(storage), null, { store: new RecordStore(tmp()) })
  await f.ready()
  await f.addDevice(await attest(f))
  await f.update()
  return f
}

test('the founding device admits itself and becomes writable', async (t) => {
  const f = await feed()
  t.teardown(() => f.close())

  t.alike(await f.devices(), [f.deviceKey], 'roster is exactly this device')
  t.is((await f.founder()).device, f.deviceKey, 'genesis recorded in the view')
    t.ok((await f.founder()).identityRoot, 'and it bound an identity root — the feed has an owner')
  t.ok(f.writable, 'and the feed is writable')
})

test('a record round-trips through the linearized view', async (t) => {
  const f = await feed()
  t.teardown(() => f.close())

  await f.put('post', 'a1', rec('hello from a real base'))
  await f.update()

  const r = await f.resolve('post', 'a1')
  t.is(r.status, 'ok')
  t.is(r.record.body, 'hello from a real base\n')
  t.ok(r.pointer.$oid === null || typeof r.pointer.$oid === 'string', 'the log carries a pointer, not the record')
})

test('★ the log carries pointers, never bytes — its size does not scale with the record', async (t) => {
  // NOT a threshold. A threshold only ever encodes whatever number today produced — this test
  // passed by accident across the autobase→autobee port because the encoding changed underneath it.
  // The actual property is that a 20KB record and a 1-byte one cost the SAME in the append-only
  // core, because only the pointer is appended. A regression that inlined the record would blow the
  // difference open no matter what the absolute numbers are.
  const measure = async (body) => {
    const f = await feed()
    await f.put('post', 'a1', rec(body))
    await f.update()
    let bytes = 0
    for (let i = 0; i < f.base.local.length; i++) {
      const b = await f.base.local.get(i)
      bytes += b == null ? 0 : (b.byteLength != null ? b.byteLength : Buffer.byteLength(JSON.stringify(b)))
    }
    const read = (await f.get('post', 'a1')).body
    await f.close()
    return { bytes, read }
  }

  const small = await measure('x')
  const big = await measure('x'.repeat(20000))

  t.is(big.read.length, 20001, 'the 20KB record is fully readable')
  t.is(small.read.length, 2, 'and so is the small one')
  t.ok(Math.abs(big.bytes - small.bytes) < 64,
    'log cost is the same either way (' + small.bytes + ' vs ' + big.bytes + ' bytes)')
})

test('del is a tombstone in the view, distinct from absent', async (t) => {
  const f = await feed()
  t.teardown(() => f.close())

  await f.put('post', 'a1', rec('x'))
  await f.update()
  await f.del('post', 'a1')
  await f.update()

  t.is((await f.resolve('post', 'a1')).status, DELETED)
  t.is((await f.resolve('post', 'never')).status, ABSENT)
  t.alike(await f.list(), [], 'and it leaves the listing')
})

test('list scopes by collection and skips reserved keys', async (t) => {
  const f = await feed()
  t.teardown(() => f.close())

  await f.put('post', 'b1', rec('p'))
  await f.put('like', 'a1', rec(''))
  await f.update()

  t.alike((await f.list()).map((x) => x.key), ['like/a1', 'post/b1'], 'roster keys never appear')
  t.alike((await f.list('post')).map((x) => x.key), ['post/b1'])
})

test('★ the view is REBUILT from the ops across a restart', async (t) => {
  const storage = tmp()
  const records = tmp()

  const a = new Feed(new Corestore(storage), null, { store: new RecordStore(records) })
  await a.ready()
  await a.addDevice(await attest(a))
  await a.update()
  await a.put('post', 'a1', rec('survives'))
  await a.update()
  const key = a.key
  await a.close()

  const b = new Feed(new Corestore(storage), key, { store: new RecordStore(records) })
  await b.ready()
  t.teardown(() => b.close())
  await b.update()

  t.is((await b.get('post', 'a1')).body, 'survives\n', 'replayed, not restored')
  t.alike(await b.devices(), [b.deviceKey], 'and the roster came back with it')
})

test('⛔ the view cannot be written directly — a write is an append', async (t) => {
  const f = await feed()
  t.teardown(() => f.close())
  await t.exception(() => f._readKv().set('post/a1', { $oid: 'x', size: 1 }))
})