Files
2026-05-27 00:57:55 +08:00

213 lines
7.6 KiB
Python

from __future__ import annotations
import base64
import json
from pyxray.libs.nodes import import_node_links, parse_node_link
def test_parse_vless_reality_link_normalizes_node() -> None:
node = parse_node_link(
"vless://00000000-0000-4000-8000-000000000001@example.com:443"
"?type=tcp&security=reality&encryption=none&flow=xtls-rprx-vision"
"&sni=www.example.com&fp=chrome&pbk=public-key&sid=abcd&spx=%2F#hk"
)
assert node.protocol == "vless"
assert node.name == "hk"
assert node.server == "example.com"
assert node.port == 443
assert node.settings["uuid"] == "00000000-0000-4000-8000-000000000001"
assert node.settings["flow"] == "xtls-rprx-vision"
assert node.transport == {
"network": "tcp",
"header_type": "none",
"host": "",
"path": "",
}
assert node.security["type"] == "reality"
assert node.security["server_name"] == "www.example.com"
assert node.security["public_key"] == "public-key"
assert node.security["short_id"] == "abcd"
assert node.fingerprint
assert node.id.startswith("vless_")
assert node.canonical_link.startswith("vless://")
def test_parse_trojan_ws_tls_link_normalizes_node() -> None:
node = parse_node_link(
"trojan://secret@example.org:443"
"?type=ws&host=cdn.example.org&path=%2Fws&sni=edge.example.org&allowInsecure=1#trojan"
)
assert node.protocol == "trojan"
assert node.name == "trojan"
assert node.server == "example.org"
assert node.port == 443
assert node.settings["password"] == "secret"
assert node.transport["network"] == "ws"
assert node.transport["path"] == "/ws"
assert node.transport["host"] == "cdn.example.org"
assert node.security["type"] == "tls"
assert node.security["server_name"] == "edge.example.org"
assert node.security["allow_insecure"] is True
def test_parse_vmess_base64_json_link_normalizes_node() -> None:
payload = {
"v": "2",
"ps": "vmess-node",
"add": "vmess.example.net",
"port": "443",
"id": "00000000-0000-4000-8000-000000000002",
"aid": "0",
"scy": "auto",
"net": "websocket",
"type": "none",
"host": "cdn.example.net",
"path": "/ray",
"tls": "tls",
"sni": "sni.example.net",
}
encoded = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
node = parse_node_link(f"vmess://{encoded}")
assert node.protocol == "vmess"
assert node.name == "vmess-node"
assert node.server == "vmess.example.net"
assert node.port == 443
assert node.settings["uuid"] == "00000000-0000-4000-8000-000000000002"
assert node.transport["network"] == "ws"
assert node.security["type"] == "tls"
def test_parse_shadowsocks_sip002_link_normalizes_node() -> None:
user = base64.urlsafe_b64encode(b"chacha20-ietf-poly1305:secret").decode().rstrip("=")
node = parse_node_link(f"ss://{user}@ss.example.net:8388#ss-node")
assert node.protocol == "shadowsocks"
assert node.name == "ss-node"
assert node.server == "ss.example.net"
assert node.port == 8388
assert node.settings["method"] == "chacha20-ietf-poly1305"
assert node.settings["password"] == "secret"
assert node.security["type"] == "none"
def test_import_node_links_parses_multiline_input_without_subscription_groups() -> None:
user = base64.urlsafe_b64encode(b"aes-128-gcm:secret").decode().rstrip("=")
results = import_node_links(
"\n"
"# comment\n"
"bad://example\n"
f"ss://{user}@ss.example.net:8388#ss-node\n"
)
assert len(results) == 2
assert results[0].ok is False
assert results[0].error == "unsupported node link scheme: bad"
assert results[1].ok is True
assert results[1].node is not None
assert results[1].node.protocol == "shadowsocks"
def test_parse_trojan_go_keeps_protocol_and_specific_fields() -> None:
node = parse_node_link(
"trojan-go://secret@example.org:443"
"?type=ws&host=cdn.example.org&path=%2Fgo&sni=edge.example.org"
"&encryption=ss%3Baes-128-gcm%3Asecret&allowInsecure=1#tg"
)
assert node.protocol == "trojan-go"
assert node.settings["password"] == "secret"
assert node.settings["encryption"] == "ss;aes-128-gcm:secret"
assert node.transport["network"] == "ws"
assert node.transport["host"] == "cdn.example.org"
assert node.security["allow_insecure"] is False
assert node.canonical_link.startswith("trojan-go://")
def test_parse_vless_xhttp_ws_and_grpc_extension_fields() -> None:
xhttp = parse_node_link(
"vless://00000000-0000-4000-8000-000000000001@example.com:443"
"?type=xhttp&security=tls&sni=www.example.com#xhttp"
)
ws = parse_node_link(
"vless://00000000-0000-4000-8000-000000000001@example.com:443"
"?type=ws&security=tls&maxEarlyData=2048&earlyDataHeaderName=Sec-WebSocket-Protocol#ws"
)
grpc = parse_node_link(
"vless://00000000-0000-4000-8000-000000000001@example.com:443"
"?type=grpc&security=tls&serviceName=svc&multiMode=true&idleTimeout=60"
"&healthCheckTimeout=20&permitWithoutStream=true&initialWindowsSize=65535#grpc"
)
assert xhttp.transport["network"] == "xhttp"
assert xhttp.transport["xhttp_mode"] == "auto"
assert ws.transport["max_early_data"] == "2048"
assert ws.transport["early_data_header_name"] == "Sec-WebSocket-Protocol"
assert grpc.transport["service_name"] == "svc"
assert grpc.transport["multi_mode"] == "true"
assert grpc.transport["idle_timeout"] == "60"
assert grpc.transport["health_check_timeout"] == "20"
assert grpc.transport["permit_without_stream"] == "true"
assert grpc.transport["initial_windows_size"] == "65535"
def test_parse_vmess_legacy_link() -> None:
payload = base64.urlsafe_b64encode(b"auto:00000000-0000-4000-8000-000000000003@legacy.example:443").decode()
node = parse_node_link(
f"vmess://{payload}?remarks=legacy&obfs=websocket&obfsParam=cdn.example"
"&path=%2Fray&alterId=0&tls=1&sni=sni.example"
)
assert node.protocol == "vmess"
assert node.name == "legacy"
assert node.server == "legacy.example"
assert node.port == 443
assert node.settings["uuid"] == "00000000-0000-4000-8000-000000000003"
assert node.transport["network"] == "ws"
assert node.transport["host"] == "cdn.example"
assert node.transport["path"] == "/ray"
assert node.security["type"] == "tls"
assert node.security["server_name"] == "sni.example"
def test_parse_vmess_moves_path_from_host_when_needed() -> None:
payload = {
"ps": "host-path",
"add": "vmess.example.net",
"port": "443",
"id": "00000000-0000-4000-8000-000000000004",
"host": "/ray",
"net": "ws",
}
encoded = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
node = parse_node_link(f"vmess://{encoded}")
assert node.transport["host"] == ""
assert node.transport["path"] == "/ray"
def test_parse_shadowsocks_plugin_as_structured_sip003_options() -> None:
user = base64.urlsafe_b64encode(b"chacha20-ietf-poly1305:secret").decode().rstrip("=")
node = parse_node_link(
f"ss://{user}@ss.example.net:8388?plugin=simpleobfs%3Bobfs%3Dhttp%3Bobfs-host%3Dcdn.example"
"%3Bobfs-uri%3Dws#ss-plugin"
)
assert node.settings["plugin_options"] == {
"name": "simple-obfs",
"raw": "simpleobfs;obfs=http;obfs-host=cdn.example;obfs-uri=ws",
"tls": "",
"obfs": "http",
"host": "cdn.example",
"path": "/ws",
"impl": "",
}