454 lines
13 KiB
JavaScript
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)
|
|
})
|