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.
246 lines
11 KiB
JavaScript
246 lines
11 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import net from 'node:net'
|
|
import { mkdtemp, mkdir, writeFile, rm, stat, rename, symlink } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
import { openFileInExistingWorkspace } from '../lib/editor-file-bridge.js'
|
|
import bridgeProtocol from '../editor-file-bridge/extension.js'
|
|
|
|
const { startWorkspaceBridge, workspaceSocketPrefix, MAX_MESSAGE_BYTES } = bridgeProtocol
|
|
|
|
async function fixture(t) {
|
|
const directory = await mkdtemp('/tmp/db-')
|
|
const workspaceDirectory = join(directory, 'workspace')
|
|
const bridgeDirectory = join(directory, 's')
|
|
const filePath = join(workspaceDirectory, 'space # + ü.txt')
|
|
await mkdir(workspaceDirectory)
|
|
await mkdir(bridgeDirectory, { mode: 0o700 })
|
|
await writeFile(filePath, 'disk content')
|
|
t.after(() => rm(directory, { recursive: true, force: true }))
|
|
const shown = []
|
|
const dirtyDocument = { isDirty: true, content: 'unsaved content' }
|
|
const vscode = {
|
|
Uri: { file: filePath => ({ fsPath: filePath }) },
|
|
workspace: {
|
|
workspaceFolders: [{ uri: { fsPath: workspaceDirectory } }],
|
|
openTextDocument: async uri => {
|
|
dirtyDocument.uri = uri
|
|
return dirtyDocument
|
|
},
|
|
},
|
|
window: { showTextDocument: async (document, options) => { shown.push({ document, options }) } },
|
|
}
|
|
return { directory, workspaceDirectory, bridgeDirectory, filePath, shown, dirtyDocument, vscode }
|
|
}
|
|
|
|
async function startBridge(t, setup) {
|
|
const bridge = await startWorkspaceBridge(setup)
|
|
t.after(() => bridge.dispose())
|
|
return bridge
|
|
}
|
|
|
|
async function rawServer(t, setup, suffix, onRequest) {
|
|
const socketPath = join(setup.bridgeDirectory, `${workspaceSocketPrefix(setup.workspaceDirectory)}${suffix}.sock`)
|
|
const connections = new Set()
|
|
const server = net.createServer(socket => {
|
|
connections.add(socket)
|
|
socket.once('close', () => connections.delete(socket))
|
|
socket.on('error', () => {})
|
|
let buffer = ''
|
|
socket.on('data', chunk => {
|
|
buffer += chunk
|
|
if (buffer.includes('\n')) onRequest(socket, JSON.parse(buffer.split('\n')[0]))
|
|
})
|
|
})
|
|
await new Promise((resolve, reject) => {
|
|
server.once('error', reject)
|
|
server.listen(socketPath, resolve)
|
|
})
|
|
const close = () => new Promise(resolve => {
|
|
server.close(resolve)
|
|
for (const socket of connections) socket.end()
|
|
})
|
|
t.after(close)
|
|
return { socketPath, close }
|
|
}
|
|
|
|
function rawRequest(socketPath, message) {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = net.createConnection(socketPath)
|
|
let buffer = ''
|
|
socket.once('error', reject)
|
|
socket.once('connect', () => socket.write(message))
|
|
socket.on('data', chunk => { buffer += chunk })
|
|
socket.once('end', () => resolve(JSON.parse(buffer)))
|
|
})
|
|
}
|
|
|
|
test('opens an existing dirty document without changing its content or workspace', async t => {
|
|
const setup = await fixture(t)
|
|
const bridge = await startBridge(t, setup)
|
|
assert.equal((await stat(bridge.socketPath)).mode & 0o777, 0o600)
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: true })
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: true })
|
|
assert.equal(setup.shown.length, 2)
|
|
assert.equal(setup.shown[0].document, setup.dirtyDocument)
|
|
assert.deepEqual(setup.shown[0].options, { preview: false, preserveFocus: false })
|
|
assert.equal(setup.dirtyDocument.isDirty, true)
|
|
assert.equal(setup.dirtyDocument.content, 'unsaved content')
|
|
assert.equal(setup.dirtyDocument.uri.fsPath, setup.filePath)
|
|
})
|
|
|
|
test('permits a parent-authorized file outside the opened workspace subfolder', async t => {
|
|
const setup = await fixture(t)
|
|
setup.filePath = join(setup.directory, 'outside.txt')
|
|
await writeFile(setup.filePath, 'outside')
|
|
await startBridge(t, setup)
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: true })
|
|
})
|
|
|
|
test('returns unopened for absent bridge directory or unrelated workspaces', async t => {
|
|
const setup = await fixture(t)
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: false })
|
|
assert.deepEqual(await openFileInExistingWorkspace({ ...setup, bridgeDirectory: join(setup.directory, 'missing') }), { opened: false })
|
|
await startBridge(t, setup)
|
|
const otherWorkspace = join(setup.directory, 'other')
|
|
await mkdir(otherWorkspace)
|
|
assert.deepEqual(await openFileInExistingWorkspace({ ...setup, workspaceDirectory: otherWorkspace }), { opened: false })
|
|
})
|
|
|
|
test('canonicalizes workspace aliases and removes its socket on repeated disposal', async t => {
|
|
const setup = await fixture(t)
|
|
const bridge = await startBridge(t, setup)
|
|
const alias = join(setup.directory, 'alias')
|
|
await symlink(setup.workspaceDirectory, alias)
|
|
assert.deepEqual(await openFileInExistingWorkspace({ ...setup, workspaceDirectory: alias }), { opened: true })
|
|
await bridge.dispose()
|
|
await bridge.dispose()
|
|
await assert.rejects(stat(bridge.socketPath), { code: 'ENOENT' })
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: false })
|
|
})
|
|
|
|
test('skips stale sockets and selects a deterministic live instance', async t => {
|
|
const setup = await fixture(t)
|
|
const stale = await rawServer(t, setup, '000', () => {})
|
|
const stalePath = join(setup.bridgeDirectory, `${workspaceSocketPrefix(setup.workspaceDirectory)}001.sock`)
|
|
await rename(stale.socketPath, stalePath)
|
|
await stale.close()
|
|
const requests = []
|
|
await rawServer(t, setup, '100', (socket, request) => {
|
|
requests.push('first')
|
|
socket.end(`${JSON.stringify({ requestId: request.requestId, ok: true })}\n`)
|
|
})
|
|
await rawServer(t, setup, '200', (socket, request) => {
|
|
requests.push('second')
|
|
socket.end(`${JSON.stringify({ requestId: request.requestId, ok: true })}\n`)
|
|
})
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: true })
|
|
assert.deepEqual(requests, ['first'])
|
|
})
|
|
|
|
test('returns unopened when only stale sockets remain', async t => {
|
|
const setup = await fixture(t)
|
|
const stale = await rawServer(t, setup, '000', () => {})
|
|
await rename(stale.socketPath, join(setup.bridgeDirectory, `${workspaceSocketPrefix(setup.workspaceDirectory)}001.sock`))
|
|
await stale.close()
|
|
assert.deepEqual(await openFileInExistingWorkspace(setup), { opened: false })
|
|
})
|
|
|
|
test('live document failures throw and never fall through to another instance', async t => {
|
|
const setup = await fixture(t)
|
|
let secondCalled = false
|
|
await rawServer(t, setup, '100', (socket, request) => socket.end(`${JSON.stringify({ requestId: request.requestId, ok: false, error: 'Cannot open document' })}\n`))
|
|
await rawServer(t, setup, '200', () => { secondCalled = true })
|
|
await assert.rejects(openFileInExistingWorkspace(setup), /Cannot open document/)
|
|
assert.equal(secondCalled, false)
|
|
})
|
|
|
|
test('rejects invalid acknowledgements and disconnected live editors', async t => {
|
|
for (const response of ['invalid\n', '{"requestId":"wrong","ok":true}\n', '']) {
|
|
await t.test(JSON.stringify(response), async t => {
|
|
const setup = await fixture(t)
|
|
await rawServer(t, setup, '100', socket => socket.end(response))
|
|
await assert.rejects(openFileInExistingWorkspace(setup), /acknowledg|disconnected/)
|
|
})
|
|
}
|
|
})
|
|
|
|
test('cancellation after a live connection throws rather than allowing fallback', async t => {
|
|
const setup = await fixture(t)
|
|
const controller = new AbortController()
|
|
await rawServer(t, setup, '100', () => controller.abort(new Error('Cancelled by test')))
|
|
await assert.rejects(openFileInExistingWorkspace({ ...setup, signal: controller.signal }), /Cancelled by test/)
|
|
})
|
|
|
|
test('rejects workspace mismatch, changed workspace, relative paths and non-files', async t => {
|
|
const setup = await fixture(t)
|
|
const bridge = await startBridge(t, setup)
|
|
const request = { kind: 'open-file', workspaceDirectory: setup.workspaceDirectory, filePath: setup.filePath, requestId: 'test' }
|
|
for (const changes of [{ workspaceDirectory: setup.directory }, { filePath: 'relative.txt' }, { filePath: setup.workspaceDirectory }, { filePath: join(setup.directory, 'missing') }]) {
|
|
const response = await rawRequest(bridge.socketPath, `${JSON.stringify({ ...request, ...changes })}\n`)
|
|
assert.equal(response.ok, false)
|
|
}
|
|
setup.vscode.workspace.workspaceFolders = []
|
|
await assert.rejects(openFileInExistingWorkspace(setup), /workspace has changed/)
|
|
assert.equal(setup.shown.length, 0)
|
|
})
|
|
|
|
test('bounds incoming messages and rejects malformed JSON', async t => {
|
|
const setup = await fixture(t)
|
|
const bridge = await startBridge(t, setup)
|
|
assert.equal((await rawRequest(bridge.socketPath, 'not-json\n')).ok, false)
|
|
assert.equal((await rawRequest(bridge.socketPath, `${'x'.repeat(MAX_MESSAGE_BYTES + 1)}\n`)).ok, false)
|
|
assert.equal(setup.shown.length, 0)
|
|
})
|
|
|
|
test('rejects insecure bridge directory and overly long socket paths', async t => {
|
|
const setup = await fixture(t)
|
|
const insecureDirectory = join(setup.directory, 'insecure')
|
|
await mkdir(insecureDirectory, { mode: 0o755 })
|
|
await assert.rejects(startWorkspaceBridge({ ...setup, bridgeDirectory: insecureDirectory }), /mode 0700/)
|
|
const longDirectory = join(setup.directory, 'x'.repeat(80))
|
|
await mkdir(longDirectory, { mode: 0o700 })
|
|
await assert.rejects(startWorkspaceBridge({ ...setup, bridgeDirectory: longDirectory }), /too long/)
|
|
})
|
|
|
|
test('propagates public VS Code document opening failures', async t => {
|
|
const setup = await fixture(t)
|
|
setup.vscode.workspace.openTextDocument = async () => { throw new Error('Document provider rejected file') }
|
|
await startBridge(t, setup)
|
|
await assert.rejects(openFileInExistingWorkspace(setup), /Document provider rejected file/)
|
|
assert.equal(setup.shown.length, 0)
|
|
})
|
|
|
|
test('bounds acknowledgements from live clients', async t => {
|
|
const setup = await fixture(t)
|
|
await rawServer(t, setup, '100', socket => socket.end('x'.repeat(MAX_MESSAGE_BYTES + 1)))
|
|
await assert.rejects(openFileInExistingWorkspace(setup), /acknowledgement is too large/)
|
|
})
|
|
|
|
test('accepts fragmented UTF-8 messages and processes only one request per connection', async t => {
|
|
const setup = await fixture(t)
|
|
const bridge = await startBridge(t, setup)
|
|
const request = Buffer.from(`${JSON.stringify({ kind: 'open-file', workspaceDirectory: setup.workspaceDirectory, filePath: setup.filePath, requestId: 'fragmented' })}\n`)
|
|
const response = await new Promise((resolve, reject) => {
|
|
const socket = net.createConnection(bridge.socketPath)
|
|
let buffered = ''
|
|
socket.once('error', reject)
|
|
socket.once('connect', () => {
|
|
const unicodeOffset = request.indexOf(Buffer.from('ü'))
|
|
socket.write(request.subarray(0, unicodeOffset + 1), () => {
|
|
socket.write(Buffer.concat([request.subarray(unicodeOffset + 1), request]))
|
|
})
|
|
})
|
|
socket.on('data', chunk => { buffered += chunk })
|
|
socket.once('end', () => resolve(JSON.parse(buffered)))
|
|
})
|
|
assert.deepEqual(response, { requestId: 'fragmented', ok: true })
|
|
assert.equal(setup.shown.length, 1)
|
|
})
|
|
|
|
test('does not create a bridge for an empty workspace or missing configuration', async t => {
|
|
const setup = await fixture(t)
|
|
assert.equal(await startWorkspaceBridge({ ...setup, bridgeDirectory: undefined }), null)
|
|
setup.vscode.workspace.workspaceFolders = []
|
|
assert.equal(await startWorkspaceBridge(setup), null)
|
|
})
|