219 lines
6.6 KiB
TypeScript
219 lines
6.6 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { SshService } from 'src/tunnels/ssh.service';
|
|
|
|
// Мокируем ssh2 перед импортом сервиса
|
|
const mockStreamOn = jest.fn();
|
|
const mockStreamStderrOn = jest.fn();
|
|
const mockStream = {
|
|
on: mockStreamOn,
|
|
stderr: { on: mockStreamStderrOn },
|
|
};
|
|
|
|
const mockConnOn = jest.fn();
|
|
const mockConnExec = jest.fn();
|
|
const mockConnConnect = jest.fn();
|
|
const mockConnEnd = jest.fn();
|
|
const mockConn = {
|
|
on: mockConnOn,
|
|
exec: mockConnExec,
|
|
connect: mockConnConnect,
|
|
end: mockConnEnd,
|
|
};
|
|
|
|
jest.mock('ssh2', () => ({
|
|
Client: jest.fn(() => mockConn),
|
|
}));
|
|
|
|
describe('SshService', () => {
|
|
let service: SshService;
|
|
let readyCallback: (() => void) | null = null;
|
|
let errorCallback: ((err: Error) => void) | null = null;
|
|
let streamCloseCallback: ((code: number, signal: unknown) => void) | null =
|
|
null;
|
|
let streamDataCallback: ((data: Buffer) => void) | null = null;
|
|
let streamStderrDataCallback: ((data: Buffer) => void) | null = null;
|
|
|
|
beforeEach(async () => {
|
|
// Сброс всех моков
|
|
jest.clearAllMocks();
|
|
readyCallback = null;
|
|
errorCallback = null;
|
|
streamCloseCallback = null;
|
|
streamDataCallback = null;
|
|
streamStderrDataCallback = null;
|
|
|
|
// Настройка mockConn.on для сохранения callback'ов
|
|
mockConnOn.mockImplementation((event: string, cb: () => void) => {
|
|
if (event === 'ready') readyCallback = cb;
|
|
if (event === 'error') errorCallback = cb;
|
|
return mockConn;
|
|
});
|
|
|
|
// Настройка mockConn.exec
|
|
mockConnExec.mockImplementation(
|
|
(_command: string, cb: (err: Error | null, stream: unknown) => void) => {
|
|
cb(null, mockStream);
|
|
return mockStream;
|
|
},
|
|
);
|
|
|
|
// Настройка stream.on
|
|
mockStreamOn.mockImplementation(
|
|
(event: string, cb: (...args: unknown[]) => void) => {
|
|
if (event === 'close')
|
|
streamCloseCallback = cb as (code: number, signal: unknown) => void;
|
|
if (event === 'data') streamDataCallback = cb as (data: Buffer) => void;
|
|
return mockStream;
|
|
},
|
|
);
|
|
|
|
// Настройка stream.stderr.on
|
|
mockStreamStderrOn.mockImplementation(
|
|
(event: string, cb: (data: Buffer) => void) => {
|
|
if (event === 'data') streamStderrDataCallback = cb;
|
|
return { on: mockStreamStderrOn };
|
|
},
|
|
);
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [SshService],
|
|
}).compile();
|
|
|
|
service = module.get<SshService>(SshService);
|
|
});
|
|
|
|
describe('executeCommand', () => {
|
|
const config = {
|
|
host: '192.168.1.100',
|
|
port: 22,
|
|
username: 'root',
|
|
password: 'password123',
|
|
};
|
|
|
|
const command = 'echo "test"';
|
|
|
|
it('должен выполнить команду успешно', async () => {
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
// Симулируем успешное подключение
|
|
readyCallback();
|
|
|
|
// Симулируем получение данных
|
|
streamDataCallback(Buffer.from('test output\n'));
|
|
|
|
// Симулируем завершение команды с кодом 0
|
|
streamCloseCallback(0, null);
|
|
|
|
const result = await connectPromise;
|
|
|
|
expect(result).toBe('test output\n');
|
|
expect(mockConnConnect).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
host: '192.168.1.100',
|
|
port: 22,
|
|
username: 'root',
|
|
password: 'password123',
|
|
readyTimeout: 20000,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('должен выполнить команду с privateKey', async () => {
|
|
const configWithKey = {
|
|
...config,
|
|
privateKey: '-----BEGIN OPENSSH PRIVATE KEY-----',
|
|
};
|
|
|
|
const connectPromise = service.executeCommand(configWithKey, command);
|
|
|
|
readyCallback();
|
|
streamDataCallback(Buffer.from('success'));
|
|
streamCloseCallback(0, null);
|
|
|
|
await connectPromise;
|
|
|
|
expect(mockConnConnect).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
privateKey: '-----BEGIN OPENSSH PRIVATE KEY-----',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('должен обработать ошибку подключения', async () => {
|
|
const error = new Error('Connection refused');
|
|
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
// Симулируем ошибку подключения
|
|
errorCallback(error);
|
|
|
|
await expect(connectPromise).rejects.toThrow('Connection refused');
|
|
});
|
|
|
|
it('должен обработать ошибку выполнения команды', async () => {
|
|
const execError = new Error('Command not found');
|
|
|
|
mockConnExec.mockImplementationOnce(
|
|
(_command: string, cb: (err: Error | null) => void) => {
|
|
cb(execError, null);
|
|
},
|
|
);
|
|
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
readyCallback();
|
|
|
|
await expect(connectPromise).rejects.toThrow('Command not found');
|
|
});
|
|
|
|
it('должен обработать ненулевой код выхода', async () => {
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
readyCallback();
|
|
streamDataCallback(Buffer.from('error output'));
|
|
streamCloseCallback(1, null);
|
|
|
|
await expect(connectPromise).rejects.toThrow('Exit code 1');
|
|
});
|
|
|
|
it('должен собрать вывод из stderr', async () => {
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
readyCallback();
|
|
streamDataCallback(Buffer.from('stdout'));
|
|
streamStderrDataCallback(Buffer.from('stderr'));
|
|
streamCloseCallback(0, null);
|
|
|
|
const result = await connectPromise;
|
|
|
|
expect(result).toContain('stdout');
|
|
expect(result).toContain('stderr');
|
|
});
|
|
|
|
it('должен обработать пустой вывод', async () => {
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
readyCallback();
|
|
streamCloseCallback(0, null);
|
|
|
|
const result = await connectPromise;
|
|
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('должен собрать вывод из нескольких чанков', async () => {
|
|
const connectPromise = service.executeCommand(config, command);
|
|
|
|
readyCallback();
|
|
streamDataCallback(Buffer.from('chunk1'));
|
|
streamDataCallback(Buffer.from('chunk2'));
|
|
streamDataCallback(Buffer.from('chunk3'));
|
|
streamCloseCallback(0, null);
|
|
|
|
const result = await connectPromise;
|
|
|
|
expect(result).toBe('chunk1chunk2chunk3');
|
|
});
|
|
});
|
|
});
|