Files
3X-UI_autoVPS/tools/test/links.test.js
T
PinkmanXXX aebfb15780 Генераторы конфигов Xray и Mihomo для XKeen на GitHub Pages
- tools/xray: 04_outbounds.json и 05_routing.json — серверы, выбор сервисов
  по геобазам XKeen (ZKeen, V2Fly, Re:filter), реклама, QUIC, свои сайты,
  балансировщик для нескольких серверов
- tools/mihomo: config.yaml с портами XKeen, подпиской, Hysteria2,
  автовыбором сервера и панелью zashboard
- Обе страницы выдают команду, которая пишет файлы на роутер и перезапускает XKeen
- Всё считается в браузере, ссылки не сохраняются и никуда не отправляются
- Корень сайта перенаправляет на репозиторий

Проверено: модульные тесты, xray -test и mihomo -t на всех сценариях,
команды — в busybox ash, живое подключение через REALITY и Hysteria2.
2026-09-25 22:50:16 +03:00

107 lines
5.6 KiB
JavaScript
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.
// Тесты разбора ссылок и сборки конфигов: node --test tools/test/
const test = require('node:test');
const assert = require('node:assert/strict');
const { parseLink, parseText } = require('../lib/links.js');
const { buildXray } = require('../lib/xray.js');
const { buildMihomo } = require('../lib/mihomo.js');
const REALITY = 'vless://5f2c1e7a-0b3d-4c6e-9a8f-1234567890ab@203.0.113.10:443?type=tcp&security=reality&pbk=Fj98Liz-8fJCAaK_hcsGDJRSd9ztkw0TDR2v4kqK7TA&fp=chrome&sni=www.microsoft.com&sid=2831cdcf4a278ac2&spx=%2F&flow=xtls-rprx-vision#%D0%93%D0%B5%D1%80%D0%BC%D0%B0%D0%BD%D0%B8%D1%8F';
const WS_TLS = 'vless://11111111-2222-3333-4444-555555555555@cdn.example.com:443?type=ws&security=tls&path=%2Fws%3Fed%3D2048&host=cdn.example.com&sni=cdn.example.com&alpn=h2%2Chttp%2F1.1#ws';
const XHTTP = 'vless://11111111-2222-3333-4444-555555555555@x.example.com:443?type=xhttp&security=reality&pbk=abc&sid=01&sni=www.apple.com&path=%2Fx&mode=packet-up#xhttp';
const GRPC = 'trojan://p%40ss@t.example.com:443?type=grpc&serviceName=gun&security=tls&sni=t.example.com#grpc';
const VMESS = 'vmess://' + Buffer.from(JSON.stringify({ v: '2', ps: 'vm', add: 'v.example.com', port: '8443', id: '11111111-2222-3333-4444-555555555555', aid: '0', scy: 'auto', net: 'ws', host: 'v.example.com', path: '/vm', tls: 'tls', sni: 'v.example.com' })).toString('base64');
const SS_B64 = 'ss://' + Buffer.from('chacha20-ietf-poly1305:secret').toString('base64url') + '@198.51.100.7:8388#ss';
const SS_2022 = 'ss://2022-blake3-aes-128-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@198.51.100.8:443#ss22';
const SS_OLD = 'ss://' + Buffer.from('aes-256-gcm:pw@198.51.100.9:1234').toString('base64') + '#old';
const HY2 = 'hy2://admin:9c1e5b@hy.example.com:443/?sni=hy.example.com&insecure=1&pinSHA256=AB:CD:EF&obfs=salamander&obfs-password=x#hy';
test('REALITY', () => {
const p = parseLink(REALITY);
assert.equal(p.type, 'vless');
assert.equal(p.name, 'Германия');
assert.equal(p.tls.security, 'reality');
assert.equal(p.tls.pbk, 'Fj98Liz-8fJCAaK_hcsGDJRSd9ztkw0TDR2v4kqK7TA');
assert.equal(p.tls.spx, '/');
assert.equal(p.flow, 'xtls-rprx-vision');
});
test('WS + TLS с путём и ALPN', () => {
const p = parseLink(WS_TLS);
assert.equal(p.transport.path, '/ws?ed=2048');
assert.deepEqual(p.tls.alpn, ['h2', 'http/1.1']);
});
test('xhttp, gRPC trojan, vmess', () => {
assert.equal(parseLink(XHTTP).transport.mode, 'packet-up');
const g = parseLink(GRPC);
assert.equal(g.password, 'p@ss');
assert.equal(g.transport.serviceName, 'gun');
const v = parseLink(VMESS);
assert.equal(v.port, 8443);
assert.equal(v.transport.path, '/vm');
});
test('Shadowsocks во всех трёх форматах', () => {
assert.equal(parseLink(SS_B64).method, 'chacha20-ietf-poly1305');
assert.equal(parseLink(SS_2022).password, 'YctPZ6U7xPPcU+gp3u+0tx/tRizJN9K8y+uKlW2qjlI=');
const o = parseLink(SS_OLD);
assert.equal(o.server, '198.51.100.9');
assert.equal(o.port, 1234);
});
test('Hysteria2: логин:пароль и отпечаток', () => {
const h = parseLink(HY2);
assert.equal(h.password, 'admin:9c1e5b');
assert.equal(h.pinSHA256, 'abcdef');
assert.equal(h.obfs, 'salamander');
});
test('подписка в base64 и ошибки по строкам', () => {
const sub = Buffer.from([REALITY, 'garbage', 'tuic://x@y:1', HY2].join('\n')).toString('base64');
const r = parseText(sub);
assert.equal(r.proxies.length, 2);
assert.equal(r.errors.length, 2);
assert.match(r.errors[1].error, /tuic/);
});
test('одинаковые имена становятся уникальными', () => {
const r = parseText(REALITY + '\n' + REALITY);
assert.deepEqual(r.proxies.map((p) => p.name), ['Германия', 'Германия 2']);
});
test('битые ссылки дают понятную ошибку', () => {
assert.throws(() => parseLink('vless://@h:443'), /UUID/);
assert.throws(() => parseLink('vless://id@h:443?security=reality'), /pbk/);
assert.throws(() => parseLink('hy2://pw@h:443-500'), /диапазон/);
assert.throws(() => parseLink('vless://id@h:99999'), /порт|Invalid/);
});
test('Xray: теги, балансировщик и маршрутизация', () => {
const one = buildXray(parseText(REALITY).proxies, {});
assert.equal(one.files['04_outbounds.json'].outbounds[0].tag, 'vless-reality');
assert.equal(one.files['04_outbounds.json'].outbounds[0].streamSettings.realitySettings.publicKey, 'Fj98Liz-8fJCAaK_hcsGDJRSd9ztkw0TDR2v4kqK7TA');
const rules = one.files['05_routing.json'].routing.rules;
assert.equal(rules[rules.length - 1].outboundTag, 'direct');
const multi = buildXray(parseText([REALITY, WS_TLS, HY2].join('\n')).proxies, {});
assert.equal(multi.count, 2);
assert.equal(multi.skipped.length, 1);
assert.ok(multi.files['05_routing.json'].routing.balancers);
});
test('Xray: сервисы без нужной геобазы не попадают в правила', () => {
const r = buildXray(parseText(REALITY).proxies, { bases: ['zkeen'], services: ['twitter', 'youtube'] });
const s = JSON.stringify(r.files['05_routing.json']);
assert.ok(!s.includes('twitter'));
assert.ok(s.includes('ext:zkeen.dat:youtube'));
});
test('Mihomo: порты XKeen и прокси', () => {
const r = buildMihomo(parseText([REALITY, HY2, SS_2022].join('\n')).proxies, { secret: 'x' });
assert.equal(r.config['redir-port'], 5000);
assert.equal(r.config['tproxy-port'], 5001);
assert.equal(r.config['routing-mark'], 255);
assert.equal(r.config.proxies.length, 3);
assert.equal(r.config.proxies[1].fingerprint, 'abcdef');
assert.match(r.yaml, /name: "Германия"/);
});