mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 08:02:44 +00:00
149 lines
4.9 KiB
TypeScript
149 lines
4.9 KiB
TypeScript
import { EventEmitter } from 'events'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { registerSshBrowseHandler } from './ssh-browse'
|
|
|
|
const { handleMock, removeHandlerMock } = vi.hoisted(() => ({
|
|
handleMock: vi.fn(),
|
|
removeHandlerMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('electron', () => ({
|
|
ipcMain: {
|
|
handle: handleMock,
|
|
removeHandler: removeHandlerMock
|
|
}
|
|
}))
|
|
|
|
type BrowseHandler = (
|
|
event: unknown,
|
|
args: { targetId: string; dirPath: string }
|
|
) => Promise<unknown>
|
|
|
|
function createMockChannel(): EventEmitter & { stderr: EventEmitter } {
|
|
return Object.assign(new EventEmitter(), {
|
|
stderr: new EventEmitter()
|
|
})
|
|
}
|
|
|
|
describe('registerSshBrowseHandler', () => {
|
|
let handler: BrowseHandler
|
|
|
|
beforeEach(() => {
|
|
handleMock.mockReset()
|
|
removeHandlerMock.mockReset()
|
|
handleMock.mockImplementation((_channel: string, registeredHandler: BrowseHandler) => {
|
|
handler = registeredHandler
|
|
})
|
|
})
|
|
|
|
it('bypasses remote ls aliases when listing a directory', async () => {
|
|
const channel = createMockChannel()
|
|
const exec = vi.fn().mockResolvedValue(channel)
|
|
const getConnectionManager = () => ({
|
|
getConnection: () => ({ exec })
|
|
})
|
|
registerSshBrowseHandler(getConnectionManager as never)
|
|
|
|
const resultPromise = handler(null, { targetId: 'ssh-1', dirPath: '~' })
|
|
await Promise.resolve()
|
|
channel.emit('data', Buffer.from('/home/user\nsrc/\nREADME.md\nnotes file.txt\n'))
|
|
channel.emit('exit', 0)
|
|
channel.emit('close')
|
|
|
|
await expect(resultPromise).resolves.toEqual({
|
|
resolvedPath: '/home/user',
|
|
entries: [
|
|
{ name: 'src', isDirectory: true },
|
|
{ name: 'notes file.txt', isDirectory: false },
|
|
{ name: 'README.md', isDirectory: false }
|
|
]
|
|
})
|
|
expect(exec).toHaveBeenCalledWith('cd "$HOME" && pwd && command ls -1Ap')
|
|
expect(channel.listenerCount('data')).toBe(0)
|
|
expect(channel.listenerCount('exit')).toBe(0)
|
|
expect(channel.listenerCount('close')).toBe(0)
|
|
expect(channel.listenerCount('error')).toBe(0)
|
|
expect(channel.stderr.listenerCount('data')).toBe(0)
|
|
expect(channel.stderr.listenerCount('error')).toBe(0)
|
|
})
|
|
|
|
it('escapes remote browse paths before invoking command ls', async () => {
|
|
const channel = createMockChannel()
|
|
const exec = vi.fn().mockResolvedValue(channel)
|
|
const getConnectionManager = () => ({
|
|
getConnection: () => ({ exec })
|
|
})
|
|
registerSshBrowseHandler(getConnectionManager as never)
|
|
|
|
const resultPromise = handler(null, { targetId: 'ssh-1', dirPath: "/tmp/it's here" })
|
|
await Promise.resolve()
|
|
channel.emit('data', Buffer.from("/tmp/it's here\n"))
|
|
channel.emit('exit', 0)
|
|
channel.emit('close')
|
|
|
|
await expect(resultPromise).resolves.toEqual({
|
|
resolvedPath: "/tmp/it's here",
|
|
entries: []
|
|
})
|
|
expect(exec).toHaveBeenCalledWith("cd '/tmp/it'\\''s here' && pwd && command ls -1Ap")
|
|
})
|
|
|
|
it('rejects and detaches listeners when the browse channel errors', async () => {
|
|
const channel = createMockChannel()
|
|
const exec = vi.fn().mockResolvedValue(channel)
|
|
const getConnectionManager = () => ({
|
|
getConnection: () => ({ exec })
|
|
})
|
|
registerSshBrowseHandler(getConnectionManager as never)
|
|
|
|
const resultPromise = handler(null, { targetId: 'ssh-1', dirPath: '/tmp' })
|
|
await Promise.resolve()
|
|
channel.emit('error', new Error('remote disconnected'))
|
|
|
|
await expect(resultPromise).rejects.toThrow('remote disconnected')
|
|
expect(channel.listenerCount('data')).toBe(0)
|
|
expect(channel.listenerCount('exit')).toBe(0)
|
|
expect(channel.listenerCount('close')).toBe(0)
|
|
expect(channel.listenerCount('error')).toBe(0)
|
|
expect(channel.stderr.listenerCount('data')).toBe(0)
|
|
expect(channel.stderr.listenerCount('error')).toBe(0)
|
|
})
|
|
|
|
it('times out browse channels that never close', async () => {
|
|
vi.useFakeTimers()
|
|
try {
|
|
const channel = createMockChannel()
|
|
const exec = vi.fn().mockResolvedValue(channel)
|
|
const getConnectionManager = () => ({
|
|
getConnection: () => ({ exec })
|
|
})
|
|
registerSshBrowseHandler(getConnectionManager as never)
|
|
|
|
const resultPromise = handler(null, { targetId: 'ssh-1', dirPath: '/mnt/stalled' })
|
|
let settled = false
|
|
void resultPromise.then(
|
|
() => {
|
|
settled = true
|
|
},
|
|
() => {
|
|
settled = true
|
|
}
|
|
)
|
|
|
|
await Promise.resolve()
|
|
await vi.advanceTimersByTimeAsync(15_000)
|
|
|
|
expect(settled).toBe(true)
|
|
await expect(resultPromise).rejects.toThrow('Remote directory listing timed out')
|
|
expect(channel.listenerCount('data')).toBe(0)
|
|
expect(channel.listenerCount('exit')).toBe(0)
|
|
expect(channel.listenerCount('close')).toBe(0)
|
|
expect(channel.listenerCount('error')).toBe(0)
|
|
expect(channel.stderr.listenerCount('data')).toBe(0)
|
|
expect(channel.stderr.listenerCount('error')).toBe(0)
|
|
} finally {
|
|
vi.useRealTimers()
|
|
}
|
|
})
|
|
})
|