]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Test] composites: functional test for dynamic UCL composites map 6064/head
authorVsevolod Stakhov <vsevolod@rspamd.com>
Tue, 26 May 2026 20:57:47 +0000 (21:57 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Tue, 26 May 2026 20:57:47 +0000 (21:57 +0100)
Exercises load -> reload-with-update -> reload-with-stub:
 1. INITIAL MAP - DYN_ONE FIRES: load composites from map.1, scan a
    message, confirm DYN_ONE and DYN_TWO fire with their declared
    scores. Static composite STATIC_COMP also fires alongside.
 2. RELOAD - UPDATED SCORES AND NEW NAME: swap to map.2 (DYN_ONE
    score updated, DYN_TWO removed, DYN_THREE introduced), wait for
    the map watcher, scan, confirm new scores + new composite +
    DYN_TWO gone (stubbed).
 3. RELOAD - REMOVED COMPOSITE BECOMES STUB: swap back to map.1.
    DYN_ONE/DYN_TWO are back with original scores, DYN_THREE was in
    the previous generation but is now absent -> verifies the stub
    path keeps the name out of scan results.

Lua plugin registers DYN_BASE_A/B/C as always-firing atomic symbols
so the composite expressions resolve deterministically. Config sets
map_watch_interval = 0.5s for tight reload turnaround.

test/functional/cases/119_dynamic_composites.robot [new file with mode: 0644]
test/functional/configs/dynamic_composites.conf [new file with mode: 0644]
test/functional/configs/maps/dynamic_composites.map.1 [new file with mode: 0644]
test/functional/configs/maps/dynamic_composites.map.2 [new file with mode: 0644]
test/functional/lua/dynamic_composites.lua [new file with mode: 0644]

diff --git a/test/functional/cases/119_dynamic_composites.robot b/test/functional/cases/119_dynamic_composites.robot
new file mode 100644 (file)
index 0000000..26e2eab
--- /dev/null
@@ -0,0 +1,54 @@
+*** Settings ***
+Suite Setup     Dynamic Composites Setup
+Suite Teardown  Dynamic Composites Teardown
+Library         ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource        ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables       ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG}                  ${RSPAMD_TESTDIR}/configs/dynamic_composites.conf
+${RSPAMD_MAP_WATCH_INTERVAL}  0.5s
+${MESSAGE}                 ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT}       ${RSPAMD_TESTDIR}/lua/dynamic_composites.lua
+${RSPAMD_SCOPE}            Suite
+${RSPAMD_URL_TLD}          ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+INITIAL MAP - DYN_ONE FIRES
+  Scan File  ${MESSAGE}
+  Expect Symbol With Score  DYN_ONE  2.5
+  Expect Symbol With Score  DYN_TWO  3.5
+  Expect Symbol With Score  STATIC_COMP  1.0
+  Do Not Expect Symbol  DYN_THREE
+
+RELOAD - UPDATED SCORES AND NEW NAME
+  ${TMP_FILE} =  Make Temporary File
+  Copy File  ${RSPAMD_TESTDIR}/configs/maps/dynamic_composites.map.2  ${TMP_FILE}
+  Move File  ${TMP_FILE}  ${RSPAMD_DYN_COMP_MAP}
+  Sleep  2s  Wait for map reload
+  Scan File  ${MESSAGE}
+  Expect Symbol With Score  DYN_ONE  7.0
+  Expect Symbol With Score  DYN_THREE  4.0
+  Expect Symbol With Score  STATIC_COMP  1.0
+  Do Not Expect Symbol  DYN_TWO
+
+RELOAD - REMOVED COMPOSITE BECOMES STUB
+  ${TMP_FILE} =  Make Temporary File
+  Copy File  ${RSPAMD_TESTDIR}/configs/maps/dynamic_composites.map.1  ${TMP_FILE}
+  Move File  ${TMP_FILE}  ${RSPAMD_DYN_COMP_MAP}
+  Sleep  2s  Wait for map reload
+  Scan File  ${MESSAGE}
+  Expect Symbol With Score  DYN_ONE  2.5
+  Expect Symbol With Score  DYN_TWO  3.5
+  Do Not Expect Symbol  DYN_THREE
+
+*** Keywords ***
+Dynamic Composites Setup
+  ${RSPAMD_DYN_COMP_MAP} =  Make Temporary File
+  Set Suite Variable  ${RSPAMD_DYN_COMP_MAP}
+  Copy File  ${RSPAMD_TESTDIR}/configs/maps/dynamic_composites.map.1  ${RSPAMD_DYN_COMP_MAP}
+  Rspamd Setup
+
+Dynamic Composites Teardown
+  Remove File  ${RSPAMD_DYN_COMP_MAP}
+  Rspamd Teardown
diff --git a/test/functional/configs/dynamic_composites.conf b/test/functional/configs/dynamic_composites.conf
new file mode 100644 (file)
index 0000000..32356a3
--- /dev/null
@@ -0,0 +1,42 @@
+options = {
+       filters = []
+       url_tld = "{= env.URL_TLD =}"
+       pidfile = "{= env.TMPDIR =}/rspamd.pid"
+       map_watch_interval = {= env.MAP_WATCH_INTERVAL =};
+}
+logging = {
+       type = "file",
+       level = "debug"
+       filename = "{= env.TMPDIR =}/rspamd.log"
+}
+metric = {
+       name = "default",
+       actions = {
+               reject = 100500,
+       }
+       unknown_weight = 1
+}
+
+worker {
+       type = normal
+       bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_NORMAL =}"
+       count = 1
+       task_timeout = 10s;
+}
+worker {
+       type = controller
+       bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_CONTROLLER =}"
+       count = 1
+       secure_ip = ["127.0.0.1", "::1"];
+       stats_path = "{= env.TMPDIR =}/stats.ucl"
+}
+lua = "{= env.TESTDIR =}/lua/test_coverage.lua";
+lua = "{= env.LUA_SCRIPT =}";
+
+composites {
+       STATIC_COMP {
+               expression = "DYN_BASE_A";
+               score = 1.0;
+       }
+       dynamic = "{= env.DYN_COMP_MAP =}";
+}
diff --git a/test/functional/configs/maps/dynamic_composites.map.1 b/test/functional/configs/maps/dynamic_composites.map.1
new file mode 100644 (file)
index 0000000..7dacf82
--- /dev/null
@@ -0,0 +1,8 @@
+DYN_ONE {
+       expression = "DYN_BASE_A";
+       score = 2.5;
+}
+DYN_TWO {
+       expression = "DYN_BASE_B";
+       score = 3.5;
+}
diff --git a/test/functional/configs/maps/dynamic_composites.map.2 b/test/functional/configs/maps/dynamic_composites.map.2
new file mode 100644 (file)
index 0000000..2254269
--- /dev/null
@@ -0,0 +1,8 @@
+DYN_ONE {
+       expression = "DYN_BASE_A";
+       score = 7.0;
+}
+DYN_THREE {
+       expression = "DYN_BASE_C";
+       score = 4.0;
+}
diff --git a/test/functional/lua/dynamic_composites.lua b/test/functional/lua/dynamic_composites.lua
new file mode 100644 (file)
index 0000000..ccfe62b
--- /dev/null
@@ -0,0 +1,11 @@
+-- Always-firing atomic symbols used as inputs for dynamic composites.
+local atoms = { 'DYN_BASE_A', 'DYN_BASE_B', 'DYN_BASE_C' }
+for _, name in ipairs(atoms) do
+  rspamd_config:register_symbol({
+    name = name,
+    score = 0.1,
+    callback = function()
+      return true, 'fires always'
+    end
+  })
+end