Files
op-packages/luci-app-substore/tests/parser_mixed_format_test.lua
T

173 lines
7.3 KiB
Lua
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.
-- parser_mixed_format_test.lua — 混合格式文本导入的显式报错(M11 / A3)
-- 用法:lua5.1 tests/parser_mixed_format_test.lua
--
-- 背景:detect() 只返回优先级最高的**一种**格式,其余部分被静默丢弃,
-- 而 parse 返回的是合法表 —— 同步报成功,用户以为整份都导进来了。
-- 实测(2.6.15-r1):
-- 「URI + WG conf」→ detect=wireguard-conf,只得到 WG 节点,err=nil
-- 「URI + JSON」 → detect=uri,只得到 URI 节点,err=nil
--
-- 修复:新增 detect_all()(整份文档级标记 + 行首锚定),
-- parse_local 文本模式在命中 >1 种格式时**明确报错**。
--
-- 本文件覆盖三类:
-- A 单格式内容一律不得被误判(误判会把正常配置挡在门外,比漏报更糟)
-- B 混合内容必须报错,且错误信息点名命中的格式
-- C 作用域:只影响本地文本导入,远程订阅(M.parse)行为不变
package.path = "./root/usr/share/?.lua;" .. package.path
local parser = require("substore.parser")
local util = require("substore.util")
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
local function vmess_uri(name)
return "vmess://" .. util.base64_encode(util.json_encode({
v = "2", ps = name, add = "1.2.3.4", port = "443",
id = "11111111-2222-3333-4444-555555555555",
aid = "0", net = "ws", type = "none", host = "", path = "/", tls = "",
}))
end
local URI = vmess_uri("U-01")
local VLESS = "vless://11111111-2222-3333-4444-555555555555@1.2.3.4:443?encryption=none&type=ws#V-01"
local WG_CONF = [[
[Interface]
PrivateKey = aGVsbG93b3JsZHByaXZhdGVrZXkwMDAwMDAwMDAwMDA=
Address = 10.7.0.2/32
DNS = 1.1.1.1
[Peer]
PublicKey = cGVlcnB1YmxpY2tleTAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=
AllowedIPs = 0.0.0.0/0
Endpoint = wg.example.com:51820
]]
local JSON_OBJ = util.json_encode({
outbounds = { { type = "vmess", server = "1.2.3.4", server_port = 443,
uuid = "11111111-2222-3333-4444-555555555555" } },
})
local JSON_ARRAY = util.json_encode({
{ type = "vmess", server = "1.2.3.4", port = 443,
uuid = "11111111-2222-3333-4444-555555555555" },
})
local CLASH_YAML = [[
proxies:
- name: n1
type: vmess
server: 1.2.3.4
port: 443
uuid: 11111111-2222-3333-4444-555555555555
alterId: 0
cipher: auto
]]
local SURGE = [[
[Proxy]
n1 = vmess, 1.2.3.4, 443, username=11111111-2222-3333-4444-555555555555
[Proxy Group]
auto = select, n1
[Rule]
FINAL,auto
]]
-- ---------- A:单格式内容不得被误判 ----------
-- 每条都断言「能解析出节点」且「没有 err」:只断言 err 的话,
-- 一份本来就解析不出节点的内容(比如 base64 那段)会让断言失去意义。
local function ok_single(name, content, want_format, want_nodes)
local res, err = parser.parse_local(content, "text")
check("A " .. name .. " imports without error", err == nil)
check("A " .. name .. " parses nodes",
res ~= nil and #res.nodes >= want_nodes)
if want_format then
check("A " .. name .. " format is " .. want_format,
res ~= nil and res.format == want_format)
end
end
ok_single("uri list", URI .. "\n" .. VLESS .. "\n", "uri", 2)
ok_single("uri with comment line", "# 我的订阅 https://example.com/sub\n" .. URI .. "\n", "uri", 1)
ok_single("wireguard conf", WG_CONF, "wireguard-conf", 1)
ok_single("json config", JSON_OBJ, "json", 1)
ok_single("json array", JSON_ARRAY, "json", 1)
ok_single("clash yaml", CLASH_YAML, "yaml", 1)
ok_single("surge", SURGE, "surge", 1)
-- base64:整份内容是一个 base64 blob(解出来是 URI 列表)
ok_single("base64 of uri list",
util.base64_encode(URI .. "\n" .. VLESS .. "\n"), "base64", 2)
-- 最容易误判的一类:**正常配置里带订阅地址**。
-- Clash YAML 的 `url: https://…`、sing-box JSON 的 `"url": "https://…"` 都含 "://",
-- 若用 detect() 里那条宽松的 content:find("://") 当 URI 判据,这些配置会被判成
-- 「YAML + URI 混合」而拒绝导入。
ok_single("clash yaml with proxy-provider url", CLASH_YAML ..
"proxy-providers:\n p:\n url: https://example.com/sub\n path: ./p.yaml\n",
"yaml", 1)
ok_single("singbox json with url in route rule", util.json_encode({
outbounds = { { type = "vmess", server = "1.2.3.4", server_port = 443,
uuid = "11111111-2222-3333-4444-555555555555" } },
route = { rules = { { url = "https://example.com/x" } } },
}), "json", 1)
-- YAML 流式映射 `{path: /x}` 不是 JSON 对象(键没有引号)
ok_single("clash yaml with flow mapping", CLASH_YAML ..
" ws-opts:\n {path: /ws, headers: {Host: a.example.com}}\n",
"yaml", 1)
-- WG conf 的 [Interface] 也是 "[" 开头,不得被判成 JSON 数组
ok_single("wireguard conf not read as json array", WG_CONF, "wireguard-conf", 1)
-- Surge 的 [Proxy] 段头同样以 "[" 开头
ok_single("surge not read as json array", SURGE, "surge", 1)
-- 注释里的链接不是节点行
ok_single("wireguard conf with commented url", WG_CONF .. "\n# https://example.com/sub\n",
"wireguard-conf", 1)
-- detect_all 单格式只命中一种
check("A detect_all uri", #parser.detect_all(URI .. "\n" .. VLESS) == 1)
check("A detect_all wireguard-conf", #parser.detect_all(WG_CONF) == 1)
check("A detect_all surge", #parser.detect_all(SURGE) == 1)
check("A detect_all empty", #parser.detect_all("") == 0)
check("A detect_all plain text", #parser.detect_all("hello world") == 0)
-- ---------- B:混合内容必须明确报错 ----------
local function mixed(name, content, want_a, want_b)
local res, err = parser.parse_local(content, "text")
check("B " .. name .. " rejected", res == nil)
check("B " .. name .. " error is explicit",
type(err) == "string" and err:find("Mixed formats in one text", 1, true) ~= nil)
-- 错误信息必须点名两种格式,否则用户不知道要拆成哪两份
check("B " .. name .. " names both formats",
type(err) == "string" and err:find(want_a, 1, true) ~= nil
and err:find(want_b, 1, true) ~= nil)
end
mixed("uri + wireguard conf", URI .. "\n" .. WG_CONF, "URI link", "WireGuard .conf")
mixed("wireguard conf + uri", WG_CONF .. "\n" .. URI, "URI link", "WireGuard .conf")
mixed("uri + json object", URI .. "\n" .. JSON_OBJ, "URI link", "JSON")
mixed("json object + uri", JSON_OBJ .. "\n" .. URI, "URI link", "JSON")
mixed("uri + json array", URI .. "\n" .. JSON_ARRAY, "URI link", "JSON")
mixed("uri + clash yaml", URI .. "\n" .. CLASH_YAML, "URI link", "Clash YAML")
mixed("uri + surge", URI .. "\n" .. SURGE, "URI link", "Surge/Loon config")
-- 报错不能是「静默丢弃」的另一种说法:绝不能返回部分节点
local resB = parser.parse_local(URI .. "\n" .. WG_CONF, "text")
check("B mixed import returns no partial nodes", resB == nil)
-- ---------- C:作用域 ----------
-- 远程订阅走 M.parse,不经 detect_all —— 混合格式的远程订阅行为不变。
-- 这是刻意的:本地导入是人工粘贴(能提示用户拆开),远程订阅的响应内容
-- 不受用户控制,直接拒收会让「订阅源多吐了一行」变成整条订阅不可用。
local resC = parser.parse(URI .. "\n" .. WG_CONF)
check("C remote parse still returns nodes", resC ~= nil and #resC.nodes >= 1)
print(string.format("\n%d passed, %d failed", passed, failed))
os.exit(failed == 0 and 0 or 1)