Idempotent tunnel create: reuse existing, block UI double-submit
CI / test (push) Successful in 10m5s
CI / test (push) Successful in 10m5s
Return the active tunnel for the same host:port:protocol instead of starting a second Holesail instance; coalesce concurrent creates. UI disables the create button and shows “already exists” feedback.
This commit is contained in:
@@ -113,3 +113,59 @@ test('createTunnel end-to-end when holesail enabled (default)', async (t) => {
|
||||
t.ok(await closeTunnel(tunnel.id))
|
||||
t.is(list().length, 0)
|
||||
})
|
||||
|
||||
test('createTunnel is idempotent for same host:port (no duplicate)', async (t) => {
|
||||
const prev = process.env.ENABLE_HOLESAIL
|
||||
const prevPath = process.env.PEARDOCK_TUNNELS_PATH
|
||||
const fs = await import('fs')
|
||||
const os = await import('os')
|
||||
const path = await import('path')
|
||||
const tmpFile = path.join(os.tmpdir(), `peardock-tunnels-dup-${Date.now()}.json`)
|
||||
process.env.ENABLE_HOLESAIL = '1'
|
||||
process.env.PEARDOCK_TUNNELS_PATH = tmpFile
|
||||
t.teardown(async () => {
|
||||
if (prev === undefined) delete process.env.ENABLE_HOLESAIL
|
||||
else process.env.ENABLE_HOLESAIL = prev
|
||||
if (prevPath === undefined) delete process.env.PEARDOCK_TUNNELS_PATH
|
||||
else process.env.PEARDOCK_TUNNELS_PATH = prevPath
|
||||
try {
|
||||
fs.unlinkSync(tmpFile)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const { closeAllTunnels } = await import('../server/services/holesail-tunnels.js')
|
||||
await closeAllTunnels()
|
||||
})
|
||||
|
||||
const net = await import('net')
|
||||
const local = net.createServer((c) => c.end('ok'))
|
||||
await new Promise((resolve) => local.listen(0, '127.0.0.1', resolve))
|
||||
const port = local.address().port
|
||||
t.teardown(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
local.close(() => resolve())
|
||||
})
|
||||
)
|
||||
|
||||
const { createTunnel, closeTunnel, listTunnels: list } = await import(
|
||||
'../server/services/holesail-tunnels.js'
|
||||
)
|
||||
const a = await createTunnel({ name: 'dup-a', host: '127.0.0.1', port, secure: true })
|
||||
t.absent(a.existing)
|
||||
const b = await createTunnel({ name: 'dup-b', host: '127.0.0.1', port, secure: true })
|
||||
t.ok(b.existing, 'second create reuses existing')
|
||||
t.is(b.id, a.id)
|
||||
t.is(b.url, a.url)
|
||||
t.is(list().length, 1, 'only one live tunnel')
|
||||
|
||||
// Concurrent creates coalesce
|
||||
const c1 = createTunnel({ name: 'race-1', host: 'localhost', port, secure: true })
|
||||
const c2 = createTunnel({ name: 'race-2', host: '127.0.0.1', port, secure: true })
|
||||
const [r1, r2] = await Promise.all([c1, c2])
|
||||
t.is(r1.id, a.id)
|
||||
t.is(r2.id, a.id)
|
||||
t.is(list().length, 1)
|
||||
|
||||
await closeTunnel(a.id)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user