mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 14:12:51 +08:00
130 lines
7.0 KiB
Lua
130 lines
7.0 KiB
Lua
-- ssr_test.lua — SSR(ShadowsocksR)解析与输出单元测试
|
||
-- 用法:lua5.1 tests/ssr_test.lua
|
||
|
||
package.path = "./root/usr/share/?.lua;" .. package.path
|
||
|
||
local util = require("substore.util")
|
||
local parser = require("substore.parser")
|
||
local output = require("substore.output")
|
||
local output_uri = require("substore.output_uri")
|
||
|
||
local passed, failed = 0, 0
|
||
|
||
local function check(name, cond)
|
||
if cond then
|
||
passed = passed + 1
|
||
print("PASS " .. name)
|
||
else
|
||
failed = failed + 1
|
||
print("FAIL " .. name)
|
||
end
|
||
end
|
||
|
||
-- 独立构造的已知 SSR 分享链接(由 Python base64 生成,非本实现生成,避免循环验证):
|
||
-- server=127.0.0.1 port=8388 protocol=auth_aes128_md5 method=aes-128-cfb obfs=http_simple
|
||
-- password=test-password obfsparam=download.windowsupdate.com remarks=测试节点 group=测试组
|
||
local link = "ssr://MTI3LjAuMC4xOjgzODg6YXV0aF9hZXMxMjhfbWQ1OmFlcy0xMjgtY2ZiOmh0dHBfc2ltcGxlOmRHVnpkQzF3WVhOemQyOXlaQT09Lz9vYmZzcGFyYW09Wkc5M2JteHZZV1F1ZDJsdVpHOTNjM1Z3WkdGMFpTNWpiMjAmcmVtYXJrcz01cldMNkstVjZJcUM1NEs1Jmdyb3VwPTVyV0w2Sy1WNTd1RQ=="
|
||
|
||
check("detect ssr = uri", parser.detect(link) == "uri")
|
||
local n = parser.parse_uri(link)
|
||
check("ssr parses", n ~= nil)
|
||
check("ssr proto", n and n.proto == "ssr")
|
||
check("ssr server", n and n.server == "127.0.0.1")
|
||
check("ssr port", n and n.port == 8388)
|
||
check("ssr protocol", n and n.protocol == "auth_aes128_md5")
|
||
check("ssr method", n and n.method == "aes-128-cfb")
|
||
check("ssr obfs", n and n.obfs == "http_simple")
|
||
check("ssr password", n and n.password == "test-password")
|
||
check("ssr obfs_param", n and n.obfs_param == "download.windowsupdate.com")
|
||
check("ssr name", n and n.name == "测试节点")
|
||
check("ssr group", n and n.group == "测试组")
|
||
|
||
-- ---------- 订阅解析(URI 列表含 ssr) ----------
|
||
local res = parser.parse(link .. "\nvless://uuid1@1.1.1.1:443#V")
|
||
check("parse ssr sub count", res and res.nodes and #res.nodes == 2)
|
||
check("parse ssr sub proto", res and res.nodes[1] and res.nodes[1].proto == "ssr")
|
||
|
||
-- ---------- ssr:// 分享链接回环 ----------
|
||
local re = output_uri.to_ssr_uri(n)
|
||
check("to_ssr_uri returns link", re and re:sub(1, 6) == "ssr://")
|
||
local n2 = parser.parse_uri(re)
|
||
check("ssr roundtrip proto", n2 and n2.proto == "ssr")
|
||
check("ssr roundtrip server", n2 and n2.server == "127.0.0.1")
|
||
check("ssr roundtrip port", n2 and n2.port == 8388)
|
||
check("ssr roundtrip method", n2 and n2.method == "aes-128-cfb")
|
||
check("ssr roundtrip password", n2 and n2.password == "test-password")
|
||
check("ssr roundtrip protocol", n2 and n2.protocol == "auth_aes128_md5")
|
||
check("ssr roundtrip obfs", n2 and n2.obfs == "http_simple")
|
||
check("ssr roundtrip obfs_param", n2 and n2.obfs_param == "download.windowsupdate.com")
|
||
check("ssr roundtrip name", n2 and n2.name == "测试节点")
|
||
check("ssr roundtrip group", n2 and n2.group == "测试组")
|
||
|
||
-- ---------- Clash.Meta / Mihomo 输出 ----------
|
||
local cm = output.generate({ n }, "clashmeta")
|
||
check("clashmeta type ssr", cm and cm:find("type: ssr") ~= nil)
|
||
check("clashmeta cipher", cm and cm:find("cipher: aes%-128%-cfb") ~= nil)
|
||
check("clashmeta password", cm and cm:find("password: test%-password") ~= nil)
|
||
check("clashmeta protocol", cm and cm:find("protocol: auth_aes128_md5") ~= nil)
|
||
check("clashmeta obfs", cm and cm:find("obfs: http_simple") ~= nil)
|
||
check("clashmeta obfs-param", cm and cm:find("obfs%-param: download%.windowsupdate%.com") ~= nil)
|
||
|
||
-- ---------- Shadowrocket(base64 URI)输出包含 ssr:// ----------
|
||
local shr = output.generate({ n }, "shadowrocket")
|
||
check("shadowrocket has ssr", shr and util.base64_decode(shr):find("ssr://") ~= nil)
|
||
|
||
-- ---------- Loon 保留 SSR,Surge 丢弃 ----------
|
||
local loon = output.generate({ n }, "loon")
|
||
check("loon has ssr", loon and loon:find("ssr, 127%.0%.0%.1, 8388") ~= nil)
|
||
-- Loon 把加密方式与密码写成端口之后的**位置参数**(密码带双引号),不是具名参数:
|
||
-- `ShadowsocksR,服务器,端口,加密方式,"密码",protocol=…,obfs=…`(nsloon.app/docs/Node/)
|
||
-- 具名的 encrypt-method= / password= 在 Loon 的语法里会各自占掉一个位置参数,
|
||
-- 加密方式与密码都解析不出来。
|
||
check("loon cipher positional", loon and loon:find('ssr, 127%.0%.0%.1, 8388, aes%-128%-cfb, "test%-password"') ~= nil)
|
||
check("loon no named encrypt-method", loon and loon:find("encrypt%-method=") == nil)
|
||
check("loon protocol", loon and loon:find("protocol=auth_aes128_md5") ~= nil)
|
||
|
||
-- ---------- Loon 输出 → 导入回环(位置参数写法) ----------
|
||
-- 生成端与解析端必须成对:只改生成端的话,导出的 Loon 配置再导入回来会丢掉
|
||
-- 加密方式与密码(parser_surge 此前对 ssr 根本没有取字段的分支)。
|
||
local loon_parsed = require("substore.parser_surge").parse(loon)
|
||
check("loon roundtrip count", #loon_parsed == 1)
|
||
local lr = loon_parsed[1]
|
||
check("loon roundtrip proto", lr and lr.proto == "ssr")
|
||
check("loon roundtrip method", lr and lr.method == "aes-128-cfb")
|
||
check("loon roundtrip password", lr and lr.password == "test-password")
|
||
check("loon roundtrip protocol", lr and lr.protocol == "auth_aes128_md5")
|
||
check("loon roundtrip obfs", lr and lr.obfs == "http_simple")
|
||
check("loon roundtrip obfs_param", lr and (lr.obfs_param == "download.windowsupdate.com"
|
||
or lr["obfs-param"] == "download.windowsupdate.com"))
|
||
check("loon roundtrip name", lr and lr.name == "测试节点")
|
||
local surge = output.generate({ n }, "surge")
|
||
check("surge drops ssr", surge and surge:find("127%.0%.0%.1") == nil)
|
||
|
||
-- ---------- sing-box / V2Ray 丢弃 SSR ----------
|
||
-- 完整配置里 outbounds 恒含 direct / block,因此断言「不含 ssr 出站」
|
||
-- 而非「outbounds 为空」;无可用节点时也不生成分组
|
||
local sb = output.generate({ n }, "singbox")
|
||
local sbd = util.json_decode(sb)
|
||
local sb_types = {}
|
||
for _, o in ipairs(sbd and sbd.outbounds or {}) do sb_types[o.type] = true end
|
||
check("singbox drops ssr", sbd and not sb_types.ssr and not sb_types.shadowsocksr)
|
||
check("singbox no group without nodes", sbd and not sb_types.selector and not sb_types.urltest)
|
||
check("singbox keeps direct/block", sb_types.direct and sb_types.block)
|
||
local v2 = output.generate({ n }, "v2ray")
|
||
local v2d = util.json_decode(v2)
|
||
local v2_protos = {}
|
||
for _, o in ipairs(v2d and v2d.outbounds or {}) do v2_protos[o.protocol] = true end
|
||
check("v2ray drops ssr", v2d and not v2_protos.ssr and not v2_protos.shadowsocksr)
|
||
check("v2ray keeps freedom/blackhole", v2_protos.freedom and v2_protos.blackhole)
|
||
|
||
-- ---------- 最小 ssr(无参数,默认 origin/plain) ----------
|
||
local minimal = parser.parse_uri("ssr://" .. util.base64_encode("1.2.3.4:80:origin:aes-256-cfb:plain:" .. util.base64_encode("p")))
|
||
check("ssr minimal", minimal and minimal.proto == "ssr" and minimal.server == "1.2.3.4" and minimal.password == "p")
|
||
|
||
-- ---------- base64url 参数解码回退 ----------
|
||
local b64u = util.base64_url_encode("香港节点")
|
||
check("base64url bare", b64u == "6aaZ5riv6IqC54K5")
|
||
check("base64url roundtrip", util.base64_url_decode(b64u) == "香港节点")
|
||
|
||
print(string.format("\n%d passed, %d failed", passed, failed))
|
||
os.exit(failed == 0 and 0 or 1) |