-- rules_fields_test.lua — 订阅规则的「协议筛选 / 重命名」字段必须端到端可达 -- 用法:lua5.1 tests/rules_fields_test.lua -- -- 覆盖 M30:`proto_filter_*` 与 `rename_map` 被控制器读取并落盘,但三个表单 -- (form.htm / local_form.htm / combo.htm)都没有提交它们的控件 —— -- 1. UI 不可达:用户永远无法设置这两项; -- 2. **静默数据丢失**:控制器每次都把 formvalue 的 nil 落成 "", -- 于是通过 UCI 手工设过的值在下一次保存时被抹掉。 -- -- .htm 需要 LuCI 运行时才能渲染,lua5.1 跑不了模板,因此这里分两层: -- * 静态检查:模板确实提交了这两个字段,且协议清单与控制器同源; -- * 行为检查:用同一份 RULE_PROTOS 复刻控制器的收集逻辑, -- 验证它生成的 proto_filter 真的能被 node.apply_rules 用上。 package.path = "./root/usr/share/?.lua;" .. package.path local util = require("substore.util") local core = require("substore.core") local node = require("substore.node") 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 TEMPLATES = { "root/usr/lib/lua/luci/view/substore/form.htm", "root/usr/lib/lua/luci/view/substore/local_form.htm", "root/usr/lib/lua/luci/view/substore/combo.htm", } local CONTROLLER = "root/usr/lib/lua/luci/controller/admin/substore.lua" -- ---------- RULE_PROTOS 本身 ---------- check("RULE_PROTOS is a table", type(core.RULE_PROTOS) == "table") check("RULE_PROTOS non-empty", #core.RULE_PROTOS > 0) local seen, dup = {}, nil for _, p in ipairs(core.RULE_PROTOS) do if seen[p] then dup = p end seen[p] = true end check("RULE_PROTOS has no duplicate" .. (dup and (" (" .. dup .. ")") or ""), dup == nil) -- 名字必须是 node.normalize 产出的**规范**协议名,否则 proto_filter 匹配不到任何 -- 节点(node.apply_rules 用 set[n.proto] 精确比对),症状是「勾了没用」。 local bad = {} for _, p in ipairs(core.RULE_PROTOS) do local n = node.normalize({ proto = p, server = "1.2.3.4", port = 443 }) if n.proto ~= p then bad[#bad + 1] = p .. "->" .. tostring(n.proto) end end check("RULE_PROTOS are canonical proto names" .. (#bad > 0 and (" (" .. table.concat(bad, ",") .. ")") or ""), #bad == 0) -- socks5 是解析阶段的归一目标(parser 把它写成 socks),勾选框里不该出现 socks5 check("RULE_PROTOS has no socks5 alias", seen["socks5"] == nil) check("RULE_PROTOS has socks", seen["socks"] == true) -- ---------- 静态:三个模板都必须提交这两个字段 ---------- for _, path in ipairs(TEMPLATES) do local src = util.read_file(path) local name = path:match("[^/]+$") check(name .. " readable", src ~= nil) src = src or "" check(name .. " submits rename_map", src:find('name="rename_map"', 1, true) ~= nil) check(name .. " submits proto_filter_*", src:find('name="proto_filter_', 1, true) ~= nil) -- 协议清单必须来自 core.RULE_PROTOS,模板里不得再抄一份 check(name .. " iterates core.RULE_PROTOS", src:find("ipairs(core.RULE_PROTOS)", 1, true) ~= nil) -- 已有值必须回填,否则打开编辑页看不到当前设置,一保存就被覆盖 check(name .. " preloads proto_filter", src:find("proto_sel", 1, true) ~= nil) check(name .. " preloads rename_map", src:find("local rm =", 1, true) ~= nil) end -- ---------- 静态:协议复选框不得嵌在