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.
193 lines
6.1 KiB
JavaScript
193 lines
6.1 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { createEditorOpenHandler } from '../lib/editor-route.js'
|
|
|
|
function trackedEntry(overrides = {}) {
|
|
return {
|
|
ok: true,
|
|
id: 'entry-1',
|
|
path: '/repo',
|
|
repository: '/repo',
|
|
workingDirectory: '/repo/work',
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function makeRequest(overrides = {}) {
|
|
return {
|
|
method: 'POST',
|
|
url: '/touched-git/editor/open?sessionId=s1&path=%2Frepo%2Fwork',
|
|
headers: { host: '127.0.0.1:3080', 'x-dsh-editor-action': 'open' },
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function makeResponse() {
|
|
return {
|
|
statusCode: 0,
|
|
headers: {},
|
|
body: null,
|
|
setHeader(name, value) { this.headers[name] = value },
|
|
}
|
|
}
|
|
|
|
function writeJson(response, status, body) {
|
|
response.statusCode = status
|
|
response.body = body
|
|
return body
|
|
}
|
|
|
|
function buildHandler(overrides = {}) {
|
|
const calls = { resolved: [], opened: [] }
|
|
const deps = {
|
|
store: { list: async () => [trackedEntry()] },
|
|
resolveDirectoryRepository: async (directory) => {
|
|
calls.resolved.push(directory)
|
|
return { repository: '/repo', workingDirectory: '/repo/work' }
|
|
},
|
|
isLoopbackRequest: () => true,
|
|
editor: {
|
|
open: async (directory) => {
|
|
calls.opened.push(directory)
|
|
return { url: 'http://127.0.0.1:40000/?tkn=x&folder=/repo/work', directory }
|
|
},
|
|
},
|
|
writeJson,
|
|
...overrides,
|
|
}
|
|
return { handler: createEditorOpenHandler(deps), calls }
|
|
}
|
|
|
|
test('a non-POST request is rejected and never opens an editor', async () => {
|
|
const { handler, calls } = buildHandler()
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest({ method: 'GET' }), response)
|
|
|
|
assert.equal(response.statusCode, 405)
|
|
assert.equal(body.error.code, 'method-not-allowed')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a cross-site request without a loopback origin is forbidden', async () => {
|
|
const { handler, calls } = buildHandler({ isLoopbackRequest: () => false })
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 403)
|
|
assert.equal(body.error.code, 'forbidden')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a request missing the custom editor-action header is forbidden', async () => {
|
|
const { handler, calls } = buildHandler()
|
|
const response = makeResponse()
|
|
const request = makeRequest({ headers: { host: '127.0.0.1:3080' } })
|
|
|
|
const body = await handler(request, response)
|
|
|
|
assert.equal(response.statusCode, 403)
|
|
assert.equal(body.error.code, 'forbidden')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a request missing the session or directory is a bad request', async () => {
|
|
const { handler, calls } = buildHandler()
|
|
const response = makeResponse()
|
|
const request = makeRequest({ url: '/touched-git/editor/open?sessionId=s1' })
|
|
|
|
const body = await handler(request, response)
|
|
|
|
assert.equal(response.statusCode, 400)
|
|
assert.equal(body.error.code, 'invalid-request')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a directory the session does not track is refused', async () => {
|
|
const { handler, calls } = buildHandler({ store: { list: async () => [] } })
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 403)
|
|
assert.equal(body.error.code, 'not-tracked')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('an invalid tracked entry is never matched or opened', async () => {
|
|
const invalid = trackedEntry({ ok: false, error: { message: 'Directory is unavailable.' } })
|
|
const { handler, calls } = buildHandler({ store: { list: async () => [invalid] } })
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 403)
|
|
assert.equal(body.error.code, 'not-tracked')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a relocated repository is refused before opening', async () => {
|
|
const { handler, calls } = buildHandler({
|
|
resolveDirectoryRepository: async () => ({ repository: '/moved', workingDirectory: '/moved/work' }),
|
|
})
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 409)
|
|
assert.equal(body.error.code, 'directory-changed')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a directory deleted since tracking fails resolution and never opens', async () => {
|
|
const { handler, calls } = buildHandler({
|
|
resolveDirectoryRepository: async () => { throw new Error('ENOENT: no such file or directory') },
|
|
})
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 503)
|
|
assert.equal(body.error.code, 'editor-unavailable')
|
|
assert.equal(body.error.message, 'ENOENT: no such file or directory')
|
|
assert.equal(calls.opened.length, 0)
|
|
})
|
|
|
|
test('a tracked directory opens its resolved working directory only', async () => {
|
|
const { handler, calls } = buildHandler()
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 200)
|
|
assert.equal(body.ok, true)
|
|
assert.equal(body.value.directory, '/repo/work')
|
|
assert.deepEqual(calls.resolved, ['/repo/work'])
|
|
assert.deepEqual(calls.opened, ['/repo/work'])
|
|
assert.equal(response.headers['cache-control'], 'no-store')
|
|
})
|
|
|
|
test('an entry matched by its id still opens the resolved working directory', async () => {
|
|
const { handler, calls } = buildHandler()
|
|
const response = makeResponse()
|
|
const request = makeRequest({ url: '/touched-git/editor/open?sessionId=s1&path=entry-1' })
|
|
|
|
const body = await handler(request, response)
|
|
|
|
assert.equal(response.statusCode, 200)
|
|
assert.deepEqual(calls.opened, ['/repo/work'])
|
|
})
|
|
|
|
test('an editor failure is reported as an unavailable editor', async () => {
|
|
const { handler } = buildHandler({
|
|
editor: { open: async () => { throw new Error('runtime is not installed') } },
|
|
})
|
|
const response = makeResponse()
|
|
|
|
const body = await handler(makeRequest(), response)
|
|
|
|
assert.equal(response.statusCode, 503)
|
|
assert.equal(body.error.code, 'editor-unavailable')
|
|
assert.equal(body.error.message, 'runtime is not installed')
|
|
})
|