mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 06:45:13 +08:00
🦄 Sync 2026-10-02 14:52:05
This commit is contained in:
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)
|
||||
Reference in new issue
Block a user