OverviewFilesCommitsIssuesDocsReleasesPeers

keet-daemon / test / state-base-v3-persist.test.js

79 lines · javascript ref:
// state-base-v3-persist.test.js — proves StateBaseV3 game-state survives a
// full close/reopen cycle (the "does it persist to disk across restart?" test).
// Standalone (no brittle): run with `bare test/state-base-v3-persist.test.js`.
// network:false → no swarm, pure local disk round-trip.
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import crypto from 'hypercore-crypto'
import { StateBaseV3 as StateBase } from '../lib/state-base-v3.js'

let failures = 0
function ok (cond, msg) { console.log((cond ? 'ok   ' : 'FAIL ') + msg); if (!cond) failures++ }

const testDir = path.join(os.tmpdir(), 'sbv3-persist-' + crypto.randomBytes(4).toString('hex'))
const roomKey = crypto.randomBytes(32)            // the per-room dir key (stable)
const roomSeed = crypto.randomBytes(32)           // members-only secret (stable across restarts)
const encryptionKey = roomSeed
const selfKeyPair = crypto.keyPair()              // our keet identity (admin)
const selfKey = selfKeyPair.publicKey
// admin (role 1) → writable creator; deterministic single-member election picks us.
const getMembers = async () => [{ key: selfKey.toString('hex'), role: 1 }]

function mk () {
  return new StateBase(testDir, roomKey, {
    network: false,
    roomSeed,
    encryptionKey,
    selfKey,
    selfKeyPair,
    getMembers
  })
}

async function main () {
  // ---- session 1: write ----
  let sb = mk()
  await sb.ready()
  ok(sb.writable, 'session1: writable=true (admin creator)')

  await sb.set('world:players:me', { pos: [1, 2, 3], ts: 100 })
  await sb.set('world:terrain:edit:me_0', { pos: [4, 5, 6], ext: [1, 1, 1], str: 0.5 })
  await sb.set('world:creatures:registry:crawler_0', { type: 'crawler', seed: 42, pos: [7, 8, 9] })

  const live = await sb.list('')
  ok(Object.keys(live).length >= 3, 'session1: 3 keys visible in-session (got ' + Object.keys(live).length + ')')
  ok(live['world:players:me'] && live['world:players:me'].pos[0] === 1, 'session1: player pos readable in-session')
  await sb.close()

  // ---- session 2: reopen SAME dir + seed (simulates restart) ----
  sb = mk()
  await sb.ready()
  ok(sb.writable, 'session2: writable=true after reopen')

  const player = await sb.get('world:players:me')
  ok(player !== null && player.pos && player.pos[1] === 2, 'PERSIST: player position survived restart')

  const edit = await sb.get('world:terrain:edit:me_0')
  ok(edit !== null && edit.str === 0.5, 'PERSIST: terrain edit survived restart')

  const creature = await sb.get('world:creatures:registry:crawler_0')
  ok(creature !== null && creature.seed === 42, 'PERSIST: creature registry survived restart')

  const all = await sb.list('')
  ok(Object.keys(all).filter(k => k !== '__members__').length === 3, 'PERSIST: exactly 3 game keys after restart (got ' + Object.keys(all).length + ' incl __members__)')

  // ---- a fresh write after reopen also persists + is readable ----
  await sb.set('world:players:me', { pos: [9, 9, 9] })
  const updated = await sb.get('world:players:me')
  ok(updated && updated.pos[0] === 9, 'session2: new write after reopen applied (LWW by set()-stamped ts)')

  await sb.close()
  try { fs.rmSync(testDir, { recursive: true, force: true }) } catch {}

  console.log(failures === 0 ? '\nPASS — game-state persists across restart' : '\nFAIL — ' + failures + ' assertion(s) failed')
  Bare.exit(failures === 0 ? 0 : 1)
}

main().catch(err => { console.error('THREW', err && err.stack || err); Bare.exit(1) })