DSH-Arch-Setup/plugins/dsh-touched-git/test/editor-runtime.test.js
Шурупов Илья Викторович cf9443753e Add one-command Arch DSH setup
Package the portable DSH profiles, local plugins, desktop integration, Firefox wrapper, and ownership-aware installer lifecycle. Bundle checksum-pinned OpenVSCode, editor extensions, translation assets, and cloudflared so unreliable upstream artifact downloads cannot break a clean installation.
2026-09-12 19:13:36 +03:00

178 lines
6.7 KiB
JavaScript

import assert from 'node:assert/strict'
import { EventEmitter } from 'node:events'
import test from 'node:test'
import { EditorWorkspaces, waitForEditorReady } from '../lib/editor-runtime.js'
function deferred() {
let resolve
let reject
const promise = new Promise((accept, fail) => { resolve = accept; reject = fail })
return { promise, resolve, reject }
}
function fakeRuntime() {
const closure = deferred()
return {
url: 'http://127.0.0.1:40000',
token: 'test-token',
closed: closure.promise,
stop: async () => { closure.resolve() },
finish: closure.resolve,
}
}
test('constructing and disposing an unused launcher never starts a runtime', async () => {
let starts = 0
const editor = new EditorWorkspaces(async () => { starts++; return fakeRuntime() })
assert.equal(starts, 0)
await editor.dispose()
assert.equal(starts, 0)
await assert.rejects(editor.open('/repo'), /stopped/)
})
test('concurrent workspace opens share one startup and preserve exact folder paths', async () => {
let starts = 0
const startup = deferred()
const editor = new EditorWorkspaces(() => { starts++; return startup.promise })
const first = editor.open('/repo with spaces/#feature?&')
const second = editor.open('/another/repo')
assert.equal(starts, 1)
startup.resolve(fakeRuntime())
const results = await Promise.all([first, second])
assert.equal(new URL(results[0].url).searchParams.get('folder'), '/repo with spaces/#feature?&')
assert.equal(new URL(results[1].url).searchParams.get('folder'), '/another/repo')
assert.equal(new URL(results[0].url).searchParams.get('tkn'), 'test-token')
await editor.open('/third')
assert.equal(starts, 1)
await editor.dispose()
})
test('failed startup does not retry until another explicit open', async () => {
let starts = 0
const editor = new EditorWorkspaces(async () => {
starts++
if (starts === 1) throw new Error('startup failed')
return fakeRuntime()
})
await assert.rejects(editor.open('/repo'), /startup failed/)
assert.equal(starts, 1)
await editor.open('/repo')
assert.equal(starts, 2)
await editor.dispose()
})
test('server exit clears its cached handle but does not restart automatically', async () => {
let starts = 0
const runtimes = []
const editor = new EditorWorkspaces(async () => {
starts++
const runtime = fakeRuntime()
runtimes.push(runtime)
return runtime
})
await editor.open('/repo')
runtimes[0].finish()
await runtimes[0].closed
assert.equal(starts, 1)
await editor.open('/repo')
assert.equal(starts, 2)
await editor.dispose()
})
test('disposal during startup shuts down without returning an editor URL', async () => {
const startup = deferred()
const runtime = fakeRuntime()
const editor = new EditorWorkspaces(() => startup.promise)
const opening = editor.open('/repo')
const disposing = editor.dispose()
startup.resolve(runtime)
await assert.rejects(opening, /stopped/)
await disposing
await runtime.closed
})
test('first file open carries a correctly encoded remote URI only in the new-tab URL', async () => {
const editor = new EditorWorkspaces(async () => fakeRuntime(), async () => ({ opened: false }))
const filePath = '/repo/file #?%雪.txt'
const result = await editor.openFile('/repo', filePath)
const url = new URL(result.url)
const payload = JSON.parse(url.searchParams.get('payload'))
assert.equal(result.delivery, 'new')
assert.equal(url.searchParams.get('folder'), '/repo')
assert.equal(payload[0][0], 'openFile')
const remoteFile = new URL(payload[0][1])
assert.equal(remoteFile.protocol, 'vscode-remote:')
assert.equal(remoteFile.host, url.host)
assert.equal(decodeURIComponent(remoteFile.pathname), filePath)
await editor.dispose()
})
test('file delivery to an existing workspace never returns a navigation URL', async () => {
let starts = 0
const received = []
const editor = new EditorWorkspaces(async () => { starts++; return fakeRuntime() }, async request => { received.push(request); return { opened: true } })
await editor.open('/repo')
const result = await editor.openFile('/repo', '/repo/file.txt', { requireExisting: true })
assert.deepEqual(result, { delivery: 'existing', directory: '/repo' })
assert.equal(starts, 1)
assert.equal(received[0].filePath, '/repo/file.txt')
await editor.dispose()
})
test('an unavailable remembered workspace never falls back to a replacement tab', async () => {
let starts = 0
const editor = new EditorWorkspaces(async () => { starts++; return fakeRuntime() }, async () => ({ opened: false }))
await assert.rejects(editor.openFile('/repo', '/repo/file.txt', { requireExisting: true }), /not connected/)
assert.equal(starts, 0)
await editor.open('/repo')
await assert.rejects(editor.openFile('/repo', '/repo/file.txt', { requireExisting: true }), /not ready/)
assert.equal(starts, 1)
await editor.dispose()
})
test('live bridge rejection is surfaced without new-tab fallback', async () => {
const editor = new EditorWorkspaces(async () => fakeRuntime(), async () => { throw new Error('document unavailable') })
await assert.rejects(editor.openFile('/repo', '/repo/file.txt'), /document unavailable/)
await editor.dispose()
})
function fakeChild() {
const child = new EventEmitter()
child.stdout = new EventEmitter()
child.stderr = new EventEmitter()
return child
}
test('readiness requires complete loopback Web UI announcement and strips token', async () => {
const child = fakeChild()
const ready = waitForEditorReady(child)
child.stdout.emit('data', 'Extension host agent listening on 40000\nWeb UI avail')
child.stdout.emit('data', 'able at http://127.0.0.1:40')
child.stdout.emit('data', '000/?tkn=secret\n')
assert.equal(await ready, 'http://127.0.0.1:40000')
assert.equal(child.listenerCount('exit'), 0)
assert.equal(child.stdout.listenerCount('data'), 0)
})
test('installed runtime localhost readiness format is normalized to IPv4 loopback', async () => {
const child = fakeChild()
const ready = waitForEditorReady(child)
child.stdout.emit('data', 'Web UI available at http://localhost:40000?tkn=secret\n')
assert.equal(await ready, 'http://127.0.0.1:40000')
})
test('readiness rejects an early exit and removes listeners', async () => {
const child = fakeChild()
const ready = waitForEditorReady(child)
child.emit('exit', 1)
await assert.rejects(ready, /exit 1/)
assert.equal(child.stdout.listenerCount('data'), 0)
})
test('readiness rejects a spawn error without including process output', async () => {
const child = fakeChild()
const ready = waitForEditorReady(child)
child.stderr.emit('data', 'sensitive startup output')
child.emit('error', new Error('internal detail'))
await assert.rejects(ready, { message: 'Unable to execute the installed VS Code runtime.' })
})