From: Vsevolod Stakhov Date: Fri, 10 Jul 2026 07:32:18 +0000 (+0100) Subject: [Test] composites: functional cases for Lua conditions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=62d6e8acb02aedf7f9b3fa51020cc34748a3c16c;p=thirdparty%2Frspamd.git [Test] composites: functional cases for Lua conditions Cover: option-inspecting condition, cross-symbol join on a shared option value, numeric return as atom weight combined with an expression limit, false condition, and depends_on deferring a composite whose condition consults a postfilter symbol. --- diff --git a/test/functional/cases/109_composites.robot b/test/functional/cases/109_composites.robot index 4d73cf4e8a..97e360aa69 100644 --- a/test/functional/cases/109_composites.robot +++ b/test/functional/cases/109_composites.robot @@ -76,3 +76,19 @@ Composites - Opts RE Hit 3 Expect Symbol With Score SYMOPTS4 6.00 Do Not Expect Symbol SYMOPTS2 Do Not Expect Symbol SYMOPTS1 + +Composites - Lua conditions + Scan File ${MESSAGE} opts=b-opt1 + Expect Symbol With Score COND_TRUE 5.00 + Expect Symbol COND_JOIN + Expect Symbol COND_WEIGHT + Expect Symbol COND_DEPENDS + Do Not Expect Symbol COND_FALSE + Do Not Expect Symbol COND_WEIGHT_MISS + +Composites - Lua condition join miss + Scan File ${MESSAGE} opts=zzz-no-match + Expect Symbol COND_TRUE + Expect Symbol COND_DEPENDS + Do Not Expect Symbol COND_JOIN + Do Not Expect Symbol COND_FALSE diff --git a/test/functional/lua/composites.lua b/test/functional/lua/composites.lua index 648eda0ff2..cdeeff7690 100644 --- a/test/functional/lua/composites.lua +++ b/test/functional/lua/composites.lua @@ -138,3 +138,132 @@ rspamd_config:register_symbol({ end end }) + +-- Composites with per-symbol Lua conditions; the auxiliary symbols fire only +-- when the `opts` request header is present to keep the exact score +-- assertions of the plain scan intact +local function has_opts(task) + local h = task:get_request_header('opts') + return h and #tostring(h) > 0 +end + +rspamd_config:register_symbol({ + name = 'COND_SYM_A', + score = 1.0, + callback = function(task) + if has_opts(task) then + return true, 1.0, 'a-opt1', 'a-opt2' + end + end +}) +rspamd_config:register_symbol({ + name = 'COND_SYM_B', + score = 1.0, + callback = function(task) + if has_opts(task) then + return true, 1.0, 'b-opt1' + end + end +}) +rspamd_config:register_symbol({ + name = 'COND_POSTFILTER_SYM', + type = 'postfilter', + score = 1.0, + callback = function(task) + if has_opts(task) then + task:insert_result('COND_POSTFILTER_SYM', 1.0, 'post-opt') + end + end +}) + +rspamd_config:add_composite('COND_TRUE', { + expression = 'COND_SYM_A & COND_SYM_B', + score = 5.0, + policy = 'leave', + conditions = { + COND_SYM_A = function(_, sym) + if not sym or sym.name ~= 'COND_SYM_A' then + return false + end + for _, o in ipairs(sym.options or {}) do + if o == 'a-opt2' then + return true + end + end + return false + end, + }, +}) + +rspamd_config:add_composite('COND_FALSE', { + expression = 'COND_SYM_A & COND_SYM_B', + score = 5.0, + policy = 'leave', + conditions = { + COND_SYM_B = function() + return false + end, + }, +}) + +-- Join on a shared option value between two independent symbols +rspamd_config:add_composite('COND_JOIN', { + expression = 'OPTS & COND_SYM_B', + score = 5.0, + policy = 'leave', + conditions = { + OPTS = function(task, sym) + local other = task:get_symbol('COND_SYM_B') + if not other then + return false + end + local seen = {} + for _, o in ipairs(other[1].options or {}) do + seen[o] = true + end + for _, o in ipairs(sym.options or {}) do + if seen[o] then + return true + end + end + return false + end, + }, +}) + +-- Numeric return is used as the atom weight in the expression +rspamd_config:add_composite('COND_WEIGHT', { + expression = 'COND_SYM_A + COND_SYM_B > 5', + score = 5.0, + policy = 'leave', + conditions = { + COND_SYM_A = function() + return 10 + end, + }, +}) + +rspamd_config:add_composite('COND_WEIGHT_MISS', { + expression = 'COND_SYM_A + COND_SYM_B > 5', + score = 5.0, + policy = 'leave', + conditions = { + COND_SYM_A = function() + return 2 + end, + }, +}) + +-- The condition consults a postfilter symbol invisible to the expression; +-- depends_on defers the composite to the second pass +rspamd_config:add_composite('COND_DEPENDS', { + expression = 'COND_SYM_A', + score = 5.0, + policy = 'leave', + depends_on = { 'COND_POSTFILTER_SYM' }, + conditions = { + COND_SYM_A = function(task) + return task:get_symbol('COND_POSTFILTER_SYM') ~= nil + end, + }, +})