159 lines
4.2 KiB
TypeScript
159 lines
4.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
|
|
const mocks = vi.hoisted(() => {
|
|
const connect = vi.fn()
|
|
const run = vi.fn()
|
|
const sendStdin = vi.fn()
|
|
const closeStdin = vi.fn()
|
|
const ensureAPIKey = vi.fn(() => 'test-api-key')
|
|
const isPipedStdin = vi.fn()
|
|
const streamStdinChunks = vi.fn()
|
|
const setupSignalHandlers = vi.fn(() => () => {})
|
|
|
|
return {
|
|
connect,
|
|
run,
|
|
sendStdin,
|
|
closeStdin,
|
|
ensureAPIKey,
|
|
isPipedStdin,
|
|
streamStdinChunks,
|
|
setupSignalHandlers,
|
|
}
|
|
})
|
|
|
|
vi.mock('e2b', () => {
|
|
class CommandExitError extends Error {
|
|
exitCode: number
|
|
constructor(exitCode: number) {
|
|
super(`Command exited with ${exitCode}`)
|
|
this.exitCode = exitCode
|
|
}
|
|
}
|
|
|
|
class NotFoundError extends Error {}
|
|
|
|
return {
|
|
Sandbox: {
|
|
connect: mocks.connect,
|
|
},
|
|
CommandExitError,
|
|
NotFoundError,
|
|
}
|
|
})
|
|
|
|
vi.mock('../../../src/api', () => ({
|
|
ensureAPIKey: mocks.ensureAPIKey,
|
|
}))
|
|
|
|
vi.mock('src/utils/signal', () => ({
|
|
setupSignalHandlers: mocks.setupSignalHandlers,
|
|
}))
|
|
|
|
vi.mock(
|
|
'../../../src/commands/sandbox/exec_helpers',
|
|
async (importOriginal: <T>() => Promise<T>) => {
|
|
const actual =
|
|
await importOriginal<
|
|
typeof import('../../../src/commands/sandbox/exec_helpers')
|
|
>()
|
|
return {
|
|
...actual,
|
|
isPipedStdin: mocks.isPipedStdin,
|
|
streamStdinChunks: mocks.streamStdinChunks,
|
|
}
|
|
}
|
|
)
|
|
|
|
describe('sandbox exec stdin run flag', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules()
|
|
vi.clearAllMocks()
|
|
|
|
const handle = {
|
|
pid: 1234,
|
|
error: undefined,
|
|
wait: vi.fn().mockResolvedValue({ exitCode: 0 }),
|
|
kill: vi.fn().mockResolvedValue(undefined),
|
|
disconnect: vi.fn().mockResolvedValue(undefined),
|
|
}
|
|
|
|
mocks.run.mockResolvedValue(handle)
|
|
mocks.sendStdin.mockResolvedValue(undefined)
|
|
mocks.closeStdin.mockResolvedValue(undefined)
|
|
mocks.isPipedStdin.mockReturnValue(false)
|
|
mocks.streamStdinChunks.mockResolvedValue(undefined)
|
|
mocks.connect.mockResolvedValue({
|
|
commands: {
|
|
run: mocks.run,
|
|
sendStdin: mocks.sendStdin,
|
|
closeStdin: mocks.closeStdin,
|
|
supportsStdinClose: true,
|
|
},
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
test('does not pass stdin flag when stdin is not piped', async () => {
|
|
const exitSpy = vi
|
|
.spyOn(process, 'exit')
|
|
.mockImplementation((() => undefined) as never)
|
|
|
|
const { execCommand } = await import('../../../src/commands/sandbox/exec')
|
|
await execCommand.parseAsync(['sandbox-id', 'echo', 'hello'], {
|
|
from: 'user',
|
|
})
|
|
|
|
expect(mocks.run).toHaveBeenCalledTimes(1)
|
|
const runOpts = mocks.run.mock.calls[0][1]
|
|
expect(runOpts).not.toHaveProperty('stdin')
|
|
expect(mocks.streamStdinChunks).not.toHaveBeenCalled()
|
|
expect(exitSpy).toHaveBeenCalledWith(0)
|
|
})
|
|
|
|
test('passes stdin: true when stdin piping is active', async () => {
|
|
mocks.isPipedStdin.mockReturnValue(true)
|
|
const events: string[] = []
|
|
mocks.setupSignalHandlers.mockImplementation(() => {
|
|
events.push('setup')
|
|
return () => {
|
|
events.push('cleanup')
|
|
}
|
|
})
|
|
mocks.streamStdinChunks.mockImplementation(
|
|
async (
|
|
_stream: NodeJS.ReadableStream,
|
|
onChunk: (chunk: Uint8Array) => Promise<void | boolean>,
|
|
_maxBytes: number
|
|
) => {
|
|
events.push('stream-start')
|
|
await onChunk(Buffer.from('hello'))
|
|
events.push('stream-end')
|
|
}
|
|
)
|
|
const exitSpy = vi
|
|
.spyOn(process, 'exit')
|
|
.mockImplementation((() => undefined) as never)
|
|
|
|
const { execCommand } = await import('../../../src/commands/sandbox/exec')
|
|
await execCommand.parseAsync(['sandbox-id', 'cat'], {
|
|
from: 'user',
|
|
})
|
|
|
|
expect(mocks.run).toHaveBeenCalledTimes(1)
|
|
const runOpts = mocks.run.mock.calls[0][1]
|
|
expect(runOpts).toHaveProperty('stdin', true)
|
|
expect(mocks.streamStdinChunks).toHaveBeenCalled()
|
|
expect(mocks.sendStdin).toHaveBeenCalled()
|
|
expect(mocks.closeStdin).toHaveBeenCalled()
|
|
expect(events).toContain('setup')
|
|
expect(events).toContain('stream-start')
|
|
expect(events.indexOf('setup')).toBeLessThan(events.indexOf('stream-start'))
|
|
expect(events).toContain('cleanup')
|
|
expect(exitSpy).toHaveBeenCalledWith(0)
|
|
})
|
|
})
|