OverviewFilesCommitsIssuesDocsReleasesPeers

keet-cli / test-discovery.js

369 lines · javascript ref:
/**
 * Test runner for discovery integration tests.
 * Run from keet-cli dir:
 *   node test-discovery.js
 *   bare test-discovery.js
 */

// Use relative path to keet-daemon package (not in npm, copied during CI)
const DAEMON_PKG = '../packages/keet-daemon'

const { RoomDiscovery } = await import(DAEMON_PKG + '/lib/room-discovery.js')
const { setDebug, dbg } = await import(DAEMON_PKG + '/lib/debug.js')

import fs from 'fs'
import path from 'path'
import os from 'os'

// Enable debug for all tests
setDebug(true)

let passed = 0
let failed = 0
const tmpDirs = []

function tmpDir (label) {
  const dir = path.join(os.tmpdir(), 'keet-test-' + label + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8))
  fs.mkdirSync(dir, { recursive: true })
  tmpDirs.push(dir)
  return dir
}

function rmrf (dir) {
  try {
    fs.rmSync(dir, { recursive: true, force: true })
  } catch {}
}

async function test (name, fn) {
  try {
    await fn()
    passed++
    console.log('  PASS:', name)
  } catch (err) {
    failed++
    console.log('  FAIL:', name)
    console.log('       ', err.message)
    if (err.stack) console.log('       ', err.stack.split('\n').slice(1, 3).join('\n        '))
  }
}

function assert (cond, msg) {
  if (!cond) throw new Error(msg || 'Assertion failed')
}

function assertEqual (a, b, msg) {
  if (a !== b) throw new Error((msg || 'assertEqual') + ': ' + JSON.stringify(a) + ' !== ' + JSON.stringify(b))
}

// ============================================================
// T1: Single Autobase (RoomDiscovery)
// ============================================================

console.log('\n--- T1: Single RoomDiscovery ---')

await test('T1.1 Create registry and verify admin role', async () => {
  const dir = tmpDir('t1-1')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  assert(disc._base.writable, 'should be writable (indexer)')
  assert(disc.localWriterKey, 'should have localWriterKey')

  const roleEntry = await disc.getRole(disc.localWriterKey)
  assert(roleEntry, 'should have role entry')
  assertEqual(roleEntry.role, 'admin', 'creator should be admin')

  await disc.close()
})

await test('T1.2 Register room and verify in view', async () => {
  const dir = tmpDir('t1-2')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  const roomId = 'test-room-' + Date.now()
  await disc.register(roomId, 'Test Room', 'A test', ['chat'])

  const entry = await disc.view.get('roomid:' + roomId)
  assert(entry, 'roomid entry should exist')

  const room = await disc.view.get('room:' + entry.value.titleLower)
  assert(room, 'room entry should exist')
  assertEqual(room.value.title, 'Test Room')
  assertEqual(room.value.description, 'A test')

  await disc.close()
})

await test('T1.3 Register duplicate title throws', async () => {
  const dir = tmpDir('t1-3')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  await disc.register('room-a', 'UniqueTitle', '', [])

  let threw = false
  try {
    await disc.register('room-b', 'UniqueTitle', '', [])
  } catch (err) {
    threw = true
    assert(err.message.includes('taken'), 'should say title is taken: ' + err.message)
  }
  assert(threw, 'should throw for duplicate title')

  await disc.close()
})

await test('T1.4 Register duplicate roomId throws', async () => {
  const dir = tmpDir('t1-4')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  await disc.register('same-room-id', 'Title A', '', [])

  let threw = false
  try {
    await disc.register('same-room-id', 'Title B', '', [])
  } catch (err) {
    threw = true
    assert(err.message.includes('already registered'), 'should say already registered: ' + err.message)
  }
  assert(threw, 'should throw for duplicate roomId')

  await disc.close()
})

await test('T1.5 Unregister room removes from view', async () => {
  const dir = tmpDir('t1-5')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  const roomId = 'del-room-' + Date.now()
  await disc.register(roomId, 'Deletable', '', [])

  const before = await disc.view.get('roomid:' + roomId)
  assert(before, 'room should exist before unregister')

  await disc.unregister(roomId)

  const after = await disc.view.get('roomid:' + roomId)
  assert(!after, 'room should be gone after unregister')

  await disc.close()
})

await test('T1.6 Ban room prevents re-registration', async () => {
  const dir = tmpDir('t1-6')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  const roomId = 'ban-room-' + Date.now()
  await disc.register(roomId, 'BanMe', '', [])
  await disc.banRoom(roomId, 'spam')

  const banned = await disc.isBanned(roomId)
  assert(banned, 'room should be banned')

  let threw = false
  try {
    await disc.register(roomId, 'BanMe Again', '', [])
  } catch (err) {
    threw = true
    assert(err.message.includes('banned'), 'should say room is banned: ' + err.message)
  }
  assert(threw, 'should throw when registering banned room')

  await disc.close()
})

await test('T1.7 List rooms returns registered rooms', async () => {
  const dir = tmpDir('t1-7')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  await disc.register('list-1', 'Alpha Room', '', ['tag1'])
  await disc.register('list-2', 'Beta Room', '', ['tag2'])

  const rooms = await disc.list()
  assert(rooms.length >= 2, 'should have at least 2 rooms, got ' + rooms.length)

  const titles = rooms.map(r => r.title)
  assert(titles.includes('Alpha Room'), 'should include Alpha Room')
  assert(titles.includes('Beta Room'), 'should include Beta Room')

  await disc.close()
})

// ============================================================
// T2: Multi-Registry (Create, Close-Rename-Reopen pattern)
// ============================================================

console.log('\n--- T2: Multi-Registry ---')

await test('T2.1 New registry returns key and writable instance', async () => {
  const dir = tmpDir('t2-1')
  const disc = new RoomDiscovery({ storagePath: dir, bootstrapKey: null })
  await disc.ready()

  assert(disc._base.key, 'should have a key')
  assert(disc._base.writable, 'should be writable')
  assert(disc.localWriterKey, 'should have localWriterKey')

  await disc.close()
})

await test('T2.2 CRITICAL: Close-rename-reopen preserves writer key', async () => {
  const tempDir = tmpDir('t2-2-temp')
  const disc1 = new RoomDiscovery({ storagePath: tempDir, bootstrapKey: null })
  await disc1.ready()

  const key = disc1._base.key.toString('hex')
  const writerKey1 = disc1.localWriterKey
  const writable1 = disc1._base.writable

  console.log('    Phase 1: key=' + key.slice(0, 12) + ' writerKey=' + writerKey1.slice(0, 12) + ' writable=' + writable1)

  await disc1.close()

  const renamedDir = tmpDir('t2-2-renamed')
  rmrf(renamedDir)
  fs.renameSync(tempDir, renamedDir)

  const disc2 = new RoomDiscovery({ storagePath: renamedDir, bootstrapKey: key })
  await disc2.ready()

  const writerKey2 = disc2.localWriterKey
  const writable2 = disc2._base.writable

  console.log('    Phase 2: writerKey=' + writerKey2.slice(0, 12) + ' writable=' + writable2)

  assertEqual(writerKey1, writerKey2, 'Writer key MUST survive close-rename-reopen')
  assertEqual(writable2, true, 'Must still be writable (indexer)')

  await disc2.close()
})

await test('T2.3 CRITICAL: Close-rename-reopen preserves admin role', async () => {
  const tempDir = tmpDir('t2-3-temp')
  const disc1 = new RoomDiscovery({ storagePath: tempDir, bootstrapKey: null })
  await disc1.ready()

  const key = disc1._base.key.toString('hex')
  const writerKey1 = disc1.localWriterKey

  const roleEntry1 = await disc1.getRole(writerKey1)
  assert(roleEntry1, 'should have role entry in phase 1')
  assertEqual(roleEntry1.role, 'admin', 'Should be admin in phase 1')

  await disc1.close()

  const renamedDir = tmpDir('t2-3-renamed')
  rmrf(renamedDir)
  fs.renameSync(tempDir, renamedDir)

  const disc2 = new RoomDiscovery({ storagePath: renamedDir, bootstrapKey: key })
  await disc2.ready()

  const writerKey2 = disc2.localWriterKey
  const roleEntry2 = await disc2.getRole(writerKey2)
  console.log('    Phase 2: writerKey=' + writerKey2.slice(0, 12) + ' role=' + (roleEntry2 ? roleEntry2.role : '(null)'))

  assert(roleEntry2, 'should have role entry in phase 2')
  assertEqual(roleEntry2.role, 'admin', 'Admin role MUST survive close-rename-reopen')

  await disc2.close()
})

await test('T2.4 CRITICAL: Register room works after close-rename-reopen', async () => {
  const tempDir = tmpDir('t2-4-temp')
  const disc1 = new RoomDiscovery({ storagePath: tempDir, bootstrapKey: null })
  await disc1.ready()

  const key = disc1._base.key.toString('hex')
  await disc1.close()

  const renamedDir = tmpDir('t2-4-renamed')
  rmrf(renamedDir)
  fs.renameSync(tempDir, renamedDir)

  const disc2 = new RoomDiscovery({ storagePath: renamedDir, bootstrapKey: key })
  await disc2.ready()

  const roomId = 'post-reopen-' + Date.now()
  await disc2.register(roomId, 'Post Reopen Room', '', [])

  const entry = await disc2.view.get('roomid:' + roomId)
  assert(entry, 'Room should be registered after close-rename-reopen')

  const rooms = await disc2.list()
  const titles = rooms.map(r => r.title)
  assert(titles.includes('Post Reopen Room'), 'Room should appear in list')

  await disc2.close()
})

await test('T2.5 Two registries are isolated', async () => {
  const dir1 = tmpDir('t2-5a')
  const dir2 = tmpDir('t2-5b')

  const disc1 = new RoomDiscovery({ storagePath: dir1, bootstrapKey: null })
  const disc2 = new RoomDiscovery({ storagePath: dir2, bootstrapKey: null })
  await disc1.ready()
  await disc2.ready()

  await disc1.register('room-in-1', 'Only In Registry 1', '', [])
  await disc2.register('room-in-2', 'Only In Registry 2', '', [])

  const list1 = await disc1.list()
  const list2 = await disc2.list()

  assert(list1.some(r => r.title === 'Only In Registry 1'), 'Registry 1 should have its room')
  assert(!list1.some(r => r.title === 'Only In Registry 2'), 'Registry 1 should NOT have registry 2\'s room')
  assert(list2.some(r => r.title === 'Only In Registry 2'), 'Registry 2 should have its room')
  assert(!list2.some(r => r.title === 'Only In Registry 1'), 'Registry 2 should NOT have registry 1\'s room')

  await disc1.close()
  await disc2.close()
})

await test('T2.6 SetMeta + GetMeta work after close-rename-reopen', async () => {
  const tempDir = tmpDir('t2-6-temp')
  const disc1 = new RoomDiscovery({ storagePath: tempDir, bootstrapKey: null })
  await disc1.ready()

  const key = disc1._base.key.toString('hex')
  await disc1.setMeta('name', 'Testy Group')
  await disc1.close()

  const renamedDir = tmpDir('t2-6-renamed')
  rmrf(renamedDir)
  fs.renameSync(tempDir, renamedDir)

  const disc2 = new RoomDiscovery({ storagePath: renamedDir, bootstrapKey: key })
  await disc2.ready()

  const meta = await disc2.getMeta('name')
  assertEqual(meta, 'Testy Group', 'Metadata should survive close-rename-reopen')

  await disc2.close()
})

// ============================================================
// Summary
// ============================================================

console.log('\n--- Results ---')
console.log(`  ${passed} passed, ${failed} failed`)

// Cleanup
for (const dir of tmpDirs) rmrf(dir)

if (failed > 0) {
  console.log('\nFAILED')
  if (typeof process !== 'undefined') process.exitCode = 1
  if (typeof Bare !== 'undefined') Bare.exit(1)
} else {
  console.log('\nALL PASS')
}