🦄 Sync 2026-10-02 14:52:05

This commit is contained in:
github-actions[bot] committed 2026-10-02 14:52:05 +08:00
1 parent 4c85793342
commit 0bfe84f1e8
133 files changed
+7983 -3225

No files matched your search

@@ -93,6 +93,11 @@ local function core_reset()
CORE.sync = function() return true end
CORE.write_cron = function() end
CORE.cron_time_valid = function() return true end
-- 控制器在 read_ua_fields 里调用它解析「订阅客户端类型」(预设 key → UA 字符串)。
-- 与上面的 validate_rename_map 同理:stub 缺这个函数会让整条保存路径 nil 调用。
-- 这里返回 ""(不设置 UA),让用例聚焦各自要覆盖的字段。
CORE.resolve_user_agent = function() return "" end
CORE.UA_PRESETS = { { key = "clash-verge", label = "Clash Verge", ua = "clash-verge/v2.5.0" } }
CORE.generate_link = function() return "body", "text/plain", "f.txt" end
CORE.RULE_PROTOS = { "vmess", "trojan" }
end
@@ -175,6 +180,85 @@ FORM = { token = "t", id = "s00000001" }
ctl.action_delete()
check("delete success has no err", not has_err())
-- ---------- 勾选批量删除:id 支持逗号分隔多个 ----------
-- 行内删除按钮提交单个 id(上面两条覆盖);表头的「删除」按钮把勾选的多个
-- id 用逗号拼起来提交,走同一个 action —— 与节点页 action_node_delete 的
-- idx 是同一套约定。
reset()
local seen_ids = {}
CORE.remove = function(id) seen_ids[#seen_ids + 1] = id; return true end
FORM = { token = "t", id = "s00000001,s00000002,s00000003" }
ctl.action_delete()
check("multi delete removes every id",
#seen_ids == 3 and seen_ids[1] == "s00000001" and seen_ids[3] == "s00000003")
check("multi delete success has no err", not has_err())
-- 逗号两侧的空格不影响切分(浏览器不会加空格,手工构造 / 复制粘贴会)
reset()
seen_ids = {}
CORE.remove = function(id) seen_ids[#seen_ids + 1] = id; return true end
FORM = { token = "t", id = " s00000001 , s00000002 " }
ctl.action_delete()
check("delete tolerates spaces around ids",
#seen_ids == 2 and seen_ids[1] == "s00000001" and seen_ids[2] == "s00000002")
-- 批量删除成功后写一次 cron:删掉的订阅不能继续留在 /etc/cron.d/substore 里
-- 断言里同时数 core.remove 的调用次数:只断言 write_cron 的话,改动前的实现
-- (把整串 id 当成一个 id 传下去)也会通过 —— stub 对任何 id 都返回 true。
reset()
local cron_writes, rm_calls = 0, {}
CORE.remove = function(id) rm_calls[#rm_calls + 1] = id; return true end
CORE.write_cron = function() cron_writes = cron_writes + 1 end
FORM = { token = "t", id = "s00000001,s00000002" }
ctl.action_delete()
check("multi delete rewrites cron once after removing both",
cron_writes == 1 and #rm_calls == 2)
-- 部分失败必须回显:勾了 3 个只删掉 1 个,不能显示成「成功」。
-- 同样要数调用次数,否则改动前的实现也会「通过」。
--
-- 同时断言 cron 被重写:删掉的那 1 个订阅的 cron 行必须清掉,
-- 否则 substore-cron.sh 会拿着已不存在的 id 反复失败(退出码非 0)。
reset()
local partial_calls, partial_cron = 0, 0
CORE.remove = function(id) partial_calls = partial_calls + 1; return id == "s00000001" end
CORE.write_cron = function() partial_cron = partial_cron + 1 end
FORM = { token = "t", id = "s00000001,s00000002,s00000003" }
ctl.action_delete()
check("partial delete attempts every id and reports error",
has_err() and partial_calls == 3)
check("partial delete rewrites cron for the removed ones", partial_cron == 1)
-- 全部失败:一个都没删掉 → 没有任何订阅的 cron 行需要清理,不重写
reset()
local none_cron = 0
CORE.remove = function() return false, "订阅不存在" end
CORE.write_cron = function() none_cron = none_cron + 1 end
FORM = { token = "t", id = "s00000001,s00000002" }
ctl.action_delete()
check("all-failed delete does not rewrite cron", none_cron == 0)
-- 全部失败:回显 core.remove 给出的原因
reset()
CORE.remove = function() return false, "订阅不存在" end
FORM = { token = "t", id = "s00000001,s00000002" }
ctl.action_delete()
check("all-failed delete reports core reason", has_err())
-- 一个可用 id 都没有:不能静默跳回(否则用户以为删了)
reset()
FORM = { token = "t", id = " , " }
ctl.action_delete()
check("delete with no usable id reports error", has_err())
-- 只有分隔符的 id 不得被当成「一个叫 ',' 的订阅」交给 core
reset()
local called = 0
CORE.remove = function() called = called + 1; return true end
FORM = { token = "t", id = ",,," }
ctl.action_delete()
check("delete does not pass empty ids to core", called == 0)
-- action_combo_save:名称为空
reset()
FORM = { token = "t", name = "" }
@@ -246,5 +330,74 @@ FORM = { token = "t", id = "s00000001", idx = "1" }
ctl.action_node_delete()
check("node_delete success has no err", not has_err())
-- ---------- M28:CSRF token 必须真的校验 ----------
-- 上游 LuCI 渲染表单时模板变量 `token` 就是 luci.dispatcher.context.authtoken
-- (luci-lua-runtime/luasrc/template.lua 的 viewns 元表),
-- 所以「提交的 token == authtoken」正是框架自己那套判定,不会误拒合法表单。
-- 此前的判定是 `token ~= nil`:`token=`(空串)也能通过,等于没有校验。
_G.luci.dispatcher.context = { authtoken = "secret" }
-- 合法表单必须照常通过(这条是关键:改了校验不能把正常提交挡在门外)
reset()
local ok_calls = 0
CORE.remove = function() ok_calls = ok_calls + 1; return true end
FORM = { token = "secret", id = "s00000001" }
ctl.action_delete()
check("M28 valid token accepted", ok_calls == 1 and not has_err())
-- 空串 token:此前被 `~= nil` 放过,现在必须拒绝
reset()
local empty_calls = 0
CORE.remove = function() empty_calls = empty_calls + 1; return true end
FORM = { token = "", id = "s00000001" }
ctl.action_delete()
check("M28 empty token rejected", empty_calls == 0)
check("M28 empty token reports error", has_err())
-- 不匹配的 token 同样拒绝
reset()
local bad_calls = 0
CORE.remove = function() bad_calls = bad_calls + 1; return true end
FORM = { token = "wrong", id = "s00000001" }
ctl.action_delete()
check("M28 mismatched token rejected", bad_calls == 0)
check("M28 mismatched token reports error", has_err())
-- 校验失败必须对所有动作生效(此前 9 个动作都是静默重定向)
reset()
local created = 0
CORE.add = function() created = created + 1; return "s0000000a" end
FORM = { token = "", name = "n", url = "http://e.com/s" }
ctl.action_create()
check("M28 create rejects empty token", created == 0 and has_err())
reset()
local updated = 0
CORE.sync = function() updated = updated + 1; return true end
FORM = { token = "wrong", id = "s00000001" }
ctl.action_update()
check("M28 update rejects bad token", updated == 0 and has_err())
-- 节点页动作走 back_to_nodes,同样要带 err
reset()
local wrote = 0
CORE.read_nodes = function() return { { name = "a" } } end
CORE.write_nodes = function() wrote = wrote + 1; return true end
FORM = { token = "", id = "s00000001", idx = "1", content = "[]" }
ctl.action_node_save()
check("M28 node_save rejects empty token",
wrote == 0 and LAST_REDIRECT:find("err=", 1, true) ~= nil)
-- 取不到 authtoken(老版本 LuCI / 非标准上下文)时退回原判定,
-- 不能因为拿不到框架内部字段就把所有表单都拒掉。
_G.luci.dispatcher.context = nil
reset()
local legacy_calls = 0
CORE.remove = function() legacy_calls = legacy_calls + 1; return true end
FORM = { token = "t", id = "s00000001" }
ctl.action_delete()
check("M28 falls back when authtoken unavailable",
legacy_calls == 1 and not has_err())
print(string.format("\n%d passed, %d failed", passed, failed))
os.exit(failed == 0 and 0 or 1)