diff --git a/backend/src/infrastructure/docker/TerminalManager.test.ts b/backend/src/infrastructure/docker/TerminalManager.test.ts new file mode 100644 index 0000000..cc1df7b --- /dev/null +++ b/backend/src/infrastructure/docker/TerminalManager.test.ts @@ -0,0 +1,41 @@ +import { PassThrough } from 'stream'; +import docker from './DockerClient'; +import { TerminalManager } from './TerminalManager'; + +jest.mock('./DockerClient', () => ({ + __esModule: true, + default: { + getContainer: jest.fn() + } +})); + +const mockedDocker = docker as jest.Mocked; + +describe('TerminalManager', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('starts an interactive exec as a raw TTY stream', async () => { + const stream = new PassThrough(); + const start = jest.fn((_options, callback) => callback(null, stream)); + const exec = { start }; + const createExec = jest.fn().mockResolvedValue(exec); + (mockedDocker.getContainer as jest.Mock).mockReturnValue({ exec: createExec }); + + const session = await TerminalManager.createTerminalSession('container-1'); + + expect(createExec).toHaveBeenCalledWith({ + Cmd: ['/bin/bash'], + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Tty: true + }); + expect(start).toHaveBeenCalledWith( + { hijack: true, stdin: true, Tty: true }, + expect.any(Function) + ); + expect(session).toEqual({ stream, exec }); + }); +}); diff --git a/backend/src/infrastructure/docker/TerminalManager.ts b/backend/src/infrastructure/docker/TerminalManager.ts index 85cf9cc..84db90a 100644 --- a/backend/src/infrastructure/docker/TerminalManager.ts +++ b/backend/src/infrastructure/docker/TerminalManager.ts @@ -18,7 +18,7 @@ export class TerminalManager { }); const stream = await new Promise((resolve, reject) => { - exec.start({ hijack: true, stdin: true }, (err, execStream) => { + exec.start({ hijack: true, stdin: true, Tty: true }, (err, execStream) => { if (err || !execStream) return reject(err || new Error('Failed to start terminal stream')); resolve(execStream); });