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') })