import test from 'brittle' import { BARE_HOLESAIL_STATE_USER, bareHolesailManagedEnsureServerSeedPersisted, bareHolesailManagedEnvForConnectionId, bareHolesailManagedLogLevel, bareHolesailManagedHsUrlKeySuffix, bareHolesailManagedNewHolesailOpts, bareHolesailManagedNormalizeEntry, bareHolesailManagedPersistedUrlMatchesLive, bareHolesailManagedPersistentSeedLooksValid, bareHolesailManagedReadState, bareHolesailManagedSeedLooksValid, bareHolesailManagedStatePath, bareHolesailManagedValidateId } from './lib/bare-holesail-managed.js' import { holesailCliParseAdd } from './lib/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} */ 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} */ (env), id ) const { state } = await bareHolesailManagedReadState( ctx, /** @type {Record} */ (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} */ 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} */ (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) })