OverviewFilesCommitsIssuesDocsReleasesPeers

p2pbuilders / test / m1.js

166 lines · javascript ref:
'use strict'

// M1 integration test — runs with plain `node test/m1.js`.
// No networking, no Pear/Bare runtime. Just identity + ops + PoW + local core.

const assert = require('assert/strict')
const b4a = require('b4a')
const { Node } = require('../src/backend/node')
const { TYPE, parseOpId, encodeOp, decodeOp, makeOpId } = require('../src/backend/ops')
const pow = require('../src/backend/pow')

const tests = []
function test (name, fn) { tests.push({ name, fn }) }

test('identity is stable: same primary key → same pubkey', async () => {
  const a = await Node.openTemp()
  const pk = a.primaryKey
  const pubkey1 = b4a.from(a.pubkey)
  await a.close()

  const b = await Node.openTemp({ primaryKey: pk })
  const pubkey2 = b4a.from(b.pubkey)
  await b.close()

  assert.equal(b4a.toString(pubkey1, 'hex'), b4a.toString(pubkey2, 'hex'))
})

test('post appends, mints PoW, round-trips through encode/decode', async () => {
  const node = await Node.openTemp()
  const t0 = Date.now()
  const { op, opId, seq } = await node.post('general', 'hello p2pbuilders', 'first post ever')
  const t1 = Date.now()

  assert.equal(op.type, TYPE.POST)
  assert.equal(op.payload.board, 'general')
  assert.equal(op.payload.title, 'hello p2pbuilders')
  assert.equal(op.payload.body, 'first post ever')
  assert.equal(op.payload.link, null)
  assert.ok(op.pow, 'post must carry PoW')
  assert.ok(op.pow.bits >= pow.DEFAULT_BITS.post)
  assert.equal(op.pow.nonce.length, 8)

  assert.equal(seq, 0)
  const parsed = parseOpId(opId)
  assert.equal(b4a.toString(parsed.pubkey, 'hex'), b4a.toString(node.pubkey, 'hex'))
  assert.equal(parsed.seq, 0)

  // round-trip via core read
  const readBack = await node.getOp(0)
  assert.equal(readBack.payload.title, 'hello p2pbuilders')
  assert.ok(b4a.equals(readBack.pow.nonce, op.pow.nonce))

  console.log(`    post minted in ${t1 - t0}ms at ${op.pow.bits} bits`)
  await node.close()
})

test('comment threads by parent opId', async () => {
  const node = await Node.openTemp()
  const postRes = await node.post('general', 't', 'b')
  const commentRes = await node.comment(postRes.opId, 'nice post')

  const c = await node.getOp(commentRes.seq)
  assert.equal(c.type, TYPE.COMMENT)
  assert.ok(b4a.equals(c.payload.parent, postRes.opId))
  assert.equal(c.payload.body, 'nice post')
  assert.ok(c.pow, 'comment must carry PoW')
  await node.close()
})

test('vote has no PoW, dir constrained to -1/0/+1', async () => {
  const node = await Node.openTemp()
  const { opId } = await node.post('general', 't', 'b')

  const up = await node.vote(opId, 1)
  const upOp = await node.getOp(up.seq)
  assert.equal(upOp.type, TYPE.VOTE)
  assert.equal(upOp.payload.dir, 1)
  assert.equal(upOp.pow, null, 'vote must not carry PoW')

  await assert.rejects(() => node.vote(opId, 2), /dir must be/)
  await node.close()
})

test('PoW verify accepts valid, rejects tampered op', async () => {
  const node = await Node.openTemp()
  const { op } = await node.post('general', 'x', 'y')

  assert.equal(pow.verify(op, op.pow.bits), true, 'fresh PoW verifies')

  // tamper body — hash changes, PoW fails
  const tampered = { ...op, payload: { ...op.payload, body: 'different' } }
  assert.equal(pow.verify(tampered, op.pow.bits), false, 'tampered op fails verify')

  // tamper nonce
  const badNonce = b4a.from(op.pow.nonce)
  badNonce[0] ^= 0xff
  const tampered2 = { ...op, pow: { ...op.pow, nonce: badNonce } }
  assert.equal(pow.verify(tampered2, op.pow.bits), false, 'bad nonce fails verify')
  await node.close()
})

test('encodeOp / decodeOp round-trip exact', async () => {
  const node = await Node.openTemp()
  const { op } = await node.post('general', 'title', 'body', { link: 'https://example.com' })
  const buf = encodeOp(op)
  const back = decodeOp(buf)
  assert.equal(back.v, op.v)
  assert.equal(back.type, op.type)
  assert.equal(back.ts, op.ts)
  assert.deepEqual(back.payload, op.payload)
  assert.ok(b4a.equals(back.pow.nonce, op.pow.nonce))
  assert.equal(back.pow.bits, op.pow.bits)
  await node.close()
})

test('opId parse <-> makeOpId is consistent', () => {
  const pk = b4a.alloc(32)
  for (let i = 0; i < 32; i++) pk[i] = i
  const id = makeOpId(pk, 42)
  const parsed = parseOpId(id)
  assert.equal(b4a.toString(parsed.pubkey, 'hex'), b4a.toString(pk, 'hex'))
  assert.equal(parsed.seq, 42)
})

test('leadingZeroBits counts correctly', () => {
  assert.equal(pow.leadingZeroBits(b4a.from([0xff])), 0)
  assert.equal(pow.leadingZeroBits(b4a.from([0x7f])), 1)
  assert.equal(pow.leadingZeroBits(b4a.from([0x01])), 7)
  assert.equal(pow.leadingZeroBits(b4a.from([0x00, 0xff])), 8)
  assert.equal(pow.leadingZeroBits(b4a.from([0x00, 0x00, 0x40])), 17)
  assert.equal(pow.leadingZeroBits(b4a.from([0x00, 0x00, 0x00])), 24)
})

test('iterate ops', async () => {
  const node = await Node.openTemp()
  await node.post('general', 'a', '1')
  await node.post('general', 'b', '2')
  await node.post('general', 'c', '3')

  const collected = []
  for await (const entry of node.ops()) collected.push(entry)
  assert.equal(collected.length, 3)
  assert.equal(collected[0].op.payload.title, 'a')
  assert.equal(collected[2].op.payload.title, 'c')
  assert.equal(collected[2].seq, 2)
  await node.close()
})

// runner
;(async () => {
  let pass = 0; let fail = 0
  for (const { name, fn } of tests) {
    try {
      await fn()
      console.log(`  ✓ ${name}`)
      pass++
    } catch (err) {
      console.log(`  ✗ ${name}`)
      console.log(`    ${err.stack || err.message}`)
      fail++
    }
  }
  console.log(`\n${pass}/${pass + fail} passed`)
  process.exit(fail ? 1 : 0)
})()