Files
Sanaei f727d04f65
CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

138 lines
4.2 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
parseLink,
detectProtocol,
buildVless,
buildVmess,
buildTrojan,
buildShadowsocks,
} from './links';
describe('detectProtocol', () => {
it('recognises supported schemes', () => {
expect(detectProtocol('vless://x')).toBe('vless');
expect(detectProtocol('vmess://x')).toBe('vmess');
expect(detectProtocol('trojan://x')).toBe('trojan');
expect(detectProtocol('ss://x')).toBe('ss');
expect(detectProtocol('https://x')).toBeNull();
});
});
describe('parseLink — vless', () => {
const link =
'vless://11111111-2222-3333-4444-555555555555@example.com:443?security=reality&pbk=abc&sid=ff&sni=www.microsoft.com&fp=chrome&flow=xtls-rprx-vision&type=tcp#My%20Node';
it('extracts address, port, credential, name, and params', () => {
const r = parseLink(link);
expect(r.protocol).toBe('vless');
expect(r.address).toBe('example.com');
expect(r.port).toBe(443);
expect(r.credential).toBe('11111111-2222-3333-4444-555555555555');
expect(r.name).toBe('My Node');
expect(r.params.security).toBe('reality');
expect(r.params.pbk).toBe('abc');
expect(r.params.flow).toBe('xtls-rprx-vision');
});
});
describe('parseLink — trojan', () => {
it('parses password and query params', () => {
const r = parseLink('trojan://p%40ss@1.2.3.4:8443?sni=a.com&type=ws&path=%2Fws#t');
expect(r.protocol).toBe('trojan');
expect(r.credential).toBe('p@ss');
expect(r.address).toBe('1.2.3.4');
expect(r.port).toBe(8443);
expect(r.params.path).toBe('/ws');
expect(r.name).toBe('t');
});
});
describe('parseLink — vmess', () => {
it('decodes the base64 JSON payload', () => {
const link = buildVmess({
ps: 'tehran',
add: 'host.example',
port: 443,
id: 'uuid-123',
net: 'ws',
tls: 'tls',
host: 'cdn.example',
path: '/v2',
});
const r = parseLink(link);
expect(r.protocol).toBe('vmess');
expect(r.name).toBe('tehran');
expect(r.address).toBe('host.example');
expect(r.port).toBe(443);
expect(r.credential).toBe('uuid-123');
expect(r.params.net).toBe('ws');
expect(r.params.path).toBe('/v2');
});
});
describe('parseLink — shadowsocks', () => {
it('parses SIP002 with base64 userinfo', () => {
const link = buildShadowsocks({
method: 'aes-256-gcm',
password: 's3cret',
address: '192.0.2.1',
port: 8388,
name: 'ss-node',
});
const r = parseLink(link);
expect(r.protocol).toBe('ss');
expect(r.credential).toBe('aes-256-gcm:s3cret');
expect(r.address).toBe('192.0.2.1');
expect(r.port).toBe(8388);
expect(r.name).toBe('ss-node');
});
it('parses the legacy fully-base64 form', () => {
// base64("aes-128-gcm:pw@example.com:8388")
const legacy =
'ss://' + Buffer.from('aes-128-gcm:pw@example.com:8388').toString('base64') + '#legacy';
const r = parseLink(legacy);
expect(r.credential).toBe('aes-128-gcm:pw');
expect(r.address).toBe('example.com');
expect(r.port).toBe(8388);
expect(r.name).toBe('legacy');
});
});
describe('build → parse round-trips', () => {
it('vless round-trips', () => {
const link = buildVless({
credential: 'uuid-abc',
address: 'example.com',
port: 443,
params: { security: 'reality', sni: 'a.com', flow: 'xtls-rprx-vision' },
name: 'node 1',
});
const r = parseLink(link);
expect(r.credential).toBe('uuid-abc');
expect(r.address).toBe('example.com');
expect(r.port).toBe(443);
expect(r.params.sni).toBe('a.com');
expect(r.name).toBe('node 1');
});
it('trojan round-trips', () => {
const r = parseLink(buildTrojan({ credential: 'pw', address: 'h.com', port: 443, name: 'x' }));
expect(r.credential).toBe('pw');
expect(r.port).toBe(443);
});
it('ss round-trips', () => {
const r = parseLink(
buildShadowsocks({ method: 'chacha20-poly1305', password: 'p', address: 'h', port: 8388 }),
);
expect(r.credential).toBe('chacha20-poly1305:p');
});
});
describe('parseLink — errors', () => {
it('throws on unsupported scheme', () => {
expect(() => parseLink('http://example.com')).toThrow(/Unsupported/);
});
});