/**
* 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')
}