Files
bare-operating-system/packages/bare-os-booter/test.bare-holesail-managed.js
T
2026-08-18 18:11:34 -04:00

454 lines
13 KiB
JavaScript

import test from 'brittle'
import {
BARE_HOLESAIL_STATE_USER,
bareHolesailManagedEnsureServerSeedPersisted,
bareHolesailManagedEnvForConnectionId,
bareHolesailManagedLogLevel,
bareHolesailManagedHsUrlKeySuffix,
bareHolesailManagedNewHolesailOpts,
bareHolesailManagedNormalizeEntry,
bareHolesailManagedPersistedUrlMatchesLive,
bareHolesailManagedPersistentSeedLooksValid,
bareHolesailManagedReadState,
bareHolesailManagedSeedLooksValid,
bareHolesailManagedStatePath,
bareHolesailManagedValidateId
} from './lib/p2p/bare-holesail-managed.js'
import {
holesailCliFormatListRow,
holesailCliParseAdd,
holesailCliParseEdit,
runHolesailCli
} from './lib/p2p/holesail-cli.js'
test('bareHolesailManagedStatePath defaults to ~/.holesail and honors override', (t) => {
t.is(
bareHolesailManagedStatePath({
USER: 'guest',
HOME: '/home/guest'
}),
BARE_HOLESAIL_STATE_USER
)
t.is(
bareHolesailManagedStatePath({
USER: 'alice',
HOME: '/home/alice'
}),
BARE_HOLESAIL_STATE_USER
)
t.is(
bareHolesailManagedStatePath({
USER: 'alice',
HOME: '/home/alice',
BARE_OS_HOLESAIL_STATE: '/tmp/custom.json'
}),
'/tmp/custom.json'
)
t.is(bareHolesailManagedStatePath({}), BARE_HOLESAIL_STATE_USER)
})
test('bareHolesailManagedEnvForConnectionId is identity on env', (t) => {
const env = { USER: 'u', HOME: '/home/u', BARE_OS_HOLESAIL_STATE: '/x.json' }
const g = bareHolesailManagedEnvForConnectionId(env, 'bare-www-8088')
t.is(g, env)
const u = bareHolesailManagedEnvForConnectionId(env, 'my-tunnel')
t.is(u, env)
})
test('bareHolesailManagedValidateId', (t) => {
t.ok(bareHolesailManagedValidateId('a').ok)
t.ok(bareHolesailManagedValidateId('t1._-x').ok)
t.absent(bareHolesailManagedValidateId('').ok)
t.absent(bareHolesailManagedValidateId('bad id').ok)
})
test('bareHolesailManagedNormalizeEntry server', (t) => {
const r = bareHolesailManagedNormalizeEntry({ server: true, port: '8080' })
t.ok(r.ok)
t.is(r.entry.server, true)
t.is(r.entry.client, false)
t.is(r.entry.port, 8080)
})
test('bareHolesailManagedNormalizeEntry client needs key', (t) => {
const r = bareHolesailManagedNormalizeEntry({ client: true })
t.absent(r.ok)
})
test('bareHolesailManagedNormalizeEntry server seed hex or z32 suffix', (t) => {
const bad = bareHolesailManagedNormalizeEntry({
server: true,
seed: 'tooshort'
})
t.absent(bad.ok)
const hex = bareHolesailManagedNormalizeEntry({
server: true,
seed: 'ab'.repeat(32)
})
t.ok(hex.ok)
t.is(/** @type {{ entry: { seed: string } }} */ (hex).entry.seed, 'ab'.repeat(32))
const z = bareHolesailManagedNormalizeEntry({
server: true,
seed: 'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
})
t.ok(z.ok)
t.is(
/** @type {{ entry: { seed: string } }} */ (z).entry.seed,
'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
)
})
test('bareHolesailManagedSeedLooksValid', (t) => {
t.ok(bareHolesailManagedSeedLooksValid('a'.repeat(64)))
t.absent(bareHolesailManagedSeedLooksValid('z'.repeat(64)))
})
test('bareHolesailManagedPersistentSeedLooksValid accepts hex or z32 suffix', (t) => {
t.ok(bareHolesailManagedPersistentSeedLooksValid('a'.repeat(64)))
t.ok(
bareHolesailManagedPersistentSeedLooksValid(
'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
)
)
t.absent(bareHolesailManagedPersistentSeedLooksValid('short'))
})
test('bareHolesailManagedHsUrlKeySuffix', (t) => {
const u = 'hs://0000xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
t.is(bareHolesailManagedHsUrlKeySuffix(u), 'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo')
t.is(bareHolesailManagedHsUrlKeySuffix('not-a-url'), '')
})
test('bareHolesailManagedNewHolesailOpts server prefers hex seed over hs key', (t) => {
const seed = 'a'.repeat(64)
const o = bareHolesailManagedNewHolesailOpts(
{
server: true,
client: false,
port: 8088,
host: '127.0.0.1',
seed,
key: 'hs://0000ignored000000000000000000000000000000000000000000'
},
{}
)
t.is(o.key, seed)
t.is(o.secure, false)
})
test('bareHolesailManagedNewHolesailOpts server prefers z32 seed over hs key', (t) => {
const z32 = 'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
const o = bareHolesailManagedNewHolesailOpts(
{
server: true,
client: false,
port: 8088,
host: '127.0.0.1',
seed: z32,
key: 'hs://0000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
},
{}
)
t.is(o.key, z32)
})
test('bareHolesailManagedNewHolesailOpts matches reference Node pattern', (t) => {
const pubKey = 'hs://0000aaaabbbbccccddddeeeeffffgggg'
const o = bareHolesailManagedNewHolesailOpts(
{
server: true,
client: false,
port: 8088,
host: '127.0.0.1',
key: pubKey,
enabled: true
},
{}
)
t.is(o.server, true)
t.is(o.client, false)
t.is(o.port, 8088)
t.is(o.host, '127.0.0.1')
t.is(o.key, pubKey)
t.is(o.secure, false)
t.is(o.udp, false)
t.absent(Object.prototype.hasOwnProperty.call(o, 'enabled'))
const sec = bareHolesailManagedNewHolesailOpts(
{
server: true,
client: false,
port: 1,
host: '127.0.0.1',
key: 'hs://s000477b2d983364922df1296a65aeaa2d2a'
},
{}
)
t.ok(sec.secure)
const udpOn = bareHolesailManagedNewHolesailOpts({ server: true, client: false, udp: true }, {})
t.ok(udpOn.udp)
})
test('bareHolesailManagedLogLevel env and conn', (t) => {
const conn = { log: 2 }
t.is(bareHolesailManagedLogLevel(conn, {}), 2)
t.is(bareHolesailManagedLogLevel({}, { BARE_OS_HOLESAIL_MANAGED_LOG: '0' }), 0)
t.is(bareHolesailManagedLogLevel({}, { BARE_OS_HOLESAIL_DEBUG: '1' }), 0)
})
test('bareHolesailManagedPersistedUrlMatchesLive', (t) => {
const u = 'hs://0000aaaabbbbccccddddeeeeffffgggg'
t.ok(bareHolesailManagedPersistedUrlMatchesLive(u, u))
t.ok(bareHolesailManagedPersistedUrlMatchesLive(` ${u} `, u.toUpperCase()))
t.absent(bareHolesailManagedPersistedUrlMatchesLive(u, 'hs://0000other'))
t.absent(bareHolesailManagedPersistedUrlMatchesLive(u, ''))
t.absent(bareHolesailManagedPersistedUrlMatchesLive('', u))
})
test('holesail list prefers live runtime URL over stale persisted key', (t) => {
const persistedUrl = 'hs://0000stale000000000000000000000000000000000000000000'
const runtimeUrl = 'hs://0000live00000000000000000000000000000000000000000000'
const live = true
const out = (live ? runtimeUrl.trim() : '') || persistedUrl
t.is(out, runtimeUrl)
})
test('holesailCliParseAdd', (t) => {
const r = holesailCliParseAdd(['t', '--server', '--port', '9000'])
t.ok(r.ok)
t.is(r.id, 't')
t.is(r.entry.server, true)
t.is(r.entry.port, 9000)
})
test('holesailCliParseAdd implicit hs key', (t) => {
const r = holesailCliParseAdd([
't',
'--client',
'hs://s000477b2d983364922df1296a65aeaa2d2a',
'--port',
'8888'
])
t.ok(r.ok)
t.is(r.entry.client, true)
t.is(r.entry.key, 'hs://s000477b2d983364922df1296a65aeaa2d2a')
t.is(r.entry.port, 8888)
})
test('bareHolesailManagedEnsureServerSeedPersisted migrates hs url suffix to seed', async (t) => {
const id = 'srv-hs-only'
const hsKey =
'hs://0000xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
const initial = {
version: 1,
connections: {
[id]: {
server: true,
port: 8088,
host: '127.0.0.1',
enabled: true,
key: hsKey
}
}
}
/** @type {Map<string, string>} */
const files = new Map([
[BARE_HOLESAIL_STATE_USER, JSON.stringify(initial, null, 2) + '\n']
])
const ctx = {
b4a: {
from: (s) => Buffer.from(String(s), 'utf8'),
toString: (buf, enc) => Buffer.from(buf).toString(enc || 'utf8')
},
vfs: {
readFile: async (p) => {
const x = files.get(p)
if (x === undefined) throw new Error('enoent')
return Buffer.from(x, 'utf8')
},
writeFile: async (p, buf) => {
files.set(p, Buffer.from(buf).toString('utf8'))
},
mkdir: async () => {}
}
}
const env = { HOME: '/home/guest' }
await bareHolesailManagedEnsureServerSeedPersisted(
ctx,
/** @type {Record<string, string | undefined>} */ (env),
id
)
const { state } = await bareHolesailManagedReadState(
ctx,
/** @type {Record<string, string | undefined>} */ (env)
)
const row = /** @type {{ seed?: string, key?: string }} */ (state.connections[id])
t.ok(row.seed && bareHolesailManagedPersistentSeedLooksValid(row.seed))
t.is(
row.seed,
'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
)
t.is(row.key, hsKey)
})
test('bareHolesailManagedEnsureServerSeedPersisted keeps z32 seed (no new hex mint)', async (t) => {
const id = 'z32-only'
const z = 'xkzftmerxjhuxdztn1zxoxb98xop65rthdf9s96yxxu6zyt55odo'
const initial = {
version: 1,
connections: {
[id]: {
server: true,
port: 9000,
host: '127.0.0.1',
enabled: true,
seed: z
}
}
}
/** @type {Map<string, string>} */
const files = new Map([
[BARE_HOLESAIL_STATE_USER, JSON.stringify(initial, null, 2) + '\n']
])
const ctx = {
b4a: {
from: (s) => Buffer.from(String(s), 'utf8'),
toString: (buf, enc) => Buffer.from(buf).toString(enc || 'utf8')
},
vfs: {
readFile: async (p) => {
const x = files.get(p)
if (x === undefined) throw new Error('enoent')
return Buffer.from(x, 'utf8')
},
writeFile: async (p, buf) => {
files.set(p, Buffer.from(buf).toString('utf8'))
},
mkdir: async () => {}
}
}
const env = { HOME: '/home/guest' }
await bareHolesailManagedEnsureServerSeedPersisted(
ctx,
/** @type {Record<string, string | undefined>} */ (env),
id
)
const text = files.get(BARE_HOLESAIL_STATE_USER)
const parsed = JSON.parse(String(text || '{}'))
const row = /** @type {{ seed?: string }} */ (parsed.connections[id])
t.is(row.seed, z)
})
test('holesailCliParseEdit patches fields and rejects mode change', (t) => {
const r = holesailCliParseEdit(['web', '--port', '9090', '--host', '127.0.0.1', '--no-udp'])
t.ok(r.ok)
t.is(r.id, 'web')
t.is(r.patch.port, '9090')
t.is(r.patch.host, '127.0.0.1')
t.is(r.patch.udp, false)
t.absent(holesailCliParseEdit(['web']).ok)
t.absent(holesailCliParseEdit(['web', '--server']).ok)
t.absent(holesailCliParseEdit([]).ok)
const key = holesailCliParseEdit(['c', 'hs://s000477b2d983364922df1296a65aeaa2d2a'])
t.ok(key.ok)
t.is(key.patch.key, 'hs://s000477b2d983364922df1296a65aeaa2d2a')
const clear = holesailCliParseEdit(['web', '--clear-host'])
t.ok(clear.ok)
t.is(clear.patch.host, '')
})
test('holesailCliFormatListRow includes port/host and prefers live URL', (t) => {
const row = holesailCliFormatListRow(
'www',
{
server: true,
enabled: true,
port: 8088,
host: '127.0.0.1',
key: 'hs://stale',
udp: false,
secure: false
},
true,
'hs://live'
)
t.ok(row.indexOf('www') === 0)
t.ok(row.indexOf('server') >= 0)
t.ok(row.indexOf('port=8088') >= 0)
t.ok(row.indexOf('host=127.0.0.1') >= 0)
t.ok(row.indexOf('url=hs://live') >= 0)
t.absent(row.indexOf('hs://stale') >= 0)
})
test('runHolesailCli status show and edit', async (t) => {
const seed = 'aa'.repeat(32)
const initial = {
version: 1,
connections: {
web: {
server: true,
port: 8088,
host: '127.0.0.1',
enabled: true,
seed,
key: 'hs://shareme'
}
}
}
/** @type {Map<string, string>} */
const files = new Map([
[BARE_HOLESAIL_STATE_USER, JSON.stringify(initial, null, 2) + '\n']
])
const lines = []
const errs = []
const ctx = {
env: { HOME: '/home/guest' },
exitCode: 0,
b4a: {
from: (s) => Buffer.from(String(s), 'utf8'),
toString: (buf, enc) => Buffer.from(buf).toString(enc || 'utf8')
},
console: {
log: (...a) => {
lines.push(a.join(' '))
},
error: (...a) => {
errs.push(a.join(' '))
}
},
vfs: {
readFile: async (p) => {
const x = files.get(p)
if (x === undefined) throw new Error('enoent')
return Buffer.from(x, 'utf8')
},
writeFile: async (p, buf) => {
files.set(p, Buffer.from(buf).toString('utf8'))
},
mkdir: async () => {}
}
}
await runHolesailCli(ctx, ['holesail', 'status'])
t.is(ctx.exitCode, 0)
t.ok(lines.some((l) => /connections:\s*1/.test(l)))
t.ok(lines.some((l) => /server=1/.test(l)))
lines.length = 0
await runHolesailCli(ctx, ['holesail', 'show', 'web'])
t.is(ctx.exitCode, 0)
const shown = lines.join('\n')
t.ok(/mode\tserver/.test(shown))
t.ok(/url\ths:\/\/shareme/.test(shown))
t.ok(/seed\tyes/.test(shown))
t.absent(new RegExp(seed).test(shown))
lines.length = 0
await runHolesailCli(ctx, ['holesail', 'edit', 'web', '--port', '9090'])
t.is(ctx.exitCode, 0, errs.join('\n'))
const next = JSON.parse(String(files.get(BARE_HOLESAIL_STATE_USER) || '{}'))
t.is(next.connections.web.port, 9090)
t.is(next.connections.web.seed, seed)
t.is(next.connections.web.server, true)
})