206 lines
6.1 KiB
TypeScript
206 lines
6.1 KiB
TypeScript
/* eslint-disable @typescript-eslint/unbound-method */
|
||
|
||
import { Test, TestingModule } from '@nestjs/testing';
|
||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||
import { Repository } from 'typeorm';
|
||
import { HttpException } from '@nestjs/common';
|
||
import { TunnelsService } from 'src/tunnels/tunnels.service';
|
||
import { Tunnel } from 'src/tunnels/entities/tunnel.entity';
|
||
import { SshService } from 'src/tunnels/ssh.service';
|
||
import { Setting } from 'src/settings/entities/setting.entity';
|
||
|
||
describe('TunnelsService', () => {
|
||
let service: TunnelsService;
|
||
let tunnelRepo: Repository<Tunnel>;
|
||
let sshService: SshService;
|
||
|
||
const mockTunnelRepo = {
|
||
find: jest.fn(),
|
||
findOne: jest.fn(),
|
||
create: jest.fn(),
|
||
save: jest.fn(),
|
||
delete: jest.fn(),
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
getOne: jest.fn(),
|
||
})),
|
||
};
|
||
|
||
const mockSettingRepo = {
|
||
find: jest.fn(),
|
||
findOne: jest.fn(),
|
||
create: jest.fn(),
|
||
save: jest.fn(),
|
||
};
|
||
|
||
const mockSshService = {
|
||
executeCommand: jest.fn(),
|
||
};
|
||
|
||
beforeEach(async () => {
|
||
const module: TestingModule = await Test.createTestingModule({
|
||
providers: [
|
||
TunnelsService,
|
||
{
|
||
provide: getRepositoryToken(Tunnel),
|
||
useValue: mockTunnelRepo,
|
||
},
|
||
{
|
||
provide: getRepositoryToken(Setting),
|
||
useValue: mockSettingRepo,
|
||
},
|
||
{
|
||
provide: SshService,
|
||
useValue: mockSshService,
|
||
},
|
||
],
|
||
}).compile();
|
||
|
||
service = module.get<TunnelsService>(TunnelsService);
|
||
tunnelRepo = module.get<Repository<Tunnel>>(getRepositoryToken(Tunnel));
|
||
settingRepo = module.get<Repository<Setting>>(getRepositoryToken(Setting));
|
||
sshService = module.get<SshService>(SshService);
|
||
});
|
||
|
||
afterEach(() => {
|
||
jest.clearAllMocks();
|
||
});
|
||
|
||
describe('create', () => {
|
||
it('должен создать туннель', async () => {
|
||
const dto = { ip: '192.168.1.1', sshPort: 22, username: 'root' };
|
||
const mockTunnel = { id: 1, ...dto };
|
||
|
||
mockTunnelRepo.create.mockReturnValue(mockTunnel);
|
||
mockTunnelRepo.save.mockResolvedValue(mockTunnel);
|
||
|
||
const result = await service.create(dto);
|
||
|
||
expect(result).toEqual(mockTunnel);
|
||
expect(tunnelRepo.create).toHaveBeenCalledWith(dto);
|
||
});
|
||
});
|
||
|
||
describe('findAll', () => {
|
||
it('должен вернуть все туннели', async () => {
|
||
const mockTunnels = [
|
||
{ id: 1, ip: '192.168.1.1' },
|
||
{ id: 2, ip: '192.168.1.2' },
|
||
];
|
||
|
||
mockTunnelRepo.find.mockResolvedValue(mockTunnels);
|
||
|
||
const result = await service.findAll();
|
||
|
||
expect(result).toEqual(mockTunnels);
|
||
expect(tunnelRepo.find).toHaveBeenCalledTimes(1);
|
||
});
|
||
});
|
||
|
||
describe('remove', () => {
|
||
it('должен удалить туннель по ID', async () => {
|
||
mockTunnelRepo.delete.mockResolvedValue({ affected: 1 });
|
||
|
||
await service.remove(1);
|
||
|
||
expect(tunnelRepo.delete).toHaveBeenCalledWith(1);
|
||
});
|
||
});
|
||
|
||
describe('installScript', () => {
|
||
const mockTunnel = {
|
||
id: 1,
|
||
ip: '192.168.1.100',
|
||
sshPort: 22,
|
||
username: 'root',
|
||
password: 'password123',
|
||
privateKey: null,
|
||
isInstalled: false,
|
||
};
|
||
|
||
it('должен установить скрипт перенаправления', async () => {
|
||
mockTunnelRepo.createQueryBuilder.mockReturnValue({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
getOne: jest.fn().mockResolvedValue(mockTunnel),
|
||
});
|
||
|
||
mockSettingRepo.findOne.mockResolvedValue({
|
||
key: 'xui_ip',
|
||
value: '10.0.0.1',
|
||
});
|
||
mockSshService.executeCommand.mockResolvedValue(
|
||
'Script executed successfully',
|
||
);
|
||
mockTunnelRepo.save.mockResolvedValue({
|
||
...mockTunnel,
|
||
isInstalled: true,
|
||
});
|
||
|
||
const result = await service.installScript(1);
|
||
|
||
expect(result).toEqual({
|
||
success: true,
|
||
output: 'Script executed successfully',
|
||
});
|
||
expect(sshService.executeCommand).toHaveBeenCalled();
|
||
expect(tunnelRepo.save).toHaveBeenCalledWith(
|
||
expect.objectContaining({ isInstalled: true }),
|
||
);
|
||
});
|
||
|
||
it('должен бросить HttpException, если туннель не найден', async () => {
|
||
mockTunnelRepo.createQueryBuilder.mockReturnValue({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
getOne: jest.fn().mockResolvedValue(null),
|
||
});
|
||
|
||
await expect(service.installScript(999)).rejects.toThrow(HttpException);
|
||
|
||
await expect(service.installScript(999)).rejects.toThrow(
|
||
'Tunnel not found',
|
||
);
|
||
});
|
||
|
||
it('должен бросить HttpException, если xui_host не настроен', async () => {
|
||
mockTunnelRepo.createQueryBuilder.mockReturnValue({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
getOne: jest.fn().mockResolvedValue(mockTunnel),
|
||
});
|
||
|
||
mockSettingRepo.findOne.mockResolvedValue(null);
|
||
|
||
await expect(service.installScript(1)).rejects.toThrow(HttpException);
|
||
|
||
await expect(service.installScript(1)).rejects.toThrow(
|
||
'В настройках (Settings) не сохранен Host/IP основного сервера',
|
||
);
|
||
});
|
||
|
||
it('должен бросить HttpException при ошибке SSH', async () => {
|
||
mockTunnelRepo.createQueryBuilder.mockReturnValue({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
getOne: jest.fn().mockResolvedValue(mockTunnel),
|
||
});
|
||
|
||
mockSettingRepo.findOne.mockResolvedValue({
|
||
key: 'xui_ip',
|
||
value: '10.0.0.1',
|
||
});
|
||
mockSshService.executeCommand.mockRejectedValue(
|
||
new Error('SSH connection failed'),
|
||
);
|
||
|
||
await expect(service.installScript(1)).rejects.toThrow(HttpException);
|
||
|
||
await expect(service.installScript(1)).rejects.toThrow(
|
||
'Ошибка установки: SSH connection failed',
|
||
);
|
||
});
|
||
});
|
||
});
|