Files
3dp-manager/server/test/inbounds/inbound-builder.service.spec.ts
T
Den Piligrim 26f3bb2934 v2.2.0
2026-05-21 22:58:29 +03:00

590 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Test, TestingModule } from '@nestjs/testing';
import { InboundBuilderService } from 'src/inbounds/inbound-builder.service';
// Mock crypto.randomBytes для детерминированных тестов
jest.mock('crypto', () => ({
randomBytes: jest.fn().mockReturnValue(Buffer.from('abcd1234', 'hex')),
randomFillSync: jest.fn((buffer: Buffer) => {
for (let i = 0; i < buffer.length; i++) {
buffer[i] = i;
}
return buffer;
}),
}));
describe('InboundBuilderService', () => {
let service: InboundBuilderService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [InboundBuilderService],
}).compile();
service = module.get<InboundBuilderService>(InboundBuilderService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('buildVlessRealityTcp', () => {
const params = {
port: 443,
uuid: 'test-uuid-123',
sni: 'ya.ru',
privateKey: 'private-key',
publicKey: 'public-key',
};
it('должен создать конфиг vless-tcp-reality', () => {
const result = service.buildVlessRealityTcp(params);
expect(result).toMatchObject({
enable: true,
port: 443,
protocol: 'vless',
remark: 'vless-tcp-reality',
});
const settings = JSON.parse(result.settings);
expect(settings.clients[0].id).toBe('test-uuid-123');
expect(settings.clients[0].flow).toBe('xtls-rprx-vision');
});
it('должен установить Reality настройки', () => {
const result = service.buildVlessRealityTcp(params);
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.security).toBe('reality');
expect(streamSettings.realitySettings.target).toBe('ya.ru:443');
expect(streamSettings.realitySettings.serverNames).toContain('ya.ru');
expect(streamSettings.realitySettings.privateKey).toBe('private-key');
});
it('должен сгенерировать shortIds', () => {
const result = service.buildVlessRealityTcp(params);
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.realitySettings.shortIds).toHaveLength(2);
});
});
describe('buildVlessRealityXhttp', () => {
const params = {
port: 8443,
uuid: 'test-uuid-456',
sni: 'vk.com',
privateKey: 'private-key',
publicKey: 'public-key',
};
it('должен создать конфиг vless-xhttp-reality', () => {
const result = service.buildVlessRealityXhttp(params);
expect(result).toMatchObject({
enable: true,
port: 8443,
protocol: 'vless',
remark: 'vless-xhttp-reality',
});
const settings = JSON.parse(result.settings);
expect(settings.clients[0].id).toBe('test-uuid-456');
expect(settings.clients[0].flow).toBe('');
});
it('должен установить xhttp настройки', () => {
const result = service.buildVlessRealityXhttp(params);
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.security).toBe('reality');
expect(streamSettings.network).toBe('xhttp');
});
});
describe('buildVlessRealityGrpc', () => {
const params = {
port: 2053,
uuid: 'test-uuid-789',
sni: 'ok.ru',
privateKey: 'private-key',
publicKey: 'public-key',
};
it('должен создать конфиг vless-grpc-reality', () => {
const result = service.buildVlessRealityGrpc(params);
expect(result).toMatchObject({
enable: true,
port: 2053,
protocol: 'vless',
remark: 'vless-grpc-reality',
});
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.network).toBe('grpc');
expect(streamSettings.grpcSettings?.serviceName).toBeTruthy();
});
});
describe('buildVlessWs', () => {
const params = {
port: 10000,
uuid: 'test-uuid-ws',
sni: 'ozon.ru',
};
it('должен создать конфиг vless-ws', () => {
const result = service.buildVlessWs(params);
expect(result).toMatchObject({
enable: true,
port: 10000,
protocol: 'vless',
remark: 'vless-ws',
});
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.network).toBe('ws');
});
it('должен установить ws настройки', () => {
const result = service.buildVlessWs(params);
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.wsSettings?.path).toBe('/');
});
});
describe('buildVmessTcp', () => {
const params = {
port: 20000,
uuid: 'test-uuid-vmess',
};
it('должен создать конфиг vmess-tcp', () => {
const result = service.buildVmessTcp(params);
expect(result).toMatchObject({
enable: true,
port: 20000,
protocol: 'vmess',
remark: 'vmess-tcp',
});
const settings = JSON.parse(result.settings);
expect(settings.clients[0].id).toBe('test-uuid-vmess');
});
});
describe('buildShadowsocksTcp', () => {
const params = {
port: 30000,
uuid: 'test-uuid-ss',
};
it('должен создать конфиг shadowsocks-tcp', () => {
const result = service.buildShadowsocksTcp(params);
expect(result).toMatchObject({
enable: true,
port: 30000,
protocol: 'shadowsocks',
remark: 'shadowsocks-tcp',
});
const settings = JSON.parse(result.settings);
expect(settings.method).toBe('2022-blake3-aes-256-gcm');
expect(settings.password).toBeTruthy(); // Генерируется из uuid
});
});
describe('buildTrojanRealityTcp', () => {
const params = {
port: 443,
uuid: 'test-uuid-trojan',
sni: 'ya.ru',
privateKey: 'private-key',
publicKey: 'public-key',
};
it('должен создать конфиг trojan-tcp-reality', () => {
const result = service.buildTrojanRealityTcp(params);
expect(result).toMatchObject({
enable: true,
port: 443,
protocol: 'trojan',
remark: 'trojan-tcp-reality',
});
const settings = JSON.parse(result.settings);
expect(settings.clients[0].password).toBeTruthy(); // Генерируется из uuid
});
it('должен установить Reality настройки для trojan', () => {
const result = service.buildTrojanRealityTcp(params);
const streamSettings = JSON.parse(result.streamSettings);
expect(streamSettings.security).toBe('reality');
expect(streamSettings.realitySettings.target).toBe('ya.ru:443');
});
});
describe('buildHysteria2Link', () => {
beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation(() => {});
});
afterEach(() => {
jest.restoreAllMocks();
});
it('должен создать ссылку hysteria2', () => {
const result = service.buildHysteria2Link(
'192.168.1.1',
'ya.ru',
'%F0%9F%92%AF%20hysteria2',
);
expect(result).toContain('hy2://');
expect(result).toContain('192.168.1.1');
// SNI может быть IP, если конфиг не найден
});
it('должен создать ссылку hysteria2 с портом', () => {
const result = service.buildHysteria2Link(
'192.168.1.1',
'ya.ru',
'%F0%9F%92%AF%20hysteria2',
443,
);
expect(result).toContain(':443');
});
});
describe('buildHysteria2Inbound', () => {
it('creates 3x-ui hysteria v2 inbound with certificate paths', () => {
const result = service.buildHysteria2Inbound({
port: 34443,
uuid: 'test-auth',
sni: 'oil.3dp-manager.com',
});
expect(result).toMatchObject({
enable: true,
listen: '0.0.0.0',
port: 34443,
protocol: 'hysteria',
tag: 'inbound-34443',
});
const settings = JSON.parse(result.settings);
const streamSettings = JSON.parse(result.streamSettings);
expect(settings.clients[0].auth).toBe('test-auth');
expect(settings.version).toBe(2);
expect(streamSettings.network).toBe('hysteria');
expect(streamSettings.hysteriaSettings.version).toBe(2);
expect(streamSettings.finalmask.udp[0].type).toBe('salamander');
expect(streamSettings.tlsSettings.certificates[0].certificateFile).toBe(
'/etc/letsencrypt/live/oil.3dp-manager.com/fullchain.pem',
);
expect(streamSettings.tlsSettings.certificates[0].keyFile).toBe(
'/etc/letsencrypt/live/oil.3dp-manager.com/privkey.pem',
);
const link = service.buildInboundLink(
result as any,
'relay.example.com',
'fallback-auth',
'%F0%9F%92%AF',
);
expect(link).toContain('hy2://test-auth@relay.example.com:34443/');
expect(link).toContain('sni=oil.3dp-manager.com');
expect(link).toContain('obfs=salamander');
expect(link).toContain('obfs-password=abcd1234');
});
});
describe('buildInboundLink', () => {
const baseInbound = {
protocol: 'vless',
port: 443,
remark: 'vless-tcp-reality',
settings: JSON.stringify({
clients: [{ id: 'test-uuid', flow: 'xtls-rprx-vision' }],
}),
streamSettings: JSON.stringify({
network: 'tcp',
security: 'reality',
realitySettings: {
serverNames: ['ya.ru'],
publicKey: 'public-key',
},
}),
};
it('должен создать ссылку vless reality', () => {
const result = service.buildInboundLink(
baseInbound as any,
'192.168.1.1',
'test-uuid',
'%F0%9F%92%AF',
);
expect(result).toContain('vless://');
expect(result).toContain('192.168.1.1');
expect(result).toContain('443');
});
it('должен создать ссылку vless reality с xhttp', () => {
const inbound = {
protocol: 'vless',
port: 8443,
remark: 'vless-xhttp-reality',
settings: JSON.stringify({
clients: [{ id: 'test-uuid', flow: '' }],
}),
streamSettings: JSON.stringify({
network: 'xhttp',
security: 'reality',
realitySettings: {
serverNames: ['ya.ru'],
publicKey: 'public-key',
settings: { publicKey: 'pk', fingerprint: 'random' },
shortIds: ['abc123'],
},
xhttpSettings: {
path: '/path',
host: 'ya.ru',
mode: 'auto',
},
}),
};
const result = service.buildInboundLink(
inbound as any,
'192.168.1.1',
'test-uuid',
'%F0%9F%92%AF',
);
expect(result).toContain('vless://');
expect(result).toContain('type=xhttp');
expect(result).toContain('path=%2Fpath');
});
it('должен создать ссылку vless reality с grpc', () => {
const inbound = {
protocol: 'vless',
port: 8443,
remark: 'vless-grpc-reality',
settings: JSON.stringify({
clients: [{ id: 'test-uuid', flow: '' }],
}),
streamSettings: JSON.stringify({
network: 'grpc',
security: 'reality',
realitySettings: {
serverNames: ['ya.ru'],
publicKey: 'public-key',
settings: { publicKey: 'pk', fingerprint: 'random' },
shortIds: ['abc123'],
},
grpcSettings: {
serviceName: 'grpc-service',
authority: 'ya.ru',
},
}),
};
const result = service.buildInboundLink(
inbound as any,
'192.168.1.1',
'test-uuid',
'%F0%9F%92%AF',
);
expect(result).toContain('vless://');
expect(result).toContain('type=grpc');
expect(result).toContain('serviceName=grpc-service');
});
it('должен создать ссылку vless с ws', () => {
const inbound = {
protocol: 'vless',
port: 443,
remark: 'vless-ws',
settings: JSON.stringify({
clients: [{ id: 'test-uuid' }],
}),
streamSettings: JSON.stringify({
network: 'ws',
security: 'tls',
wsSettings: {
path: '/ws',
headers: { Host: 'example.com' },
},
}),
};
const result = service.buildInboundLink(
inbound as any,
'192.168.1.1',
'test-uuid',
'%F0%9F%92%AF',
);
expect(result).toContain('vless://');
expect(result).toContain('type=ws');
expect(result).toContain('path=%2Fws');
});
it('должен создать ссылку vmess', () => {
const vmessInbound = {
protocol: 'vmess',
port: 20000,
remark: 'vmess-tcp',
settings: JSON.stringify({
clients: [{ id: 'test-uuid' }],
}),
streamSettings: JSON.stringify({
network: 'tcp',
}),
};
const result = service.buildInboundLink(
vmessInbound as any,
'192.168.1.1',
'test-uuid',
'%F0%9F%92%AF',
);
expect(result).toContain('vmess://');
});
it('должен создать ссылку shadowsocks', () => {
const ssInbound = {
protocol: 'shadowsocks',
port: 30000,
remark: 'shadowsocks-tcp',
settings: JSON.stringify({
method: '2022-blake3-aes-256-gcm',
password: 'test-password',
clients: [{ password: 'client-pass' }],
}),
streamSettings: JSON.stringify({
network: 'tcp',
}),
};
const result = service.buildInboundLink(
ssInbound as any,
'192.168.1.1',
'',
'%F0%9F%92%AF',
);
expect(result).toContain('ss://');
});
it('должен создать ссылку trojan', () => {
const trojanInbound = {
protocol: 'trojan',
port: 443,
remark: 'trojan-reality',
settings: JSON.stringify({
clients: [{ password: 'trojan-pass' }],
}),
streamSettings: JSON.stringify({
network: 'tcp',
security: 'reality',
realitySettings: {
serverNames: ['ya.ru'],
publicKey: 'public-key',
settings: { publicKey: 'pk', fingerprint: 'random' },
shortIds: ['abc123'],
},
}),
};
const result = service.buildInboundLink(
trojanInbound as any,
'192.168.1.1',
'trojan-pass',
'%F0%9F%92%AF',
);
expect(result).toContain('trojan://');
expect(result).toContain('security=reality');
});
it('должен вернуть пустую строку для trojan без reality', () => {
const trojanInbound = {
protocol: 'trojan',
port: 443,
remark: 'trojan',
settings: JSON.stringify({
clients: [{ password: 'pass' }],
}),
streamSettings: JSON.stringify({
network: 'tcp',
security: 'none',
}),
};
const result = service.buildInboundLink(
trojanInbound as any,
'192.168.1.1',
'pass',
'%F0%9F%92%AF',
);
expect(result).toBe('');
});
it('должен вернуть пустую строку для vless без reality settings', () => {
const vlessInbound = {
protocol: 'vless',
port: 443,
remark: 'vless',
settings: JSON.stringify({
clients: [{ id: 'uuid' }],
}),
streamSettings: JSON.stringify({
network: 'tcp',
security: 'reality',
realitySettings: null,
}),
};
const result = service.buildInboundLink(
vlessInbound as any,
'192.168.1.1',
'uuid',
'%F0%9F%92%AF',
);
expect(result).toBe('');
});
});
describe('generateUuid', () => {
it('должен сгенерировать UUID', () => {
const uuid = service.generateUuid();
expect(uuid).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
);
});
});
});