OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / test / browser-state.test.js

79 lines · javascript ref:
// Browser-state (multi-device sync) reducer tests — PURE, no autobase.
// Mirrors the autobee-catalog acceptance criteria for the bookmark dataset.
import test from 'node:test'
import assert from 'node:assert/strict'
import opsMod from '../backend/browser-state-ops.cjs'
import applyMod from '../backend/browser-state-apply.cjs'
const { addBookmarkOp, removeBookmarkOp, validateOp, sanitizeBookmark, MAX_OP_BYTES } = opsMod
const { linearize, applyView, applyTagged } = applyMod

const bm = (url, title = url) => ({ url, title })
const tag = (writer, seq, op) => ({ writer, seq, op })

test('two devices converge regardless of input arrangement', () => {
  const a = [tag('A', 0, addBookmarkOp(bm('hyper://a', 'A'))), tag('A', 1, addBookmarkOp(bm('hyper://b', 'B')))]
  const d = [tag('B', 0, addBookmarkOp(bm('hyper://c', 'C'))), tag('B', 1, removeBookmarkOp('hyper://a'))]
  const v1 = applyTagged([...a, ...d])
  const v2 = applyTagged([...d, ...a])
  assert.deepEqual(v1, v2)
  // 'a' added by A then removed later by B → gone; b and c remain.
  assert.deepEqual(v1.bookmarks.map((x) => x.url).sort(), ['hyper://b', 'hyper://c'])
})

test('same ordered log rebuilds the same view after restart', () => {
  const ops = [addBookmarkOp(bm('u1')), addBookmarkOp(bm('u2')), removeBookmarkOp('u1')]
  assert.deepEqual(applyView(ops), applyView(ops))
  assert.deepEqual(applyView(ops).bookmarks.map((x) => x.url), ['u2'])
})

test('conflict: later op in order wins for add/remove', () => {
  // remove then re-add → present, with the newer title
  let v = applyView([addBookmarkOp(bm('u', 'Old')), removeBookmarkOp('u'), addBookmarkOp(bm('u', 'New'))])
  assert.equal(v.bookmarks.length, 1)
  assert.equal(v.bookmarks[0].title, 'New')
  // add then remove → absent
  v = applyView([addBookmarkOp(bm('u')), removeBookmarkOp('u')])
  assert.equal(v.bookmarks.length, 0)
})

test('add updates the whole record (re-add changes title), url is identity', () => {
  const v = applyView([addBookmarkOp(bm('u', 'First')), addBookmarkOp(bm('u', 'Second'))])
  assert.equal(v.bookmarks.length, 1)
  assert.equal(v.bookmarks[0].url, 'u')
  assert.equal(v.bookmarks[0].title, 'Second')
})

test('addedAt is stored but never affects ordering', () => {
  // Later-in-order op wins even though it carries an EARLIER addedAt.
  const v = applyView([
    addBookmarkOp({ url: 'u', title: 'A', addedAt: 2000 }),
    addBookmarkOp({ url: 'u', title: 'B', addedAt: 1000 })
  ])
  assert.equal(v.bookmarks[0].title, 'B')   // order wins, not the bigger addedAt
  assert.equal(v.bookmarks[0].addedAt, 1000)
})

test('malicious / malformed inputs are rejected or retained-but-ignored', () => {
  const rawHuge = { v: 1, type: 'bookmark.add', url: 'u', bookmark: { url: 'u', blob: 'z'.repeat(MAX_OP_BYTES) } }
  assert.equal(validateOp(rawHuge).ok, false)
  assert.equal(validateOp(rawHuge).retain, false)

  const polluted = JSON.parse('{"v":1,"type":"bookmark.add","url":"u","bookmark":{"url":"u","__proto__":{"x":1}}}')
  assert.equal(validateOp(polluted).ok, false)

  assert.equal(validateOp({ v: 1, type: 'bookmark.add', url: '', bookmark: { url: '' } }).ok, false)

  const future = validateOp({ v: 999, type: 'bookmark.add', url: 'u', bookmark: { url: 'u' } })
  assert.equal(future.ok, false)
  assert.equal(future.retain, true)

  // unknown type retained but no effect on the view
  const v = applyView([addBookmarkOp(bm('u')), { v: 1, type: 'bookmark.frobnicate', url: 'u' }])
  assert.equal(v.bookmarks.length, 1)

  // sanitizeBookmark drops unknown keys + clamps
  const clean = sanitizeBookmark({ url: 'u', title: 't', evil: 'x', addedAt: 'nope' })
  assert.equal(clean.evil, undefined)
  assert.equal(clean.addedAt, undefined)   // non-finite dropped
})