// Standalone identity-bootstrap probe — plain Bare script, no brittle.
// Spins up keet-daemon against /tmp/keet-daemon-test-<rand>, drives the
// vault.setup RPC sequence, asserts the identity got materialized.
//
// Touches NO user storage. Cleanup unconditional.
//
// Run:
// cd hyper-fleeky/packages/keet-daemon && bare test/identity-bootstrap.test.js
// (or copy into any pgh-installed bare-runtime tree alongside its node_modules)
//
// Exit code 0 = success, 1 = any failure (with diagnostics printed).
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import Pipe from 'bare-pipe'
import FramedStream from 'framed-stream'
import { startDaemon } from '../index.js'
let exitCode = 0
const failures = []
function ok (cond, msg) {
if (cond) console.log(' ok ' + msg)
else { console.log(' FAIL ' + msg); exitCode = 1; failures.push(msg) }
}
function rmrf (dir) {
try { fs.rmSync(dir, { recursive: true, force: true }) } catch {}
}
function mktemp (prefix) {
const dir = path.join(os.tmpdir(), 'keet-daemon-test-' + prefix + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8))
fs.mkdirSync(dir, { recursive: true })
return dir
}
class TestClient {
constructor (socketPath) {
this._socketPath = socketPath
this._pipe = null
this._framed = null
this._nextId = 1
this._pending = new Map()
}
connect () {
return new Promise((resolve, reject) => {
const pipe = new Pipe(this._socketPath)
const onErr = (err) => reject(err)
pipe.once('error', onErr)
pipe.once('connect', () => {
pipe.removeListener('error', onErr)
this._pipe = pipe
const FS = FramedStream.default || FramedStream
this._framed = new FS(pipe)
this._framed.on('data', (buf) => {
let msg
try { msg = JSON.parse(buf.toString('utf-8')) } catch { return }
const p = this._pending.get(msg.id)
if (!p) return
this._pending.delete(msg.id)
if (msg.ok === false || msg.error) p.reject(new Error(msg.error || 'RPC failed'))
else p.resolve(msg.data !== undefined ? msg.data : msg)
})
resolve()
})
})
}
call (method, params = {}, timeoutMs = 60000) {
const id = this._nextId++
const payload = JSON.stringify({ id, method, params })
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this._pending.delete(id)
reject(new Error('RPC timeout: ' + method + ' (' + timeoutMs + 'ms)'))
}, timeoutMs)
this._pending.set(id, {
resolve: (v) => { clearTimeout(timer); resolve(v) },
reject: (e) => { clearTimeout(timer); reject(e) }
})
this._framed.write(Buffer.from(payload, 'utf-8'))
})
}
close () {
try { this._framed && this._framed.destroy() } catch {}
try { this._pipe && this._pipe.destroy() } catch {}
}
}
async function main () {
const baseDir = mktemp('bootstrap')
const socketPath = path.join(baseDir, 'daemon.sock')
console.log('[test] baseDir = ' + baseDir)
console.log('[test] socket = ' + socketPath)
let daemon
try {
daemon = await startDaemon({ storage: baseDir, socketPath })
ok(true, 'startDaemon resolved')
} catch (err) {
ok(false, 'startDaemon threw: ' + err.message)
rmrf(baseDir)
return
}
const client = new TestClient(socketPath)
try {
await client.connect()
ok(true, 'UDS client connected')
} catch (err) {
ok(false, 'UDS connect failed: ' + err.message)
try { await daemon.core.close() } catch {}
rmrf(baseDir)
return
}
// 1. List identities — should show identity-0 (created by migration)
try {
const list = await client.call('identity-manager.list', {}, 10000)
ok(list && Array.isArray(list.identities), 'list.identities is an array')
ok(list.activeId === 'identity-0', 'activeId === identity-0; got ' + list.activeId)
ok(list.identities.length === 1, 'one identity present; got ' + list.identities.length)
} catch (err) {
ok(false, 'identity-manager.list threw: ' + err.message)
}
// 2. vault.setup — the actual bootstrap step
const password = 'test-pw-' + Math.random().toString(36).slice(2, 10)
console.log('[test] calling vault.setup with password=' + password)
const t0 = Date.now()
try {
const setupResult = await client.call('vault.setup', { password }, 90000)
const elapsed = Date.now() - t0
ok(true, 'vault.setup OK in ' + elapsed + 'ms')
ok(setupResult && setupResult.ok === true, 'vault.setup returned {ok:true}; got ' + JSON.stringify(setupResult))
} catch (err) {
const elapsed = Date.now() - t0
ok(false, 'vault.setup failed in ' + elapsed + 'ms: ' + err.message)
}
// 3. identity RPC should now return a publicKey
try {
const id = await client.call('identity', {}, 10000)
ok(id && id.publicKey, 'identity.publicKey present after setup: ' + (id && id.publicKey ? id.publicKey.slice(0, 16) + '…' : '<missing>'))
} catch (err) {
ok(false, 'identity RPC failed: ' + err.message)
}
// 4. Disk evidence: identity-0/0/db should be populated
const idDir = path.join(baseDir, 'identity-0')
ok(fs.existsSync(idDir), 'identity-0 dir exists on disk')
const dbDir = path.join(idDir, '0', 'db')
if (fs.existsSync(dbDir)) {
const entries = fs.readdirSync(dbDir)
ok(entries.length > 0, 'identity-0/0/db has ' + entries.length + ' files')
} else {
ok(false, 'identity-0/0/db does not exist')
}
// Cleanup
console.log('[test] cleaning up…')
client.close()
try { await daemon.core.close() } catch {}
try { await daemon.uds.stop() } catch {}
rmrf(baseDir)
console.log('')
console.log('=== RESULT ===')
if (exitCode === 0) console.log('ALL PASSED')
else {
console.log(failures.length + ' FAILED:')
for (const f of failures) console.log(' - ' + f)
}
Bare.exit(exitCode)
}
main().catch(err => {
console.error('uncaught:', err)
Bare.exit(1)
})