summaryrefslogtreecommitdiffstats
path: root/test/functional/cases
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
commit133a45c109da5310add55824db21af5239951f93 (patch)
treeba6ac4c0a950a0dda56451944315d66409923918 /test/functional/cases
parentInitial commit. (diff)
downloadrspamd-133a45c109da5310add55824db21af5239951f93.tar.xz
rspamd-133a45c109da5310add55824db21af5239951f93.zip
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/functional/cases')
-rw-r--r--test/functional/cases/001_merged/100_general.robot61
-rw-r--r--test/functional/cases/001_merged/101_lua.robot50
-rw-r--r--test/functional/cases/001_merged/102_multimap.robot436
-rw-r--r--test/functional/cases/001_merged/104_get_from.robot57
-rw-r--r--test/functional/cases/001_merged/105_mimetypes.robot74
-rw-r--r--test/functional/cases/001_merged/106_mid.robot36
-rw-r--r--test/functional/cases/001_merged/114_phishing.robot26
-rw-r--r--test/functional/cases/001_merged/115_dmarc.robot101
-rw-r--r--test/functional/cases/001_merged/117_spf.robot157
-rw-r--r--test/functional/cases/001_merged/160_antivirus.robot151
-rw-r--r--test/functional/cases/001_merged/240_redis.robot16
-rw-r--r--test/functional/cases/001_merged/250_dns.robot21
-rw-r--r--test/functional/cases/001_merged/270_selector.robot19
-rw-r--r--test/functional/cases/001_merged/280_rules.robot139
-rw-r--r--test/functional/cases/001_merged/281_fnames.robot13
-rw-r--r--test/functional/cases/001_merged/290_greylist.robot25
-rw-r--r--test/functional/cases/001_merged/300_rbl.robot98
-rw-r--r--test/functional/cases/001_merged/310_udp.robot40
-rw-r--r--test/functional/cases/001_merged/321_arc_check.robot19
-rw-r--r--test/functional/cases/001_merged/340_surbl.robot182
-rw-r--r--test/functional/cases/001_merged/350_magic.robot67
-rw-r--r--test/functional/cases/001_merged/__init__.robot28
-rw-r--r--test/functional/cases/101_lua.robot35
-rw-r--r--test/functional/cases/103_password.robot71
-rw-r--r--test/functional/cases/108_settings.robot268
-rw-r--r--test/functional/cases/109_composites.robot78
-rw-r--r--test/functional/cases/110_statistics/090-peruser.robot15
-rw-r--r--test/functional/cases/110_statistics/100-redis-keyed-siphash.robot16
-rw-r--r--test/functional/cases/110_statistics/110-redis-keyed-xxhash.robot16
-rw-r--r--test/functional/cases/110_statistics/200-redis-plain-siphash.robot15
-rw-r--r--test/functional/cases/110_statistics/210-redis-plain-xxhash.robot15
-rw-r--r--test/functional/cases/110_statistics/lib.robot69
-rw-r--r--test/functional/cases/116_dkim.robot59
-rw-r--r--test/functional/cases/120_fuzzy/encrypted.robot14
-rw-r--r--test/functional/cases/120_fuzzy/fasthash-keyed.robot14
-rw-r--r--test/functional/cases/120_fuzzy/fasthash.robot14
-rw-r--r--test/functional/cases/120_fuzzy/general.robot17
-rw-r--r--test/functional/cases/120_fuzzy/lib.robot181
-rw-r--r--test/functional/cases/120_fuzzy/mumhash-keyed.robot14
-rw-r--r--test/functional/cases/120_fuzzy/mumhash.robot14
-rw-r--r--test/functional/cases/120_fuzzy/siphash-keyed.robot14
-rw-r--r--test/functional/cases/120_fuzzy/siphash.robot14
-rw-r--r--test/functional/cases/120_fuzzy/xxhash-keyed.robot15
-rw-r--r--test/functional/cases/120_fuzzy/xxhash.robot15
-rw-r--r--test/functional/cases/121_json/100_preresult.robot23
-rw-r--r--test/functional/cases/121_json/101_simple.robot23
-rw-r--r--test/functional/cases/121_json/lib.robot22
-rw-r--r--test/functional/cases/123_whitelist.robot79
-rw-r--r--test/functional/cases/125_map_reload.robot47
-rw-r--r--test/functional/cases/131_dkim_signing/001_simple.robot35
-rw-r--r--test/functional/cases/131_dkim_signing/002_redis.robot31
-rw-r--r--test/functional/cases/131_dkim_signing/003_eddsa.robot30
-rw-r--r--test/functional/cases/131_dkim_signing/004_invalidate_key.robot50
-rw-r--r--test/functional/cases/131_dkim_signing/005_multiple.robot20
-rw-r--r--test/functional/cases/131_dkim_signing/006_milter.robot31
-rw-r--r--test/functional/cases/131_dkim_signing/007_sign_maps.robot25
-rw-r--r--test/functional/cases/135_spamassassin.robot34
-rw-r--r--test/functional/cases/140_proxy.robot50
-rw-r--r--test/functional/cases/150_rspamadm.robot45
-rw-r--r--test/functional/cases/151_rspamadm_async.robot54
-rw-r--r--test/functional/cases/161_p0f.robot97
-rw-r--r--test/functional/cases/162_url_redirector.robot34
-rw-r--r--test/functional/cases/180_milter.robot40
-rw-r--r--test/functional/cases/210_clickhouse/001_migration.robot91
-rw-r--r--test/functional/cases/210_clickhouse/clickhouse.py78
-rw-r--r--test/functional/cases/220_http.robot74
-rw-r--r--test/functional/cases/230_tcp.robot75
-rw-r--r--test/functional/cases/231_tcp_down.robot28
-rw-r--r--test/functional/cases/241_redis_is_dead.robot26
-rw-r--r--test/functional/cases/260_regex.robot33
-rw-r--r--test/functional/cases/320_arc_signing/001_simple.robot31
-rw-r--r--test/functional/cases/320_arc_signing/002_redis.robot29
-rw-r--r--test/functional/cases/330_neural/001_autotrain.robot63
-rw-r--r--test/functional/cases/330_neural/002_manualtrain.robot72
-rw-r--r--test/functional/cases/360_force_actions.robot44
-rw-r--r--test/functional/cases/380_external_relay.robot48
-rw-r--r--test/functional/cases/400_known_senders.robot36
-rw-r--r--test/functional/cases/410_logging/000_console/000_systemd_logger.robot24
-rw-r--r--test/functional/cases/410_logging/000_console/001_timestamps.robot24
-rw-r--r--test/functional/cases/410_logging/001_file/000_json.robot17
-rw-r--r--test/functional/cases/__init__.robot19
81 files changed, 4381 insertions, 0 deletions
diff --git a/test/functional/cases/001_merged/100_general.robot b/test/functional/cases/001_merged/100_general.robot
new file mode 100644
index 0000000..16b9b5f
--- /dev/null
+++ b/test/functional/cases/001_merged/100_general.robot
@@ -0,0 +1,61 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${GTUBE} ${RSPAMD_TESTDIR}/messages/gtube.eml
+${SETTINGS_NOSYMBOLS} {symbols_enabled = []}
+
+*** Test Cases ***
+GTUBE
+ Scan File ${GTUBE}
+ ... Settings=${SETTINGS_NOSYMBOLS}
+ Expect Symbol GTUBE
+
+GTUBE - Encrypted
+ ${result} = Run Rspamc -p -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_NORMAL} --key ${RSPAMD_KEY_PUB1}
+ ... ${GTUBE} --header=Settings=${SETTINGS_NOSYMBOLS}
+ Check Rspamc ${result} GTUBE (
+
+GTUBE - Scan File feature
+ Scan File By Reference ${GTUBE}
+ ... Settings=${SETTINGS_NOSYMBOLS}
+ Expect Symbol GTUBE
+
+GTUBE - Scan File feature (encoded)
+ ${encoded} = Encode Filename ${GTUBE}
+ Scan File By Reference ${encoded}
+ ... Settings=${SETTINGS_NOSYMBOLS}
+ Expect Symbol GTUBE
+
+GTUBE - SPAMC
+ ${result} = Spamc ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_NORMAL} ${GTUBE}
+ Should Contain ${result} GTUBE
+
+GTUBE - RSPAMC
+ ${result} = Rspamc ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_NORMAL} ${GTUBE}
+ Should Contain ${result} GTUBE
+
+EMAILS DETECTION 1
+ Scan File ${RSPAMD_TESTDIR}/messages/emails1.eml
+ ... URL-Format=Extended Settings=${SETTINGS_NOSYMBOLS}
+ Expect Email jim@example.net
+ Expect Email bob@example.net
+ Expect Email rupert@example.net
+
+EMAILS DETECTION ZEROFONT
+ Scan File ${RSPAMD_TESTDIR}/messages/zerofont.eml
+ ... Settings={symbols_enabled = [MANY_INVISIBLE_PARTS, ZERO_FONT]}
+ Expect Symbol MANY_INVISIBLE_PARTS
+ Expect Symbol ZERO_FONT
+
+HTML ONLY - TRUE POSITIVE
+ Scan File ${RSPAMD_TESTDIR}/messages/zerofont.eml
+ ... Settings={symbols_enabled = [MIME_HTML_ONLY]}
+ Expect Symbol MIME_HTML_ONLY
+
+HTML ONLY - TRUE NEGATIVE
+ Scan File ${RSPAMD_TESTDIR}/messages/btc.eml
+ ... Settings={symbols_enabled = [MIME_HTML_ONLY]}
+ Do Not Expect Symbol MIME_HTML_ONLY
diff --git a/test/functional/cases/001_merged/101_lua.robot b/test/functional/cases/001_merged/101_lua.robot
new file mode 100644
index 0000000..d1a771c
--- /dev/null
+++ b/test/functional/cases/001_merged/101_lua.robot
@@ -0,0 +1,50 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_MAP_MAP} ${RSPAMD_TESTDIR}/configs/maps/map.list
+${RSPAMD_RADIX_MAP} ${RSPAMD_TESTDIR}/configs/maps/ip2.list
+${RSPAMD_REGEXP_MAP} ${RSPAMD_TESTDIR}/configs/maps/regexp.list
+
+*** Test Cases ***
+Recipient Parsing Sanity
+ Scan File ${MESSAGE} Rcpt=rcpt1@foobar,rcpt2@foobar,rcpt3@foobar,rcpt4@foobar
+ ... Settings={symbols_enabled = [TEST_RCPT]}
+ Expect Symbol With Exact Options TEST_RCPT rcpt1@foobar,rcpt2@foobar,rcpt3@foobar,rcpt4@foobar
+
+TLD parts
+ Scan File ${MESSAGE} Settings={symbols_enabled = [TEST_TLD]}
+ Expect Symbol With Exact Options TEST_TLD no worry
+
+Hashes
+ Scan File ${MESSAGE} Settings={symbols_enabled = [TEST_HASHES]}
+ Expect Symbol With Exact Options TEST_HASHES no worry
+
+Maps Key Values
+ Scan File ${MESSAGE} Settings={symbols_enabled = [RADIX_KV, REGEXP_KV, MAP_KV]}
+ Expect Symbol With Exact Options RADIX_KV no worry
+ Expect Symbol With Exact Options REGEXP_KV no worry
+ Expect Symbol With Exact Options MAP_KV no worry
+
+Option Order
+ Scan File ${MESSAGE} Settings={symbols_enabled = [OPTION_ORDER, TBL_OPTION_ORDER]}
+ Expect Symbol With Exact Options OPTION_ORDER one two three 4 5 a
+ Expect Symbol With Exact Options TBL_OPTION_ORDER one two three 4 5 a
+
+Remove Result
+ Scan File ${MESSAGE} Settings={symbols_enabled = [REMOVE_RESULT_EXPECTED, REMOVE_RESULT_UNEXPECTED]}
+ Expect Symbol REMOVE_RESULT_EXPECTED
+ Do Not Expect Symbol REMOVE_RESULT_UNEXPECTED
+
+Rule conditions
+ Scan File ${MESSAGE} Settings={symbols_enabled = [ANY_A]}
+ Expect Symbol With Option ANY_A hello3
+ Expect Symbol With Option ANY_A hello1
+ Expect Symbol With Option ANY_A hello2
+
+External Maps Simple
+ Scan File ${MESSAGE} Settings={symbols_enabled = [EXTERNAL_MAP]}
+ Expect Symbol With Exact Options EXTERNAL_MAP +hello map \ No newline at end of file
diff --git a/test/functional/cases/001_merged/102_multimap.robot b/test/functional/cases/001_merged/102_multimap.robot
new file mode 100644
index 0000000..135f5ce
--- /dev/null
+++ b/test/functional/cases/001_merged/102_multimap.robot
@@ -0,0 +1,436 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${FREEMAIL_CC} ${RSPAMD_TESTDIR}/messages/freemailcc.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RCVD1} ${RSPAMD_TESTDIR}/messages/received1.eml
+${RCVD2} ${RSPAMD_TESTDIR}/messages/received2.eml
+${RCVD3} ${RSPAMD_TESTDIR}/messages/received3.eml
+${RCVD4} ${RSPAMD_TESTDIR}/messages/received4.eml
+${URL1} ${RSPAMD_TESTDIR}/messages/url1.eml
+${URL2} ${RSPAMD_TESTDIR}/messages/url2.eml
+${URL3} ${RSPAMD_TESTDIR}/messages/url3.eml
+${URL4} ${RSPAMD_TESTDIR}/messages/url4.eml
+${URL5} ${RSPAMD_TESTDIR}/messages/url5.eml
+${URL_ICS} ${RSPAMD_TESTDIR}/messages/ics.eml
+${UTF_MESSAGE} ${RSPAMD_TESTDIR}/messages/utf.eml
+
+*** Test Cases ***
+URL_ICS
+ Scan File ${URL_ICS}
+ ... Settings={symbols_enabled = []}
+ Expect URL test.com
+
+MAP - DNSBL HIT
+ Scan File ${MESSAGE} IP=127.0.0.2
+ ... Settings={symbols_enabled = [DNSBL_MAP]}
+ Expect Symbol DNSBL_MAP
+
+MAP - DNSBL MISS
+ Scan File ${MESSAGE} IP=127.0.0.1
+ ... Settings={symbols_enabled = [DNSBL_MAP]}
+ Do Not Expect Symbol DNSBL_MAP
+
+MAP - IP HIT
+ Scan File ${MESSAGE} IP=127.0.0.1
+ ... Settings={symbols_enabled = [IP_MAP]}
+ Expect Symbol IP_MAP
+
+MAP - IP MISS
+ Scan File ${MESSAGE} IP=127.0.0.2
+ ... Settings={symbols_enabled = [IP_MAP]}
+ Do Not Expect Symbol IP_MAP
+
+MAP - IP MASK
+ Scan File ${MESSAGE} IP=10.1.0.10
+ ... Settings={symbols_enabled = [IP_MAP]}
+ Expect Symbol IP_MAP
+
+MAP - IP MASK MISS
+ Scan File ${MESSAGE} IP=11.1.0.10
+ ... Settings={symbols_enabled = [IP_MAP]}
+ Do Not Expect Symbol IP_MAP
+
+MAP - IP V6
+ Scan File ${MESSAGE} IP=::1
+ ... Settings={symbols_enabled = [IP_MAP]}
+ Expect Symbol IP_MAP
+
+MAP - IP V6 MISS
+ Scan File ${MESSAGE} IP=fe80::1
+ ... Settings={symbols_enabled = [IP_MAP]}
+ Do Not Expect Symbol IP_MAP
+
+MAP - FROM
+ Scan File ${MESSAGE} From=user@example.com
+ ... Settings={symbols_enabled = [FROM_MAP]}
+ Expect Symbol FROM_MAP
+
+MAP - COMBINED IP MASK FROM
+ Scan File ${MESSAGE} IP=10.1.0.10 From=user@example.com
+ ... Settings={symbols_enabled = [COMBINED_MAP_AND, COMBINED_MAP_OR]}
+ Expect Symbol With Score COMBINED_MAP_AND 10
+ Do Not Expect Symbol COMBINED_MAP_OR
+ Expect Action no action
+
+MAP - COMBINED IP MASK ONLY
+ Scan File ${MESSAGE} IP=10.1.0.10
+ ... Settings={symbols_enabled = [COMBINED_MAP_AND, COMBINED_MAP_OR]}
+ Do Not Expect Symbol COMBINED_MAP_AND
+ Expect Symbol COMBINED_MAP_OR
+
+MAP - COMBINED FROM ONLY
+ Scan File ${MESSAGE} From=user@example.com
+ ... Settings={symbols_enabled = [COMBINED_MAP_AND, COMBINED_MAP_OR]}
+ Do Not Expect Symbol COMBINED_MAP_AND
+ Expect Symbol COMBINED_MAP_OR
+
+MAP - COMBINED MISS
+ Scan File ${MESSAGE} IP=11.1.0.10 From=user@other.com
+ ... Settings={symbols_enabled = [COMBINED_MAP_AND, COMBINED_MAP_OR]}
+ Do Not Expect Symbol COMBINED_MAP_AND
+ Do Not Expect Symbol COMBINED_MAP_OR
+
+MAP - FROM MISS
+ Scan File ${MESSAGE} From=user@other.com
+ ... Settings={symbols_enabled = [FROM_MAP]}
+ Do Not Expect Symbol FROM_MAP
+
+MAP - FROM REGEXP
+ Scan File ${MESSAGE} From=user123@test.com
+ ... Settings={symbols_enabled = [REGEXP_MAP]}
+ Expect Symbol REGEXP_MAP
+ Scan File ${MESSAGE} From=somebody@example.com
+ ... Settings={symbols_enabled = [REGEXP_MAP]}
+ Expect Symbol REGEXP_MAP
+
+MAP - FROM REGEXP MISS
+ Scan File ${MESSAGE} From=user@other.org
+ ... Settings={symbols_enabled = [REGEXP_MAP]}
+ Do Not Expect Symbol REGEXP_MAP
+
+MAP - RCPT DOMAIN HIT
+ Scan File ${MESSAGE} Rcpt=user@example.com
+ ... Settings={symbols_enabled = [RCPT_DOMAIN]}
+ Expect Symbol RCPT_DOMAIN
+
+MAP - RCPT DOMAIN MISS
+ Scan File ${MESSAGE} Rcpt=example.com@user
+ ... Settings={symbols_enabled = [RCPT_DOMAIN]}
+ Do Not Expect Symbol RCPT_DOMAIN
+
+MAP - RCPT USER HIT
+ Scan File ${MESSAGE} Rcpt=bob@example.com
+ ... Settings={symbols_enabled = [RCPT_USER]}
+ Expect Symbol RCPT_USER
+
+MAP - RCPT USER MISS
+ Scan File ${MESSAGE} From=example.com@bob
+ ... Settings={symbols_enabled = [RCPT_USER]}
+ Do Not Expect Symbol RCPT_USER
+
+MAP - DEPENDS HIT
+ Scan File ${MESSAGE} IP=88.99.142.95 From=user123@rspamd.com
+ ... Settings={symbols_enabled = [DEPS_MAP,REGEXP_MAP,FROM_MAP,SPF_CHECK]}
+ Expect Symbol DEPS_MAP
+
+MAP - DEPENDS MISS
+ Scan File ${MESSAGE} IP=1.2.3.4 From=user123@rspamd.com
+ ... Settings={symbols_enabled = [DEPS_MAP,REGEXP_MAP,FROM_MAP,SPF_CHECK]}
+ Do Not Expect Symbol DEPS_MAP
+
+MAP - MULSYM PLAIN
+ Scan File ${MESSAGE} Rcpt=user1@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP, SYM1]}
+ Expect Symbol RCPT_MAP
+
+MAP - MULSYM SCORE
+ Scan File ${MESSAGE} Rcpt=user2@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP, SYM1]}
+ Expect Symbol With Score RCPT_MAP 10.0
+
+MAP - MULSYM SYMBOL
+ Scan File ${MESSAGE} Rcpt=user3@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP, SYM1]}
+ Expect Symbol With Score SYM1 1.0
+
+MAP - MULSYM SYMBOL MISS
+ Scan File ${MESSAGE} Rcpt=user4@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP, SYM1]}
+ Expect Symbol With Score RCPT_MAP 1.0
+
+MAP - MULSYM SYMBOL + SCORE
+ Scan File ${MESSAGE} Rcpt=user5@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP, SYM1]}
+ Expect Symbol With Score SYM1 -10.1
+
+MAP - MULSYM SYMBOL + SCORE + OPTS
+ Scan File ${MESSAGE} Rcpt=user6@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP, SYM1]}
+ Expect Symbol With Score And Exact Options SYM1 -10.1 opt1 opt2
+
+MAP - UTF
+ Scan File ${UTF_MESSAGE}
+ ... Settings={symbols_enabled = [HEADER_MAP]}
+ Expect Symbol HEADER_MAP
+
+MAP - UTF MISS
+ Scan File ${MESSAGE}
+ ... Settings={symbols_enabled = [HEADER_MAP]}
+ Do Not Expect Symbol HEADER_MAP
+
+MAP - HOSTNAME
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=example.com
+ ... Settings={symbols_enabled = [HOSTNAME_MAP]}
+ Expect Symbol HOSTNAME_MAP
+
+MAP - HOSTNAME MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=rspamd.com
+ ... Settings={symbols_enabled = [HOSTNAME_MAP]}
+ Do Not Expect Symbol HOSTNAME_MAP
+
+MAP - TOP
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=example.com.au
+ ... Settings={symbols_enabled = [HOSTNAME_TOP_MAP]}
+ Expect Symbol HOSTNAME_TOP_MAP
+
+MAP - TOP MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=example.com.bg
+ ... Settings={symbols_enabled = [HOSTNAME_TOP_MAP]}
+ Do Not Expect Symbol HOSTNAME_TOP_MAP
+
+MAP - CDB - HOSTNAME
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=example.com
+ ... Settings={symbols_enabled = [CDB_HOSTNAME]}
+ Expect Symbol CDB_HOSTNAME
+
+MAP - CDB - HOSTNAME MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=rspamd.com
+ ... Settings={symbols_enabled = [CDB_HOSTNAME]}
+ Do Not Expect Symbol CDB_HOSTNAME
+
+MAP - REDIS - HOSTNAME
+ Redis HSET hostname redistest.example.net ${EMPTY}
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=redistest.example.net
+ ... Settings={symbols_enabled = [REDIS_HOSTNAME]}
+ Expect Symbol REDIS_HOSTNAME
+
+MAP - REDIS - HOSTNAME MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=rspamd.com
+ ... Settings={symbols_enabled = [REDIS_HOSTNAME]}
+ Do Not Expect Symbol REDIS_HOSTNAME
+
+MAP - REDIS - HOSTNAME - EXPANSION - HIT
+ Redis HSET 127.0.0.1.foo.com redistest.example.net ${EMPTY}
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=redistest.example.net Rcpt=bob@foo.com
+ ... Settings={symbols_enabled = [REDIS_HOSTNAME_EXPANSION]}
+ Expect Symbol REDIS_HOSTNAME_EXPANSION
+
+MAP - REDIS - HOSTNAME - EXPANSION - MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=redistest.example.net Rcpt=bob@bar.com
+ ... Settings={symbols_enabled = [REDIS_HOSTNAME_EXPANSION]}
+ Do Not Expect Symbol REDIS_HOSTNAME_EXPANSION
+
+MAP - REDIS - IP
+ Redis HSET ipaddr 127.0.0.1 ${EMPTY}
+ Scan File ${MESSAGE} IP=127.0.0.1
+ ... Settings={symbols_enabled = [REDIS_IPADDR]}
+ Expect Symbol REDIS_IPADDR
+
+MAP - REDIS - IP - MISS
+ Scan File ${MESSAGE} IP=8.8.8.8
+ ... Settings={symbols_enabled = [REDIS_IPADDR]}
+ Do Not Expect Symbol REDIS_IPADDR
+
+MAP - REDIS - FROM
+ Redis HSET emailaddr from@rspamd.tk ${EMPTY}
+ Scan File ${MESSAGE} From=from@rspamd.tk
+ ... Settings={symbols_enabled = [REDIS_FROMADDR]}
+ Expect Symbol REDIS_FROMADDR
+
+MAP - REDIS - FROM MISS
+ Scan File ${MESSAGE} From=user@other.com
+ ... Settings={symbols_enabled = [REDIS_FROMADDR]}
+ Do Not Expect Symbol REDIS_FROMADDR
+
+MAP - REDIS - URL TLD - HIT
+ Redis HSET hostname example.com ${EMPTY}
+ Scan File ${URL1}
+ ... Settings={symbols_enabled = [REDIS_URL_TLD]}
+ Expect Symbol REDIS_URL_TLD
+
+MAP - REDIS - URL TLD - MISS
+ Scan File ${URL2}
+ ... Settings={symbols_enabled = [REDIS_URL_TLD]}
+ Do Not Expect Symbol REDIS_URL_TLD
+
+MAP - REDIS - URL RE FULL - HIT
+ Redis HSET fullurlre html ${EMPTY}
+ Scan File ${URL2}
+ ... Settings={symbols_enabled = [REDIS_URL_RE_FULL]}
+ Expect Symbol REDIS_URL_RE_FULL
+
+MAP - REDIS - URL RE FULL - MISS
+ Scan File ${URL1}
+ ... Settings={symbols_enabled = [REDIS_URL_RE_FULL]}
+ Do Not Expect Symbol REDIS_URL_RE_FULL
+
+MAP - REDIS - URL FULL - HIT
+ Redis HSET fullurl https://www.example.com/foo?a=b ${EMPTY}
+ Scan File ${URL1}
+ ... Settings={symbols_enabled = [REDIS_URL_FULL]}
+ Expect Symbol REDIS_URL_FULL
+
+MAP - REDIS - URL FULL - MISS
+ Scan File ${URL2}
+ ... Settings={symbols_enabled = [REDIS_URL_FULL]}
+ Do Not Expect Symbol REDIS_URL_FULL
+
+MAP - REDIS - URL PHISHED - HIT
+ Redis HSET phishedurl www.rspamd.com ${EMPTY}
+ Scan File ${URL3}
+ ... Settings={symbols_enabled = [REDIS_URL_PHISHED]}
+ Expect Symbol REDIS_URL_PHISHED
+
+MAP - REDIS - URL PHISHED - MISS
+ Scan File ${URL4}
+ ... Settings={symbols_enabled = [REDIS_URL_PHISHED]}
+ Do Not Expect Symbol REDIS_URL_PHISHED
+
+MAP - REDIS - URL PLAIN REGEX - HIT
+ Redis HSET urlre www ${EMPTY}
+ Scan File ${URL3}
+ ... Settings={symbols_enabled = [REDIS_URL_RE_PLAIN]}
+ Expect Symbol REDIS_URL_RE_PLAIN
+
+MAP - REDIS - URL PLAIN REGEX - MISS
+ Scan File ${URL4}
+ ... Settings={symbols_enabled = [REDIS_URL_RE_PLAIN]}
+ Do Not Expect Symbol REDIS_URL_RE_PLAIN
+
+MAP - REDIS - URL TLD REGEX - HIT
+ Redis HSET tldre net ${EMPTY}
+ Scan File ${URL5}
+ ... Settings={symbols_enabled = [REDIS_URL_RE_TLD]}
+ Expect Symbol REDIS_URL_RE_TLD
+
+MAP - REDIS - URL TLD REGEX - MISS
+ Scan File ${URL4}
+ ... Settings={symbols_enabled = [REDIS_URL_RE_TLD]}
+ Do Not Expect Symbol REDIS_URL_RE_TLD
+
+MAP - REDIS - URL NOFILTER - HIT
+ Redis HSET urlnofilter www.example.net ${EMPTY}
+ Scan File ${URL5}
+ ... Settings={symbols_enabled = [REDIS_URL_NOFILTER]}
+ Expect Symbol REDIS_URL_NOFILTER
+
+MAP - REDIS - URL NOFILTER - MISS
+ Scan File ${URL4}
+ ... Settings={symbols_enabled = [REDIS_URL_NOFILTER]}
+ Do Not Expect Symbol REDIS_URL_NOFILTER
+
+MAP - REDIS - ASN - HIT
+ Redis HSET asn 15169 ${EMPTY}
+ Scan File ${MESSAGE} IP=8.8.8.8
+ ... Settings={symbols_enabled = [REDIS_ASN, ASN_CHECK]}
+ Expect Symbol REDIS_ASN
+
+MAP - REDIS - ASN - MISS
+ Scan File ${MESSAGE} IP=46.228.47.114
+ ... Settings={symbols_enabled = [REDIS_ASN, ASN_CHECK]}
+ Do Not Expect Symbol REDIS_ASN
+
+MAP - REDIS - CC - HIT
+ Redis HSET cc US ${EMPTY}
+ Scan File ${MESSAGE} IP=8.8.8.8
+ ... Settings={symbols_enabled = [REDIS_COUNTRY, ASN_CHECK]}
+ Expect Symbol REDIS_COUNTRY
+
+MAP - REDIS - CC - MISS
+ Scan File ${MESSAGE} IP=46.228.47.114
+ ... Settings={symbols_enabled = [REDIS_COUNTRY, ASN_CHECK]}
+ Do Not Expect Symbol REDIS_COUNTRY
+
+MAP - REDIS - ASN FILTERED - HIT
+ Redis HSET asn 1 ${EMPTY}
+ Scan File ${MESSAGE} IP=8.8.8.8
+ ... Settings={symbols_enabled = [REDIS_ASN_FILTERED, ASN_CHECK]}
+ Expect Symbol REDIS_ASN_FILTERED
+
+MAP - REDIS - ASN FILTERED - MISS
+ Scan File ${MESSAGE} IP=46.228.47.114
+ ... Settings={symbols_enabled = [REDIS_ASN_FILTERED, ASN_CHECK]}
+ Do Not Expect Symbol REDIS_ASN_FILTERED
+
+MAP - RECEIVED - IP MINMAX POS - ONE
+ Scan File ${RCVD1}
+ ... Settings={symbols_enabled = [RCVD_TEST_01, RCVD_TEST02]}
+ Expect Symbol RCVD_TEST_01
+ Do Not Expect Symbol RCVD_TEST_02
+
+# Relies on parsing of shitty received
+#MAP - RECEIVED - IP MINMAX POS - TWO / RCVD_AUTHED_ONE HIT
+# Scan File ${RCVD2}
+# Expect Symbol RCVD_TEST_02
+# Do Not Expect Symbol RCVD_TEST_01
+# Expect Symbol RCVD_AUTHED_ONE
+
+MAP - RECEIVED - REDIS
+ Redis HSET RCVD_TEST 2a01:7c8:aab6:26d:5054:ff:fed1:1da2 ${EMPTY}
+ Scan File ${RCVD1}
+ ... Settings={symbols_enabled = [RCVD_TEST_REDIS_01]}
+ Expect Symbol RCVD_TEST_REDIS_01
+
+RCVD_AUTHED_ONE & RCVD_AUTHED_TWO - MISS
+ Scan File ${RCVD3}
+ ... Settings={symbols_enabled = [RCVD_AUTHED_ONE, RCVD_AUTHED_TWO]}
+ Do Not Expect Symbol RCVD_AUTHED_ONE
+ Do Not Expect Symbol RCVD_AUTHED_TWO
+
+RCVD_AUTHED_TWO HIT / RCVD_AUTHED_ONE MISS
+ Scan File ${RCVD4}
+ ... Settings={symbols_enabled = [RCVD_AUTHED_ONE, RCVD_AUTHED_TWO]}
+ Expect Symbol RCVD_AUTHED_TWO
+ Do Not Expect Symbol RCVD_AUTHED_ONE
+
+FREEMAIL_CC
+ Scan File ${FREEMAIL_CC}
+ ... Settings={symbols_enabled = [FREEMAIL_CC]}
+ Expect Symbol With Score And Exact Options FREEMAIL_CC 19.00 test.com test1.com test2.com test3.com test4.com test5.com test6.com test7.com test8.com test9.com test10.com test11.com test12.com test13.com test14.com
+
+MAP - MULTISYMBOL DISABLED
+ Scan File ${MESSAGE} Rcpt=user3@example.com
+ ... Settings={symbols_enabled = [RCPT_MAP_NOMULTISYM, SYM1]}
+ Expect Symbol With Exact Options RCPT_MAP_NOMULTISYM user3@example.com SYM1
+
+MAP - EXTERNAL
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=example.com.au
+ ... Settings={symbols_enabled = [EXTERNAL_MULTIMAP]}
+ Expect Symbol EXTERNAL_MULTIMAP
+
+MAP - EXTERNAL MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=example.com.bg
+ ... Settings={symbols_enabled = [EXTERNAL_MULTIMAP]}
+ Do Not Expect Symbol EXTERNAL_MULTIMAP
+
+MAP - DYNAMIC SYMBOLS - SYM1
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=foo
+ ... Settings={symbols_enabled = [DYN_TEST1,DYN_TEST2,DYN_MULTIMAP]}
+ Expect Symbol DYN_TEST1
+ Do Not Expect Symbol DYN_TEST2
+
+MAP - DYNAMIC SYMBOLS - SYM2
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=bar
+ ... Settings={symbols_enabled = [DYN_TEST1,DYN_TEST2,DYN_MULTIMAP]}
+ Expect Symbol DYN_TEST2
+ Do Not Expect Symbol DYN_TEST1
+
+MAP - DYNAMIC SYMBOLS - MISS
+ Scan File ${MESSAGE} IP=127.0.0.1 Hostname=baz
+ ... Settings={symbols_enabled = [DYN_TEST1,DYN_TEST2,DYN_MULTIMAP]}
+ Do Not Expect Symbol DYN_TEST2
+ Do Not Expect Symbol DYN_TEST1
diff --git a/test/functional/cases/001_merged/104_get_from.robot b/test/functional/cases/001_merged/104_get_from.robot
new file mode 100644
index 0000000..f01119f
--- /dev/null
+++ b/test/functional/cases/001_merged/104_get_from.robot
@@ -0,0 +1,57 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${OPTIONS1} ,user@example.org,user,example.org
+${OPTIONS2} First Last,user@example.org,user,example.org
+${OPTIONS3} First M. Last,user@example.org,user,example.org
+${SETTINGS_GETFROM} {symbols_enabled = [${SYMBOL}]}
+${SYMBOL} GET_FROM
+
+*** Test Cases ***
+task:get_from('mime') - address only
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol ${SYMBOL}
+
+task:get_from('mime') - comment
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_comment.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS1}
+
+task:get_from('mime') - display name
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_dn.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS2}
+
+task:get_from('mime') - display name Base64
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_dn_base64.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} Кириллица,user@example.org,user,example.org
+
+task:get_from('mime') - display name and comment
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_dn_comment.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS2}
+
+task:get_from('mime') - quoted display name
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_quoted_dn.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS3}
+
+task:get_from('mime') - quoted display name and comment
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_quoted_dn_comment.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS3}
+
+task:get_from('mime') - quoted in the middle of DN (outer spaces)
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_quoted_dn_middle.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS3}
+
+task:get_from('mime') - quoted in the middle of DN (inner spaces)
+ Scan File ${RSPAMD_TESTDIR}/messages/from/from_quoted_dn_middle_inner.eml
+ ... Settings=${SETTINGS_GETFROM}
+ Expect Symbol With Exact Options ${SYMBOL} ${OPTIONS3}
diff --git a/test/functional/cases/001_merged/105_mimetypes.robot b/test/functional/cases/001_merged/105_mimetypes.robot
new file mode 100644
index 0000000..e16150a
--- /dev/null
+++ b/test/functional/cases/001_merged/105_mimetypes.robot
@@ -0,0 +1,74 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_MIMETYPES} {symbols_enabled = [MIME_TYPES_CALLBACK]}
+
+*** Test Cases ***
+Zip
+ Scan File ${RSPAMD_TESTDIR}/messages/zip.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_BAD_EXTENSION exe
+
+Zip Double Bad Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/zip-doublebad.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_DOUBLE_BAD_EXTENSION .pdf.exe
+
+Next-to-last Double Bad Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/next2last-doublebad.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_DOUBLE_BAD_EXTENSION .scr.xz
+
+Date is followed by Bad Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/rar-date-bad-ext.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_BAD_EXTENSION scr
+ Do Not Expect Symbol MIME_DOUBLE_BAD_EXTENSION
+
+Dotted file name is followed by Bad Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/bad_ext.dotted_file_name.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_BAD_EXTENSION exe
+ Do Not Expect Symbol MIME_DOUBLE_BAD_EXTENSION
+
+Dotted numbers in parentheses is followed by Bad Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/next2last-digits_in_parens.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_BAD_EXTENSION msi
+ Do Not Expect Symbol MIME_DOUBLE_BAD_EXTENSION
+
+Dotted numbers in square brackets is followed by Bad Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/next2last-digits_in_brackets.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_BAD_EXTENSION msi
+ Do Not Expect Symbol MIME_DOUBLE_BAD_EXTENSION
+
+Rar4
+ Scan File ${RSPAMD_TESTDIR}/messages/rar4.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_BAD_EXTENSION exe
+
+Cloaked Archive Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/f.zip.gz.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Expect Symbol With Exact Options MIME_ARCHIVE_IN_ARCHIVE .zip.gz zip
+
+Multipart Archive Extension
+ Scan File ${RSPAMD_TESTDIR}/messages/f.zip.001.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Do Not Expect Symbol MIME_ARCHIVE_IN_ARCHIVE
+
+Exe file, but name in filename_whitelist
+ Scan File ${RSPAMD_TESTDIR}/messages/exe_attm.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Do Not Expect Symbol MIME_BAD_EXTENSION
+ Do Not Expect Symbol MIME_BAD_ATTACHMENT
+ Do Not Expect Symbol MIME_DOUBLE_BAD_EXTENSION
+
+Empty text part should not be treat as html
+ Scan File ${RSPAMD_TESTDIR}/messages/empty-plain-text.eml
+ ... Settings=${SETTINGS_MIMETYPES}
+ Do Not Expect Symbol FORGED_OUTLOOK_HTML
diff --git a/test/functional/cases/001_merged/106_mid.robot b/test/functional/cases/001_merged/106_mid.robot
new file mode 100644
index 0000000..c5510bb
--- /dev/null
+++ b/test/functional/cases/001_merged/106_mid.robot
@@ -0,0 +1,36 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_MID} {symbols_enabled = [DKIM_CHECK,INVALID_MSGID,INVALID_MSGID_ALLOWED,KNOWN_NO_MID,KNOWN_MID,MISSING_MID,MISSING_MID_ALLOWED]}
+
+*** Test Cases ***
+MID - invalid Message-ID
+ Scan File ${RSPAMD_TESTDIR}/messages/fws_fp.eml
+ ... Settings=${SETTINGS_MID}
+ Expect Symbol With Score INVALID_MSGID 1.70
+ Do Not Expect Symbol MISSING_MID
+ Do Not Expect Symbol INVALID_MSGID_ALLOWED
+
+MID - invalid Message-ID allowed
+ Scan File ${RSPAMD_TESTDIR}/messages/invalid_mid_allowed.eml
+ ... Settings=${SETTINGS_MID}
+ Expect Symbol With Score INVALID_MSGID_ALLOWED 0.00
+ Do Not Expect Symbol MISSING_MID
+ Do Not Expect Symbol INVALID_MSGID
+
+MID - missing Message-ID
+ Scan File ${RSPAMD_TESTDIR}/messages/freemail.eml
+ ... Settings=${SETTINGS_MID}
+ Expect Symbol With Score MISSING_MID 2.50
+ Do Not Expect Symbol MISSING_MID_ALLOWED
+ Do Not Expect Symbol INVALID_MSGID
+
+MID - missing Message-ID allowed
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/onsubdomain_pass_relaxed.eml
+ ... Settings=${SETTINGS_MID}
+ Expect Symbol With Score MISSING_MID_ALLOWED 0.00
+ Do Not Expect Symbol MISSING_MID
+ Do Not Expect Symbol INVALID_MSGID
diff --git a/test/functional/cases/001_merged/114_phishing.robot b/test/functional/cases/001_merged/114_phishing.robot
new file mode 100644
index 0000000..bc7f398
--- /dev/null
+++ b/test/functional/cases/001_merged/114_phishing.robot
@@ -0,0 +1,26 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE1} ${RSPAMD_TESTDIR}/messages/phishing1.eml
+${MESSAGE2} ${RSPAMD_TESTDIR}/messages/phishing2.eml
+${MESSAGE3} ${RSPAMD_TESTDIR}/messages/phishing3.eml
+${SETTINGS_PHISHING} {symbols_enabled = [PHISHING,STRICT_PHISHING,STRICTER_PHISHING]}
+
+*** Test Cases ***
+TEST PHISHING
+ Scan File ${MESSAGE1}
+ ... Settings=${SETTINGS_PHISHING}
+ Expect Symbol PHISHING
+
+TEST PHISHING STRICT ONE
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_PHISHING}
+ Expect Symbol STRICT_PHISHING
+
+TEST PHISHING STRICT TWO
+ Scan File ${MESSAGE3}
+ ... Settings=${SETTINGS_PHISHING}
+ Expect Symbol STRICTER_PHISHING
diff --git a/test/functional/cases/001_merged/115_dmarc.robot b/test/functional/cases/001_merged/115_dmarc.robot
new file mode 100644
index 0000000..e5ed670
--- /dev/null
+++ b/test/functional/cases/001_merged/115_dmarc.robot
@@ -0,0 +1,101 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${DMARC_SETTINGS} {symbols_enabled = [DMARC_CHECK, DKIM_CHECK, SPF_CHECK]}
+
+*** Test Cases ***
+DMARC NONE PASS DKIM
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/pass_none.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_ALLOW
+
+DMARC NONE PASS SPF
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+ ... IP=8.8.4.4 From=foo@spf.cacophony.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_ALLOW
+
+DMARC NONE FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_SOFTFAIL
+
+DMARC REJECT FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/fail_reject.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_REJECT
+
+DMARC QUARANTINE FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/fail_quarantine.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_QUARANTINE
+
+DMARC SP NONE FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/subdomain_fail_none.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_SOFTFAIL
+
+DMARC SP REJECT FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/subdomain_fail_reject.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_REJECT
+
+DMARC SP QUARANTINE FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/subdomain_fail_quarantine.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_QUARANTINE
+
+DMARC SUBDOMAIN FAIL DKIM STRICT ALIGNMENT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/onsubdomain_fail_alignment.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_REJECT
+
+DMARC SUBDOMAIN PASS DKIM RELAXED ALIGNMENT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/onsubdomain_pass_relaxed.eml
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_ALLOW
+
+DMARC SUBDOMAIN PASS SPF STRICT ALIGNMENT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/onsubdomain_fail_alignment.eml
+ ... IP=37.48.67.26 From=foo@yo.mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_ALLOW
+
+DMARC SUBDOMAIN FAIL SPF STRICT ALIGNMENT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/onsubdomain_fail_alignment.eml
+ ... IP=37.48.67.26 From=foo@mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_REJECT
+
+DMARC SUBDOMAIN PASS SPF RELAXED ALIGNMENT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/onsubdomain_fail.eml
+ ... IP=37.48.67.26 From=foo@mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_ALLOW
+
+DMARC DNSFAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/dmarc_tmpfail.eml
+ ... IP=37.48.67.26 From=foo@mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_DNSFAIL
+
+DMARC NA NXDOMAIN
+ Scan File ${RSPAMD_TESTDIR}/messages/utf.eml
+ ... IP=37.48.67.26 From=foo@mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_NA
+
+DMARC PCT ZERO REJECT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/pct_none.eml
+ ... IP=37.48.67.26 From=foo@mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_QUARANTINE
+
+DMARC PCT ZERO SP QUARANTINE
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/pct_none1.eml
+ ... IP=37.48.67.26 From=foo@mom.za.org
+ ... Settings=${DMARC_SETTINGS}
+ Expect Symbol DMARC_POLICY_SOFTFAIL
diff --git a/test/functional/cases/001_merged/117_spf.robot b/test/functional/cases/001_merged/117_spf.robot
new file mode 100644
index 0000000..dda35f6
--- /dev/null
+++ b/test/functional/cases/001_merged/117_spf.robot
@@ -0,0 +1,157 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_SPF} {symbols_enabled = [SPF_CHECK]}
+
+*** Test Cases ***
+SPF FAIL UNRESOLVEABLE INCLUDE
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=37.48.67.26 From=x@fail3.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_FAIL
+
+SPF DNSFAIL FAILED INCLUDE UNALIGNED
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail2.org.org.za
+ ... Settings={symbols_enabled = [SPF_CHECK,DKIM_CHECK,DMARC_CHECK]}
+ Expect Symbol R_SPF_DNSFAIL
+ Expect Symbol DMARC_POLICY_SOFTFAIL
+
+SPF ALLOW UNRESOLVEABLE INCLUDE
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail3.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_ALLOW
+
+SPF ALLOW FAILED INCLUDE
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.4.4 From=x@fail2.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_ALLOW
+
+SPF NA NA
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_NA
+
+SPF NA NOREC
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@co.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_NA
+
+SPF NA NXDOMAIN
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@zzzzaaaa
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_NA
+
+SPF PERMFAIL UNRESOLVEABLE REDIRECT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail4.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_PERMFAIL
+
+SPF REDIRECT NO USEABLE ELEMENTS
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail10.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_PERMFAIL
+
+SPF DNSFAIL FAILED REDIRECT
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail1.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_DNSFAIL
+
+SPF PERMFAIL NO USEABLE ELEMENTS
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail5.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_PERMFAIL
+
+SPF FAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@example.net
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_FAIL
+
+SPF FAIL UNRESOLVEABLE MX
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=1.2.3.4 From=x@fail6.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_FAIL
+
+SPF FAIL UNRESOLVEABLE A
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=1.2.3.4 From=x@fail7.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_FAIL
+
+SPF DNSFAIL FAILED A
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=1.2.3.4 From=x@fail8.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_DNSFAIL
+
+SPF DNSFAIL FAILED MX
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=1.2.3.4 From=x@fail9.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_DNSFAIL
+
+SPF DNSFAIL FAILED RECORD
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=1.2.3.4 From=x@www.dnssec-failed.org
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_DNSFAIL
+
+SPF PASS INCLUDE
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@pass1.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_ALLOW
+
+SPF PTRS
+ Scan File /dev/null
+ ... IP=88.99.142.95 From=foo@crazyspf.cacophony.za.org
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_ALLOW
+ Scan File /dev/null
+ ... IP=128.66.0.1 From=foo@crazyspf.cacophony.za.org
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_FAIL
+ Scan File /dev/null
+ ... IP=209.85.216.182 From=foo@crazyspf.cacophony.za.org
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_FAIL
+ #Scan File /dev/null
+ #... IP=98.138.91.166 From=foo@crazyspf.cacophony.za.org
+ #Expect Symbol R_SPF_ALLOW
+ #Scan File /dev/null
+ #... IP=98.138.91.167 From=foo@crazyspf.cacophony.za.org
+ #Expect Symbol R_SPF_ALLOW
+ #Scan File /dev/null
+ #... IP=98.138.91.168 From=foo@crazyspf.cacophony.za.org
+ #Expect Symbol R_SPF_ALLOW
+
+SPF PERMFAIL REDIRECT WITHOUT SPF
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim4.eml
+ ... IP=192.0.2.1 From=a@fail1.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_DNSFAIL
+
+SPF EXTERNAL RELAY
+ Scan File ${RSPAMD_TESTDIR}/messages/external_relay.eml
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol With Score And Exact Options R_SPF_ALLOW -0.2 +ip4:37.48.67.26
+
+SPF UPPERCASE
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=8.8.8.8 From=x@fail11.org.org.za
+ ... Settings=${SETTINGS_SPF}
+ Expect Symbol R_SPF_ALLOW
diff --git a/test/functional/cases/001_merged/160_antivirus.robot b/test/functional/cases/001_merged/160_antivirus.robot
new file mode 100644
index 0000000..0870ba6
--- /dev/null
+++ b/test/functional/cases/001_merged/160_antivirus.robot
@@ -0,0 +1,151 @@
+*** Settings ***
+Suite Teardown Antivirus Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE2} ${RSPAMD_TESTDIR}/messages/freemail.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${SETTINGS_AVAST} {symbols_enabled = [AVAST_VIRUS]}
+${SETTINGS_CLAM} {symbols_enabled = [CLAM_VIRUS]}
+${SETTINGS_FPROT} {symbols_enabled = [FPROT_VIRUS, FPROT2_VIRUS_DUPLICATE_DEFAULT]}
+
+*** Test Cases ***
+CLAMAV MISS
+ Run Dummy Clam ${RSPAMD_PORT_CLAM}
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_CLAM}
+ Do Not Expect Symbol CLAM_VIRUS
+ Shutdown clamav
+
+CLAMAV HIT
+ Run Dummy Clam ${RSPAMD_PORT_CLAM} 1
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_CLAM}
+ Expect Symbol CLAM_VIRUS
+ Do Not Expect Symbol CLAMAV_VIRUS_FAIL
+ Shutdown clamav
+
+CLAMAV CACHE HIT
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_CLAM}
+ Expect Symbol CLAM_VIRUS
+ Do Not Expect Symbol CLAMAV_VIRUS_FAIL
+
+CLAMAV CACHE MISS
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_CLAM}
+ Do Not Expect Symbol CLAM_VIRUS
+ Do Not Expect Symbol CLAMAV_VIRUS_FAIL
+
+FPROT MISS
+ Run Dummy Fprot ${RSPAMD_PORT_FPROT}
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_FPROT}
+ Do Not Expect Symbol FPROT_VIRUS
+ Do Not Expect Symbol FPROT_EICAR
+ Shutdown fport
+
+FPROT HIT - PATTERN
+ Run Dummy Fprot ${RSPAMD_PORT_FPROT} 1
+ Run Dummy Fprot ${RSPAMD_PORT_FPROT2_DUPLICATE} 1 /tmp/dummy_fprot_dupe.pid
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_FPROT}
+ Expect Symbol FPROT_EICAR
+ # Also check ordered pattern match
+ Expect Symbol FPROT2_VIRUS_DUPLICATE_PATTERN
+ Do Not Expect Symbol FPROT2_VIRUS_DUPLICATE_DEFAULT
+ Do Not Expect Symbol FPROT2_VIRUS_DUPLICATE_NOPE
+ Shutdown fport
+ Shutdown fport duplicate
+
+FPROT CACHE HIT
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_FPROT}
+ Expect Symbol FPROT_EICAR
+ Do Not Expect Symbol CLAMAV_VIRUS
+ # Also check ordered pattern match
+ Expect Symbol FPROT2_VIRUS_DUPLICATE_PATTERN
+ Do Not Expect Symbol FPROT2_VIRUS_DUPLICATE_DEFAULT
+
+FPROT CACHE MISS
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_FPROT}
+ Do Not Expect Symbol FPROT_VIRUS
+
+AVAST MISS
+ Run Dummy Avast ${RSPAMD_PORT_AVAST}
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_AVAST}
+ Do Not Expect Symbol AVAST_VIRUS
+ Shutdown avast
+
+AVAST HIT
+ Run Dummy Avast ${RSPAMD_PORT_AVAST} 1
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_AVAST}
+ Expect Symbol AVAST_VIRUS
+ Do Not Expect Symbol AVAST_VIRUS_FAIL
+ Shutdown avast
+
+AVAST CACHE HIT
+ Scan File ${MESSAGE2}
+ ... Settings=${SETTINGS_AVAST}
+ Expect Symbol AVAST_VIRUS
+ Do Not Expect Symbol AVAST_VIRUS_FAIL
+
+AVAST CACHE MISS
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_AVAST}
+ Do Not Expect Symbol AVAST_VIRUS
+ Do Not Expect Symbol AVAST_VIRUS_FAIL
+
+*** Keywords ***
+Antivirus Teardown
+ Shutdown clamav
+ Shutdown fport
+ Shutdown avast
+
+Shutdown clamav
+ ${clamav_pid} = Get File if exists /tmp/dummy_clamav.pid
+ Run Keyword if ${clamav_pid} Shutdown Process With Children ${clamav_pid}
+
+Shutdown fport
+ ${fport_pid} = Get File if exists /tmp/dummy_fprot.pid
+ Run Keyword if ${fport_pid} Shutdown Process With Children ${fport_pid}
+
+Shutdown fport duplicate
+ ${fport_pid} = Get File if exists /tmp/dummy_fprot_dupe.pid
+ Run Keyword if ${fport_pid} Shutdown Process With Children ${fport_pid}
+
+Shutdown avast
+ ${avast_pid} = Get File if exists /tmp/dummy_avast.pid
+ Run Keyword if ${avast_pid} Shutdown Process With Children ${avast_pid}
+
+Run Dummy
+ [Arguments] @{varargs}
+ ${process} = Start Process @{varargs}
+ ${pid} = Get From List ${varargs} -1
+ ${pass} = Run Keyword And Return Status Wait Until Created ${pid}
+ IF ${pass}
+ Return From Keyword
+ END
+ Wait For Process ${process}
+ ${res} = Get Process Result ${process}
+ Log To Console ${res.stdout}
+ Log To Console ${res.stderr}
+ Fail Dummy server failed to start
+
+Run Dummy Clam
+ [Arguments] ${port} ${found}= ${pid}=/tmp/dummy_clamav.pid
+ Run Dummy ${RSPAMD_TESTDIR}/util/dummy_clam.py ${port} ${found} ${pid}
+
+Run Dummy Fprot
+ [Arguments] ${port} ${found}= ${pid}=/tmp/dummy_fprot.pid
+ Run Dummy ${RSPAMD_TESTDIR}/util/dummy_fprot.py ${port} ${found} ${pid}
+
+Run Dummy Avast
+ [Arguments] ${port} ${found}= ${pid}=/tmp/dummy_avast.pid
+ Run Dummy ${RSPAMD_TESTDIR}/util/dummy_avast.py ${port} ${found} ${pid}
diff --git a/test/functional/cases/001_merged/240_redis.robot b/test/functional/cases/001_merged/240_redis.robot
new file mode 100644
index 0000000..3196dd0
--- /dev/null
+++ b/test/functional/cases/001_merged/240_redis.robot
@@ -0,0 +1,16 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${SETTINGS_REDIS} {symbols_enabled = [REDIS_TEST, SIMPLE_REDIS_ASYNC_TEST, SIMPLE_REDIS_ASYNC201809_TEST]}
+
+*** Test Cases ***
+Redis client
+ Redis SET test_key test value
+ Scan File ${MESSAGE}
+ Expect Symbol With Exact Options REDIS hello from lua on redis
+ Expect Symbol With Exact Options REDIS_ASYNC test value
+ Expect Symbol With Exact Options REDIS_ASYNC201809 test value
diff --git a/test/functional/cases/001_merged/250_dns.robot b/test/functional/cases/001_merged/250_dns.robot
new file mode 100644
index 0000000..d3e64cd
--- /dev/null
+++ b/test/functional/cases/001_merged/250_dns.robot
@@ -0,0 +1,21 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${SETTINGS_DNS} {symbols_enabled = [SIMPLE_DNS, SIMPLE_DNS_SYNC]}
+
+*** Test Cases ***
+Simple DNS request
+ Scan File ${MESSAGE} To-Resolve=example.com
+ ... Settings=${SETTINGS_DNS}
+ Expect Symbol With Exact Options DNS_SYNC 93.184.216.34
+ Expect Symbol With Exact Options DNS 93.184.216.34
+
+Faulty DNS request
+ Scan File ${MESSAGE} To-Resolve=not-resolvable.com
+ ... Settings=${SETTINGS_DNS}
+ Expect Symbol With Exact Options DNS_SYNC_ERROR requested record is not found
+ Expect Symbol With Exact Options DNS_ERROR requested record is not found
diff --git a/test/functional/cases/001_merged/270_selector.robot b/test/functional/cases/001_merged/270_selector.robot
new file mode 100644
index 0000000..fa3ab87
--- /dev/null
+++ b/test/functional/cases/001_merged/270_selector.robot
@@ -0,0 +1,19 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/subject1.eml
+
+*** Test Cases ***
+Newlines
+ Scan File ${MESSAGE} User=test@user.com Pass=all
+ ... Settings={symbols_enabled = [CONFIG_SELECTOR_RE_RCPT_SUBJECT, LUA_SELECTOR_RE]}
+ Expect Symbol CONFIG_SELECTOR_RE_RCPT_SUBJECT
+ Expect Symbol LUA_SELECTOR_RE
+
+Rspamd_text selector
+ Scan File ${MESSAGE}
+ ... Settings={symbols_enabled = [RSPAMD_TEXT_SELECTOR]}
+ Expect Symbol RSPAMD_TEXT_SELECTOR
diff --git a/test/functional/cases/001_merged/280_rules.robot b/test/functional/cases/001_merged/280_rules.robot
new file mode 100644
index 0000000..b7cbe81
--- /dev/null
+++ b/test/functional/cases/001_merged/280_rules.robot
@@ -0,0 +1,139 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE1} ${RSPAMD_TESTDIR}/messages/fws_fn.eml
+${MESSAGE2} ${RSPAMD_TESTDIR}/messages/fws_fp.eml
+${MESSAGE3} ${RSPAMD_TESTDIR}/messages/fws_tp.eml
+${MESSAGE4} ${RSPAMD_TESTDIR}/messages/broken_richtext.eml
+${MESSAGE5} ${RSPAMD_TESTDIR}/messages/badboundary.eml
+${MESSAGE6} ${RSPAMD_TESTDIR}/messages/pdf_encrypted.eml
+${MESSAGE7} ${RSPAMD_TESTDIR}/messages/pdf_js.eml
+${MESSAGE8} ${RSPAMD_TESTDIR}/messages/yand_forward.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/newlines.eml
+
+*** Test Cases ***
+Broken MIME
+ Scan File ${MESSAGE3} Settings={symbols_enabled = [MISSING_SUBJECT]}
+ Expect Symbol MISSING_SUBJECT
+
+Issue 2584
+ Scan File ${MESSAGE1}
+ ... Settings={symbols_enabled = [BROKEN_CONTENT_TYPE, MISSING_SUBJECT, R_MISSING_CHARSET]}
+ Do Not Expect Symbol BROKEN_CONTENT_TYPE
+ Do Not Expect Symbol MISSING_SUBJECT
+ Do Not Expect Symbol R_MISSING_CHARSET
+
+Issue 2349
+ Scan File ${MESSAGE2}
+ ... Settings={symbols_enabled = [MULTIPLE_UNIQUE_HEADERS]}
+ Do Not Expect Symbol MULTIPLE_UNIQUE_HEADERS
+
+Broken Rich Text
+ Scan File ${MESSAGE4}
+ ... Settings={symbols_enabled = [BROKEN_CONTENT_TYPE]}
+ Expect Symbol BROKEN_CONTENT_TYPE
+
+Broken boundary
+ Scan File ${MESSAGE4}
+ ... Settings={symbols_enabled = [BROKEN_CONTENT_TYPE]}
+ Expect Symbol BROKEN_CONTENT_TYPE
+
+PDF encrypted
+ Scan File ${MESSAGE6}
+ ... Settings={symbols_enabled = [PDF_ENCRYPTED]}
+ Expect Symbol PDF_ENCRYPTED
+
+PDF javascript
+ Scan File ${MESSAGE7}
+ ... Settings={symbols_enabled = [PDF_JAVASCRIPT]}
+ Expect Symbol PDF_JAVASCRIPT
+
+BITCOIN ADDR
+ Scan File ${RSPAMD_TESTDIR}/messages/btc.eml
+ ... Settings={symbols_enabled = [BITCOIN_ADDR]}
+ Expect Symbol BITCOIN_ADDR
+
+BITCOIN ADDR 2
+ Scan File ${RSPAMD_TESTDIR}/messages/btc2.eml
+ ... Settings={symbols_enabled = [BITCOIN_ADDR]}
+ Expect Symbol BITCOIN_ADDR
+
+BITCOIN ADDR 3
+ Scan File ${RSPAMD_TESTDIR}/messages/btc3.eml
+ ... Settings={symbols_enabled = [BITCOIN_ADDR]}
+ Expect Symbol BITCOIN_ADDR
+
+BITCOIN ADDR 4
+ Scan File ${RSPAMD_TESTDIR}/messages/btc4.eml
+ ... Settings={symbols_enabled = [BITCOIN_ADDR]}
+ Expect Symbol With Exact Options BITCOIN_ADDR 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2
+ ... bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq bitcoincash:qztslqhavnjcgth9zwu6dw0jjcfy4zahfy7vf0smwp
+
+RCVD_COUNT_ONE
+ Scan File ${RSPAMD_TESTDIR}/messages/btc.eml
+ ... Settings={symbols_enabled = [RCVD_COUNT_ONE]}
+ Expect Symbol RCVD_COUNT_ONE
+
+RCVD_COUNT_FIVE
+ Scan File ${RSPAMD_TESTDIR}/messages/yand_forward.eml
+ ... Settings={symbols_enabled = [RCVD_COUNT_ONE]}
+ Expect Symbol RCVD_COUNT_FIVE
+
+RCVD_COUNT_SEVEN
+ Scan File ${RSPAMD_TESTDIR}/messages/rcvd7.eml
+ ... Settings={symbols_enabled = [RCVD_COUNT_ONE]}
+ Expect Symbol RCVD_COUNT_SEVEN
+
+FROM_NEQ_ENVFROM
+ Scan File ${MESSAGE8} From=test@test.net
+ ... Settings={symbols_enabled = [FROM_NEQ_ENVFROM]}
+ Expect Symbol FROM_NEQ_ENVFROM
+
+PHISH_SENDER_A_1
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender.eml
+ ... Settings={symbols_enabled = [MULTIPLE_FROM]}
+ Expect Symbol With Score And Exact Options MULTIPLE_FROM 8.0 <any@attack.com> <admin@legitimate.com>
+
+PHISH_SENDER_A_2
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender.eml
+ ... Settings={symbols_enabled = [MULTIPLE_UNIQUE_HEADERS]}
+ Expect Symbol With Score And Exact Options MULTIPLE_UNIQUE_HEADERS 7.0 From
+
+PHISH_SENDER_B
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender2.eml
+ ... Settings={symbols_enabled = [BROKEN_HEADERS]}
+ Expect Symbol BROKEN_HEADERS
+
+PHISH_SENDER_C
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender3.eml
+ ... Settings={symbols_enabled = [BROKEN_HEADERS]}
+ Expect Symbol BROKEN_HEADERS
+
+PHISH_SENDER_D
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender4.eml
+ ... Settings={symbols_enabled = [BROKEN_HEADERS]}
+ Expect Symbol BROKEN_HEADERS
+
+PHISH_SENDER_E
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender5.eml
+ ... Settings={symbols_enabled = [MULTIPLE_FROM, DMARC_CHECK, DKIM_CHECK, SPF_CHECK]}
+ Expect Symbol MULTIPLE_FROM
+ Expect Symbol With Exact Options DMARC_NA Duplicate From header
+
+PHISH_SENDER_ROUTING_PART
+ Scan File ${RSPAMD_TESTDIR}/messages/phish_sender6.eml
+ ... Settings={symbols_enabled = [FROM_INVALID]}
+ Expect Symbol FROM_INVALID
+
+REPLYTO_ADDR_EQ_FROM
+ Scan File ${RSPAMD_TESTDIR}/messages/replyto_addr_eq_from.eml
+ ... Settings={symbols_enabled = [REPLYTO_ADDR_EQ_FROM]}
+ Expect Symbol REPLYTO_ADDR_EQ_FROM
+
+SUBJECT_HAS_CURRENCY
+ Scan File ${RSPAMD_TESTDIR}/messages/currency.eml
+ ... Settings={symbols_enabled = [SUBJECT_HAS_CURRENCY]}
+ Expect Symbol SUBJECT_HAS_CURRENCY
diff --git a/test/functional/cases/001_merged/281_fnames.robot b/test/functional/cases/001_merged/281_fnames.robot
new file mode 100644
index 0000000..bb600b1
--- /dev/null
+++ b/test/functional/cases/001_merged/281_fnames.robot
@@ -0,0 +1,13 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_FNAME} {symbols_enabled = [TEST_FNAME]}
+
+*** Test Cases ***
+FILE NAMES
+ Scan File ${RSPAMD_TESTDIR}/messages/fname.eml Settings=${SETTINGS_FNAME}
+ Expect Symbol With Option TEST_FNAME [삼성생명]2020.08.14 데일리 경제뉴스.pdf
+ Expect Symbol With Option TEST_FNAME 01029_402110_10620_RGT06902_PRT180ML_20200803_101820.pdf
diff --git a/test/functional/cases/001_merged/290_greylist.robot b/test/functional/cases/001_merged/290_greylist.robot
new file mode 100644
index 0000000..05ce3d3
--- /dev/null
+++ b/test/functional/cases/001_merged/290_greylist.robot
@@ -0,0 +1,25 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${SETTINGS_GREYLIST} {symbols_enabled = [GREYLIST_CHECK, GREYLIST_SAVE], symbols = [FOUR_POINTS]}
+
+*** Test Cases ***
+GREYLIST NEW
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_GREYLIST}
+ Expect Symbol With Option GREYLIST greylisted
+
+GREYLIST EARLY
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_GREYLIST}
+ Expect Symbol With Option GREYLIST greylisted
+
+GREYLIST PASS
+ Sleep 4s Wait greylisting timeout
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_GREYLIST}
+ Expect Symbol With Option GREYLIST pass
diff --git a/test/functional/cases/001_merged/300_rbl.robot b/test/functional/cases/001_merged/300_rbl.robot
new file mode 100644
index 0000000..8b19fae
--- /dev/null
+++ b/test/functional/cases/001_merged/300_rbl.robot
@@ -0,0 +1,98 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+
+*** Test Cases ***
+RBL FROM MISS
+ Scan File ${MESSAGE} IP=1.2.3.4
+ ... Settings={symbols_enabled = [FAKE_RBL_UNKNOWN_CHECK]}
+ Do Not Expect Symbol FAKE_RBL_CODE_2
+
+RBL FROM HIT
+ Scan File ${MESSAGE} IP=4.3.2.1
+ ... Settings={symbols_enabled = [FAKE_RBL_UNKNOWN_CHECK]}
+ Expect Symbol FAKE_RBL_CODE_2
+
+RBL FROM MULTIPLE HIT
+ Scan File ${MESSAGE} IP=4.3.2.3
+ ... Settings={symbols_enabled = [FAKE_RBL_UNKNOWN_CHECK]}
+ Expect Symbol FAKE_RBL_CODE_2
+ Expect Symbol FAKE_RBL_CODE_3
+
+RBL FROM UNKNOWN HIT
+ Scan File ${MESSAGE} IP=4.3.2.2
+ ... Settings={symbols_enabled = [FAKE_RBL_UNKNOWN_CHECK]}
+ Expect Symbol FAKE_RBL_FAKE_RBL_UNKNOWN
+
+RBL RECEIVED HIT
+ Scan File ${MESSAGE} IP=8.8.8.8
+ ... Settings={symbols_enabled = [FAKE_RECEIVED_RBL_FAKE_RBL_UNKNOWN]}
+ Expect Symbol FAKE_RECEIVED_RBL_CODE_3
+
+RBL FROM HIT WL
+ Scan File ${MESSAGE} IP=4.3.2.4
+ ... Settings={symbols_enabled = [FAKE_RBL_UNKNOWN, FAKE_WL_RBL_UNKNOWN]}
+ Do Not Expect Symbol FAKE_RBL_CODE_2
+ Expect Symbol With Exact Options FAKE_WL_RBL_CODE_2 4.3.2.4:from
+
+EMAILBL Compose Map 1
+ Scan File ${RSPAMD_TESTDIR}/messages/url14.eml
+ ... Settings={symbols_enabled = [RSPAMD_EMAILBL]}
+ Expect Symbol With Exact Options RSPAMD_EMAILBL dirty.sanchez.com:email
+
+EMAILBL Compose Map 2
+ Scan File ${RSPAMD_TESTDIR}/messages/url15.eml
+ ... Settings={symbols_enabled = [RSPAMD_EMAILBL]}
+ Expect Symbol With Exact Options RSPAMD_EMAILBL very.dirty.sanchez.com:email
+
+EMAILBL Compose Map 3
+ Scan File ${RSPAMD_TESTDIR}/messages/url16.eml
+ ... Settings={symbols_enabled = [RSPAMD_EMAILBL]}
+ Expect Symbol With Exact Options RSPAMD_EMAILBL 41.black.sanchez.com:email
+
+CONTENT URLS
+ Scan File ${RSPAMD_TESTDIR}/messages/content_url.eml
+ ... Settings={symbols_enabled = [URIBL_CONTENTONLY, URIBL_NOCONTENT, URIBL_WITHCONTENT]}
+ Expect Symbol With Exact Options URIBL_NOCONTENT example.org:url
+ Expect Symbol With Option URIBL_WITHCONTENT example.com:url
+ Expect Symbol With Option URIBL_WITHCONTENT example.org:url
+ Expect Symbol With Option URIBL_WITHCONTENT 8.8.8.8:url
+ Expect Symbol With Exact Options URIBL_CONTENTONLY example.com:url
+
+SELECTORS
+ Scan File ${RSPAMD_TESTDIR}/messages/btc.eml From=user@example.com Helo=example.org
+ ... Settings={symbols_enabled = [RBL_SELECTOR_SINGLE, RBL_SELECTOR_MULTIPLE]}
+ Expect Symbol With Exact Options RBL_SELECTOR_SINGLE example.org:selector
+ Expect Symbol With Option RBL_SELECTOR_MULTIPLE example.com:sel_from
+ Expect Symbol With Option RBL_SELECTOR_MULTIPLE example.org:sel_helo
+
+SELECTORS COMBINED
+ Scan File ${RSPAMD_TESTDIR}/messages/btc.eml From=user@example.org Helo=example.org
+ ... Settings={symbols_enabled = [RBL_SELECTOR_MULTIPLE]}
+ Expect Symbol With Option RBL_SELECTOR_MULTIPLE example.org:sel_from
+ Expect Symbol With Option RBL_SELECTOR_MULTIPLE example.org:sel_helo
+
+NUMERIC URLS
+ Scan File ${RSPAMD_TESTDIR}/messages/numeric_urls.eml
+ ... Settings={symbols_enabled = [URIBL_NUMERIC]}
+ Expect Symbol With Exact Options URIBL_NUMERIC 4.3.2.1:url
+
+NUMERIC URLS WITH IMAGES
+ Scan File ${RSPAMD_TESTDIR}/messages/numeric_urls.eml
+ ... Settings={symbols_enabled = [URIBL_NUMERIC_IMAGES]}
+ Expect Symbol With Exact Options URIBL_NUMERIC_IMAGES 4.3.2.1:url 12.11.10.9:url
+
+NUMERIC URLS WITH CONTENT
+ Scan File ${RSPAMD_TESTDIR}/messages/numeric_urls.eml
+ ... Settings={symbols_enabled = [URIBL_NUMERIC_CONTENT]}
+ Expect Symbol With Exact Options URIBL_NUMERIC_CONTENT 4.3.2.1:url 8.7.6.5:url
+
+NUMERIC URLS WITH EVERYTHING
+ Scan File ${RSPAMD_TESTDIR}/messages/numeric_urls.eml
+ ... IP=127.0.0.1
+ ... Settings={symbols_enabled = [URIBL_NUMERIC_EVERYTHING]}
+ Expect Symbol With Exact Options URIBL_NUMERIC_EVERYTHING 12.11.10.9:url 4.3.2.1:url 8.7.6.5:url
diff --git a/test/functional/cases/001_merged/310_udp.robot b/test/functional/cases/001_merged/310_udp.robot
new file mode 100644
index 0000000..e94ed60
--- /dev/null
+++ b/test/functional/cases/001_merged/310_udp.robot
@@ -0,0 +1,40 @@
+*** Settings ***
+Test Setup UDP Setup
+Test Teardown UDP Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${SETTINGS_UDP} {symbols_enabled = [UDP_FAIL,UDP_SENDTO,UDP_SUCCESS]}
+
+*** Test Cases ***
+Simple UDP request
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_UDP}
+ Expect Symbol With Exact Options UDP_SUCCESS helloworld
+
+Sendonly UDP request
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_UDP}
+ Expect Symbol UDP_SENDTO
+
+Errored UDP request
+ Scan File ${MESSAGE}
+ ... Settings=${SETTINGS_UDP}
+ Expect Symbol With Exact Options UDP_FAIL read timeout
+
+*** Keywords ***
+UDP Setup
+ Run Dummy UDP
+
+UDP Teardown
+ ${udp_pid} = Get File /tmp/dummy_udp.pid
+ Shutdown Process With Children ${udp_pid}
+
+Run Dummy UDP
+ [Arguments]
+ ${result} = Start Process ${RSPAMD_TESTDIR}/util/dummy_udp.py 5005
+ Wait Until Created /tmp/dummy_udp.pid
diff --git a/test/functional/cases/001_merged/321_arc_check.robot b/test/functional/cases/001_merged/321_arc_check.robot
new file mode 100644
index 0000000..3f6488b
--- /dev/null
+++ b/test/functional/cases/001_merged/321_arc_check.robot
@@ -0,0 +1,19 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_ARC} {symbols_enabled = [ARC_CALLBACK]}
+
+*** Test Cases ***
+ARC ALLOW CHECK
+ Scan File ${RSPAMD_TESTDIR}/messages/arcallow.eml
+ ... Settings=${SETTINGS_ARC}
+ Expect Symbol ARC_ALLOW
+
+ARC BAD CHECK
+ Scan File ${RSPAMD_TESTDIR}/messages/arcbad.eml
+ ... Settings=${SETTINGS_ARC}
+ Expect Symbol ARC_INVALID
+
diff --git a/test/functional/cases/001_merged/340_surbl.robot b/test/functional/cases/001_merged/340_surbl.robot
new file mode 100644
index 0000000..a1d0736
--- /dev/null
+++ b/test/functional/cases/001_merged/340_surbl.robot
@@ -0,0 +1,182 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_SURBL} {groups_enabled = [rbl]}
+
+*** Test Cases ***
+SURBL resolve ip
+ Scan File ${RSPAMD_TESTDIR}/messages/url7.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol With Exact Options URIBL_SBL_CSS 8.8.8.9:example.ru:url
+ Expect Symbol With Exact Options URIBL_XBL 8.8.8.8:example.ru:url
+ Expect Symbol With Exact Options URIBL_PBL 8.8.8.8:example.ru:url
+
+SURBL Example.com domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url4.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol With Exact Options RSPAMD_URIBL example.com:url
+ Expect Symbol With Exact Options DBL_SPAM example.com:url
+ Expect Symbol With Exact Options DBL_PHISH rspamd.tk:url
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL Example.net domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url5.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol DBL_PHISH
+ Do Not Expect Symbol DBL_SPAM
+ Do Not Expect Symbol RSPAMD_URIBL
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL Example.org domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url6.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol URIBL_BLACK
+ Do Not Expect Symbol DBL_SPAM
+ Do Not Expect Symbol RSPAMD_URIBL
+ Do Not Expect Symbol DBL_PHISH
+
+SURBL Example.ru domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url7.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol URIBL_GREY
+ Expect Symbol URIBL_RED
+ Do Not Expect Symbol DBL_SPAM
+ Do Not Expect Symbol RSPAMD_URIBL
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL Example.ru ZEN domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url7.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol URIBL_SBL_CSS
+ Expect Symbol URIBL_XBL
+ Expect Symbol URIBL_PBL
+ Do Not Expect Symbol URIBL_SBL
+ Do Not Expect Symbol DBL_SPAM
+ Do Not Expect Symbol RSPAMD_URIBL
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL Example.com domain image false
+ Scan File ${RSPAMD_TESTDIR}/messages/urlimage.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL_IMAGES
+ Do Not Expect Symbol DBL_SPAM
+ Do Not Expect Symbol RSPAMD_URIBL
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL @example.com mail html
+ Scan File ${RSPAMD_TESTDIR}/messages/mailadr.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+ Expect Symbol With Exact Options DBL_SPAM example.com:email
+ Do Not Expect Symbol RSPAMD_URIBL_IMAGES
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL @example.com mail text
+ Scan File ${RSPAMD_TESTDIR}/messages/mailadr2.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+ Expect Symbol With Exact Options DBL_SPAM example.com:email
+ Do Not Expect Symbol RSPAMD_URIBL_IMAGES
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL example.com not encoded url in subject
+ Scan File ${RSPAMD_TESTDIR}/messages/urlinsubject.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+ Expect Symbol DBL_SPAM
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL example.com encoded url in subject
+ Scan File ${RSPAMD_TESTDIR}/messages/urlinsubjectencoded.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+ Expect Symbol DBL_SPAM
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+WHITELIST
+ Scan File ${RSPAMD_TESTDIR}/messages/whitelist.eml
+ ... Settings=${SETTINGS_SURBL}
+ Do Not Expect Symbol RSPAMD_URIBL
+ Do Not Expect Symbol DBL_SPAM
+ Do Not Expect Symbol RSPAMD_URIBL_IMAGES
+
+EMAILBL full address & domain only
+ Scan File ${RSPAMD_TESTDIR}/messages/emailbltext.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_EMAILBL_FULL
+ Expect Symbol RSPAMD_EMAILBL_DOMAINONLY
+
+EMAILBL full subdomain address
+ Scan File ${RSPAMD_TESTDIR}/messages/emailbltext2.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_EMAILBL_FULL
+
+EMAILBL full subdomain address & domain only
+ Scan File ${RSPAMD_TESTDIR}/messages/emailbltext3.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol With Exact Options RSPAMD_EMAILBL_DOMAINONLY baddomain.com:email
+ Expect Symbol With Exact Options RSPAMD_EMAILBL_FULL user.subdomain.baddomain.com:email
+
+EMAILBL REPLY TO full address
+ Scan File ${RSPAMD_TESTDIR}/messages/replyto.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_EMAILBL_FULL
+ Do Not Expect Symbol RSPAMD_EMAILBL_DOMAINONLY
+
+EMAILBL REPLY TO domain only
+ Scan File ${RSPAMD_TESTDIR}/messages/replyto2.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_EMAILBL_DOMAINONLY
+ Do Not Expect Symbol RSPAMD_EMAILBL_FULL
+
+EMAILBL REPLY TO full subdomain address
+ Scan File ${RSPAMD_TESTDIR}/messages/replytosubdomain.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_EMAILBL_FULL
+ Do Not Expect Symbol RSPAMD_EMAILBL_DOMAINONLY
+
+SURBL IDN domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url8.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+ Expect Symbol DBL_SPAM
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL IDN Punycode domain
+ Scan File ${RSPAMD_TESTDIR}/messages/url9.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+ Expect Symbol DBL_SPAM
+ Do Not Expect Symbol DBL_PHISH
+ Do Not Expect Symbol URIBL_BLACK
+
+SURBL html entity&shy
+ Scan File ${RSPAMD_TESTDIR}/messages/url10.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol RSPAMD_URIBL
+
+SURBL url compose map 1
+ Scan File ${RSPAMD_TESTDIR}/messages/url11.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol With Exact Options BAD_SUBDOMAIN clean.dirty.sanchez.com:url
+
+SURBL url compose map 2
+ Scan File ${RSPAMD_TESTDIR}/messages/url12.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol With Exact Options BAD_SUBDOMAIN 4.very.dirty.sanchez.com:url
+
+SURBL url compose map 3
+ Scan File ${RSPAMD_TESTDIR}/messages/url13.eml
+ ... Settings=${SETTINGS_SURBL}
+ Expect Symbol With Exact Options BAD_SUBDOMAIN 41.black.sanchez.com:url
diff --git a/test/functional/cases/001_merged/350_magic.robot b/test/functional/cases/001_merged/350_magic.robot
new file mode 100644
index 0000000..66a18f2
--- /dev/null
+++ b/test/functional/cases/001_merged/350_magic.robot
@@ -0,0 +1,67 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${SETTINGS_MAGIC} {symbols_enabled = [MAGIC_SYM]}
+
+*** Test Cases ***
+Magic detections bundle 1
+ Scan File ${RSPAMD_TESTDIR}/messages/gargantua.eml Settings=${SETTINGS_MAGIC}
+ Expect Symbols MAGIC_SYM_ZIP_2
+ ... MAGIC_SYM_RAR_3
+ ... MAGIC_SYM_EXE_4
+ ... MAGIC_SYM_ELF_5
+ ... MAGIC_SYM_LNK_6
+ ... MAGIC_SYM_CLASS_7
+ ... MAGIC_SYM_RTF_8
+ ... MAGIC_SYM_PDF_9
+ ... MAGIC_SYM_PS_10
+ ... MAGIC_SYM_CHM_11
+ ... MAGIC_SYM_DJVU_12
+ ... MAGIC_SYM_ARJ_13
+ ... MAGIC_SYM_CAB_14
+ ... MAGIC_SYM_ACE_15
+ ... MAGIC_SYM_TAR_16
+ ... MAGIC_SYM_BZ2_17
+ ... MAGIC_SYM_XZ_18
+ ... MAGIC_SYM_LZ4_19
+ ... MAGIC_SYM_ZST_20
+ ... MAGIC_SYM_DMG_21
+ ... MAGIC_SYM_ISO_22
+ ... MAGIC_SYM_ZOO_23
+ ... MAGIC_SYM_EPUB_24
+ ... MAGIC_SYM_XAR_25
+ ... MAGIC_SYM_PSD_26
+ ... MAGIC_SYM_PCX_27
+ ... MAGIC_SYM_TIFF_28
+ ... MAGIC_SYM_ICO_29
+ ... MAGIC_SYM_SWF_30
+ ... MAGIC_SYM_DOC_31
+ ... MAGIC_SYM_XLS_32
+ ... MAGIC_SYM_PPT_33
+ ... MAGIC_SYM_MSI_34
+ ... MAGIC_SYM_MSG_35
+ ... MAGIC_SYM_DOCX_36
+ ... MAGIC_SYM_XLSX_37
+ ... MAGIC_SYM_PPTX_38
+ ... MAGIC_SYM_ODT_39
+ ... MAGIC_SYM_ODS_40
+ ... MAGIC_SYM_ODP_41
+ ... MAGIC_SYM_7Z_42
+ ... MAGIC_SYM_VSD_43
+ ... MAGIC_SYM_PNG_44
+ ... MAGIC_SYM_JPG_45
+ ... MAGIC_SYM_GIF_46
+ ... MAGIC_SYM_BMP_47
+ ... MAGIC_SYM_TXT_48
+ ... MAGIC_SYM_HTML_49
+ ... MAGIC_SYM_CSV_50
+ ... MAGIC_SYM_DWG_51
+ ... MAGIC_SYM_JAR_52
+ ... MAGIC_SYM_APK_53
+ ... MAGIC_SYM_BAT_54
+ ... MAGIC_SYM_ICS_55
+ ... MAGIC_SYM_VCF_56
+ ... MAGIC_SYM_CSV_57
diff --git a/test/functional/cases/001_merged/__init__.robot b/test/functional/cases/001_merged/__init__.robot
new file mode 100644
index 0000000..7ac640d
--- /dev/null
+++ b/test/functional/cases/001_merged/__init__.robot
@@ -0,0 +1,28 @@
+*** Settings ***
+Suite Setup Multi Setup
+Suite Teardown Rspamd Redis Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/merged.conf
+${REDIS_SCOPE} Suite
+${RSPAMD_EXTERNAL_RELAY_ENABLED} false
+${RSPAMD_MAP_MAP} ${RSPAMD_TESTDIR}/configs/maps/map.list
+${RSPAMD_RADIX_MAP} ${RSPAMD_TESTDIR}/configs/maps/ip2.list
+${RSPAMD_REGEXP_MAP} ${RSPAMD_TESTDIR}/configs/maps/regexp.list
+${RSPAMD_SCOPE} Suite
+
+*** Keywords ***
+Multi Setup
+ Run Redis
+ Run Dummy Http
+ Run Dummy Https
+ Rspamd Setup
+
+Multi Teardown
+ Rspamd Teardown
+ Dummy Http Teardown
+ Dummy Https Teardown
+ Redis Teardown \ No newline at end of file
diff --git a/test/functional/cases/101_lua.robot b/test/functional/cases/101_lua.robot
new file mode 100644
index 0000000..c820d1b
--- /dev/null
+++ b/test/functional/cases/101_lua.robot
@@ -0,0 +1,35 @@
+*** Settings ***
+Test Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_SCOPE} Test
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+Flags
+ [Setup] Lua Setup ${RSPAMD_TESTDIR}/lua/flags.lua
+ Scan File ${MESSAGE}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} stat
+ Should Contain ${result.stdout} Messages scanned: 0
+
+Dependencies
+ [Setup] Lua Setup ${RSPAMD_TESTDIR}/lua/deps.lua
+ Scan File ${MESSAGE}
+ Expect Symbol DEP10
+
+Pre and Post Filters
+ [Setup] Lua Setup ${RSPAMD_TESTDIR}/lua/prepostfilters.lua
+ Scan File ${MESSAGE}
+ Expect Symbol TEST_PRE
+ Expect Symbol TEST_POST
+
+*** Keywords ***
+Lua Setup
+ [Arguments] ${RSPAMD_LUA_SCRIPT}
+ Set Test Variable ${RSPAMD_LUA_SCRIPT}
+ Rspamd Setup
diff --git a/test/functional/cases/103_password.robot b/test/functional/cases/103_password.robot
new file mode 100644
index 0000000..2237a6a
--- /dev/null
+++ b/test/functional/cases/103_password.robot
@@ -0,0 +1,71 @@
+*** Settings ***
+Test Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/password.conf
+${CONTROLLER_ERRORS} False
+${RSPAMD_CATENA_PASSWORD} "$2$xu1581gidj5cyp4yjgo68qbj6jz1j8o3$j9yg4k58jy3fj8suijxx9d7pea6a6obtufq9kfenosyq8erm87ky"
+${RSPAMD_PBKDF_PASSWORD} "$1$rhzzahtm8a5homdhh7z4qiiy7j8pzp4u$k5toro136brshjjuy9t39r785td69qodmd39qzygxuyehn9tqauy"
+${RSPAMD_ENABLE_CATENA_PASSWORD} "$2$irotou88u89r1gj53pqnom96qo36hgkn$d4dt3466db7ccqx96k18yz9b1brx8hmk3b4w6erf4oqpmf9sag6y"
+${RSPAMD_SCOPE} Test
+
+*** Test Cases ***
+PASSWORD - PBKDF
+ [Setup] Password Setup ${RSPAMD_PBKDF_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq1 stat
+ Check Rspamc ${result} Messages scanned:
+
+PASSWORD - PBKDF WRONG
+ [Setup] Password Setup ${RSPAMD_PBKDF_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P q1q1 stat
+ Should Be Equal As Integers ${result.rc} 1
+
+PASSWORD - CATENA
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq1 stat
+ Check Rspamc ${result} Messages scanned:
+
+PASSWORD - CATENA WRONG
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P q stat
+ Should Be Equal As Integers ${result.rc} 1
+
+PASSWORD - ENABLE
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq2 stat_reset
+ Check Rspamc ${result} Messages scanned:
+
+PASSWORD - ENABLE WITH NORMAL
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq1 stat_reset
+ Should Be Equal As Integers ${result.rc} 1
+
+PASSWORD - ENABLE INCORRECT
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_ENABLE_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P q2q2 stat_reset
+ Should Be Equal As Integers ${result.rc} 1
+
+PASSWORD - ENABLE EQUAL TO NORMAL PRIV COMMAND
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq1 stat_reset
+ Check Rspamc ${result} Messages scanned:
+
+PASSWORD - ENABLE EQUAL TO NORMAL NON PRIV COMMAND
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq1 stat
+ Check Rspamc ${result} Messages scanned:
+
+PASSWORD - ENABLE EQUAL TO NORMAL WRONG PASSWORD
+ [Setup] Password Setup ${RSPAMD_CATENA_PASSWORD} ${RSPAMD_CATENA_PASSWORD}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -P nq2 stat
+ Should Be Equal As Integers ${result.rc} 1
+
+*** Keywords ***
+Password Setup
+ [Arguments] ${RSPAMD_PASSWORD} ${RSPAMD_ENABLE_PASSWORD}
+ Set Test Variable ${RSPAMD_PASSWORD}
+ Set Test Variable ${RSPAMD_ENABLE_PASSWORD}
+ Rspamd Setup
diff --git a/test/functional/cases/108_settings.robot b/test/functional/cases/108_settings.robot
new file mode 100644
index 0000000..1b51343
--- /dev/null
+++ b/test/functional/cases/108_settings.robot
@@ -0,0 +1,268 @@
+*** Settings ***
+Suite Setup Settings Setup
+Suite Teardown Settings Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/settings.conf
+${HAM_MESSAGE} ${RSPAMD_TESTDIR}/messages/ham.eml
+${MESSAGE_7BIT} ${RSPAMD_TESTDIR}/messages/utf.eml
+${MESSAGE_ABSENT_MIME} ${RSPAMD_TESTDIR}/messages/ed25519.eml
+${MESSAGE_CUSTOM_HDR} ${RSPAMD_TESTDIR}/messages/empty-plain-text.eml
+${MESSAGE_PRIORITY} ${RSPAMD_TESTDIR}/messages/priority.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/settings.lua
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+${SPAM_MESSAGE} ${RSPAMD_TESTDIR}/messages/spam.eml
+
+*** Keywords ***
+Check Everything Disabled
+ Expect Action no action
+ Do Not Expect Symbol SIMPLE_VIRTUAL
+ Do Not Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol BAYES_SPAM
+
+*** Test Cases ***
+NO SETTINGS SPAM
+ Scan File ${SPAM_MESSAGE}
+ Expect Symbol SIMPLE_TEST
+ Expect Symbol SIMPLE_VIRTUAL
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Expect Symbol SIMPLE_PRE
+ Expect Symbol SIMPLE_POST
+ Expect Symbol BAYES_SPAM
+
+NO SETTINGS HAM
+ Scan File ${HAM_MESSAGE}
+ Expect Symbol SIMPLE_TEST
+ Expect Symbol SIMPLE_PRE
+ Expect Symbol SIMPLE_POST
+ Expect Symbol BAYES_HAM
+
+EMPTY SYMBOLS ENABLED - STATIC
+ Scan File ${SPAM_MESSAGE} IP=5.5.5.5
+ Check Everything Disabled
+
+EMPTY GROUPS ENABLED - STATIC
+ Scan File ${SPAM_MESSAGE} IP=5.5.5.6
+ Check Everything Disabled
+
+EMPTY SYMBOLS ENABLED - SETTINGS-ID
+ Scan File ${SPAM_MESSAGE} Settings-Id=empty_symbols_enabled
+ Check Everything Disabled
+
+EMPTY GROUPS ENABLED - SETTINGS-ID
+ Scan File ${SPAM_MESSAGE} Settings-Id=empty_groups_enabled
+ Check Everything Disabled
+
+ENABLE SYMBOL - NORMAL
+ Scan File ${HAM_MESSAGE} Settings={symbols_enabled = ["SIMPLE_TEST"]}
+ Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol BAYES_HAM
+
+ENABLE SYMBOL - POSTFILTER
+ Scan File ${HAM_MESSAGE} Settings={symbols_enabled = ["SIMPLE_TEST", "SIMPLE_POST"]}
+ Expect Symbol SIMPLE_TEST
+ Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol BAYES_HAM
+
+ENABLE SYMBOL - PREFILTER
+ Scan File ${HAM_MESSAGE} Settings={symbols_enabled = ["SIMPLE_PRE"]}
+ Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol BAYES_HAM
+
+ENABLE SYMBOL - CLASSIFIER
+ Scan File ${HAM_MESSAGE} Settings={symbols_enabled = ["BAYES_HAM", "BAYES_SPAM"]}
+ Expect Symbol BAYES_HAM
+ Do Not Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_TEST
+
+DISABLE SYMBOL - NORMAL
+ Scan File ${MESSAGE} Settings={symbols_disabled = ["SIMPLE_TEST"]}
+ Do Not Expect Symbol SIMPLE_TEST
+ Expect Symbol SIMPLE_PRE
+ Expect Symbol SIMPLE_POST
+
+RESCORE SYMBOL - NORMAL
+ Scan File ${MESSAGE} Settings={SIMPLE_TEST = 3.33}
+ Expect Symbol With Score SIMPLE_TEST 3.33
+
+INJECT SYMBOL - NORMAL
+ Scan File ${MESSAGE} Settings={symbols = ["INJECTED_SYMBOL1", "INJECTED_SYMBOL2"]}
+ Expect Symbol INJECTED_SYMBOL1
+ Expect Symbol INJECTED_SYMBOL2
+
+RESCORE ACTION
+ Scan File ${MESSAGE} Settings={actions { reject = 1234.5; } }
+ Expect Required Score 1234.5
+
+DISABLE GROUP - NORMAL
+ Scan File ${MESSAGE} Settings={groups_disabled = ["b"]}
+ Do Not Expect Symbol SIMPLE_TEST
+ Expect Symbol SIMPLE_PRE
+ Expect Symbol SIMPLE_POST
+
+ENABLE GROUP - NORMAL
+ Scan File ${MESSAGE} Settings={groups_enabled = ["b"]}
+ Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_POST
+
+SETTINGS ID - NORMAL
+ Scan File ${MESSAGE} Settings-Id=id_test
+ Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_POST
+
+SETTINGS ID - PRE
+ Scan File ${MESSAGE} Settings-Id=id_pre
+ Expect Symbol SIMPLE_PRE
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_POST
+
+SETTINGS ID - VIRTUAL
+ Scan File ${MESSAGE} Settings-Id=id_virtual
+ Expect Symbol SIMPLE_VIRTUAL
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL GROUP
+ Scan File ${MESSAGE} Settings-Id=id_virtual_group
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL FROM
+ Scan File ${MESSAGE} From=test2@example.com
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL USER
+ Scan File ${MESSAGE} User=test@example.com
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL HOSTNAME
+ Scan File ${MESSAGE} Hostname=example.com
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL SELECTOR
+ Scan File ${MESSAGE} Rcpt=test3@example.com
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - ANGLED RECIPIENT
+ Scan File ${MESSAGE} Rcpt=<test3@example.com>
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL HEADER MATCH
+ Scan File ${MESSAGE_7BIT}
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Expect Symbol With Score EXPLICIT_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL HEADER EXISTS
+ Scan File ${MESSAGE_CUSTOM_HDR}
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL HEADER ABSENT
+ Scan File ${MESSAGE_ABSENT_MIME}
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL REQUEST HEADER
+ Scan File ${MESSAGE} Test=passed
+ Expect Symbol With Score SIMPLE_VIRTUAL 10
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL1
+ Do Not Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - VIRTUAL DEP
+ Scan File ${MESSAGE} Settings-Id=id_virtual1
+ Expect Symbol EXPLICIT_VIRTUAL1
+ Expect Symbol DEP_VIRTUAL
+ Expect Symbol DEP_REAL
+ Do Not Expect Symbol SIMPLE_TEST
+ Do Not Expect Symbol SIMPLE_VIRTUAL
+ Do Not Expect Symbol SIMPLE_POST
+ Do Not Expect Symbol SIMPLE_PRE
+
+SETTINGS ID - EXTERNAL MAP
+ Scan File ${MESSAGE} Settings-Id=external
+ Expect Symbol EXTERNAL_SETTINGS
+
+PRIORITY
+ Scan File ${MESSAGE_PRIORITY} Settings-Id=id_virtual_group From=user@test.com
+ Expect Symbol PRIORITY_2
+
+
+*** Keywords ***
+Settings Setup
+ Copy File ${RSPAMD_TESTDIR}/data/bayes.spam.sqlite3 /tmp/bayes.spam.sqlite3
+ Copy File ${RSPAMD_TESTDIR}/data/bayes.ham.sqlite3 /tmp/bayes.ham.sqlite3
+ Run Dummy Http
+ Rspamd Setup
+
+Settings Teardown
+ Rspamd Teardown
+ Dummy Http Teardown
+ Remove Files /tmp/bayes.spam.sqlite3 /tmp/bayes.ham.sqlite3
diff --git a/test/functional/cases/109_composites.robot b/test/functional/cases/109_composites.robot
new file mode 100644
index 0000000..4d73cf4
--- /dev/null
+++ b/test/functional/cases/109_composites.robot
@@ -0,0 +1,78 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/composites.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/composites.lua
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+Composites - All in One
+ Scan File ${MESSAGE}
+ Expect Symbols With Scores EXPRESSIONS=5
+ ... EXPRESSIONS_B=0
+ ... POLICY_REMOVE_WEIGHT=5
+ ... POLICY_REMOVE_WEIGHT_B=0
+ ... POLICY_FORCE_REMOVE=5.00
+ ... POLICY_FORCE_REMOVE_A=1.00
+ ... POLICY_LEAVE=5.00
+ ... POLICY_LEAVE_B=1.00
+ ... DEFAULT_POLICY_REMOVE_WEIGHT=5.00
+ ... DEFAULT_POLICY_REMOVE_WEIGHT_A=0.00
+ ... DEFAULT_POLICY_REMOVE_WEIGHT_B=0.00
+ ... DEFAULT_POLICY_REMOVE_SYMBOL=5.00
+ ... DEFAULT_POLICY_LEAVE=5.00
+ ... DEFAULT_POLICY_LEAVE_A=1.00
+ ... DEFAULT_POLICY_LEAVE_B=1.00
+ ... SYMBOL_GROUPS=5.00
+ ... POSITIVE_A=-1.00
+ ... ANY_A=-1.00
+ ... NEGATIVE_B=1.00
+ Do Not Expect Symbols DEFAULT_POLICY_REMOVE_SYMBOL_A
+ ... DEFAULT_POLICY_REMOVE_SYMBOL_B
+ ... NEGATIVE_A
+ ... POLICY_REMOVE_WEIGHT_A
+ ... POLICY_FORCE_REMOVE_B
+ ... POLICY_LEAVE_A
+ Expect Score 50
+ Expect Required Score To Be Null
+
+Composites - Opts Plain
+ Scan File ${MESSAGE} opts=sym1
+ Expect Symbol With Score SYMOPTS1 5.00
+ Do Not Expect Symbol SYMOPTS2
+
+Composites - Opts RE Miss one
+ Scan File ${MESSAGE} opts=sym1,foo1
+ Expect Symbol With Score SYMOPTS1 5.00
+ Do Not Expect Symbol SYMOPTS2
+ Do Not Expect Symbol SYMOPTS3
+
+Composites - Opts RE Miss both
+ Scan File ${MESSAGE} opts=sym2
+ Do Not Expect Symbol SYMOPTS1
+ Do Not Expect Symbol SYMOPTS2
+ Do Not Expect Symbol SYMOPTS3
+
+Composites - Opts RE Hit
+ Scan File ${MESSAGE} opts=foo1,sym2
+ Expect Symbol With Score SYMOPTS2 6.00
+ Do Not Expect Symbol SYMOPTS1
+ Do Not Expect Symbol SYMOPTS3
+
+Composites - Opts RE Hit 2
+ Scan File ${MESSAGE} opts=sym2,foo/
+ Expect Symbol With Score SYMOPTS3 6.00
+ Do Not Expect Symbol SYMOPTS2
+ Do Not Expect Symbol SYMOPTS1
+
+Composites - Opts RE Hit 3
+ Scan File ${MESSAGE} opts=example.com->app.link
+ Expect Symbol With Score SYMOPTS4 6.00
+ Do Not Expect Symbol SYMOPTS2
+ Do Not Expect Symbol SYMOPTS1
diff --git a/test/functional/cases/110_statistics/090-peruser.robot b/test/functional/cases/110_statistics/090-peruser.robot
new file mode 100644
index 0000000..cd8f3ac
--- /dev/null
+++ b/test/functional/cases/110_statistics/090-peruser.robot
@@ -0,0 +1,15 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Variables ***
+${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT}
+${RSPAMD_STATS_PER_USER} true
+
+*** Test Cases ***
+Learn
+ Learn Test test@example.com
+
+Relearn
+ Relearn Test test@example.com
diff --git a/test/functional/cases/110_statistics/100-redis-keyed-siphash.robot b/test/functional/cases/110_statistics/100-redis-keyed-siphash.robot
new file mode 100644
index 0000000..d889502
--- /dev/null
+++ b/test/functional/cases/110_statistics/100-redis-keyed-siphash.robot
@@ -0,0 +1,16 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Variables ***
+${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT}
+${RSPAMD_STATS_HASH} siphash
+${RSPAMD_STATS_KEY} ${RSPAMD_KEY_PVT1}
+
+*** Test Cases ***
+Learn
+ Learn Test
+
+Relearn
+ Relearn Test
diff --git a/test/functional/cases/110_statistics/110-redis-keyed-xxhash.robot b/test/functional/cases/110_statistics/110-redis-keyed-xxhash.robot
new file mode 100644
index 0000000..928a9ea
--- /dev/null
+++ b/test/functional/cases/110_statistics/110-redis-keyed-xxhash.robot
@@ -0,0 +1,16 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Variables ***
+${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT}
+${RSPAMD_STATS_HASH} xxhash
+${RSPAMD_STATS_KEY} ${RSPAMD_KEY_PVT1}
+
+*** Test Cases ***
+Learn
+ Learn Test
+
+Relearn
+ Relearn Test
diff --git a/test/functional/cases/110_statistics/200-redis-plain-siphash.robot b/test/functional/cases/110_statistics/200-redis-plain-siphash.robot
new file mode 100644
index 0000000..790a63e
--- /dev/null
+++ b/test/functional/cases/110_statistics/200-redis-plain-siphash.robot
@@ -0,0 +1,15 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Variables ***
+${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT}
+${RSPAMD_STATS_HASH} siphash
+
+*** Test Cases ***
+Learn
+ Learn Test
+
+Relearn
+ Relearn Test
diff --git a/test/functional/cases/110_statistics/210-redis-plain-xxhash.robot b/test/functional/cases/110_statistics/210-redis-plain-xxhash.robot
new file mode 100644
index 0000000..55e7524
--- /dev/null
+++ b/test/functional/cases/110_statistics/210-redis-plain-xxhash.robot
@@ -0,0 +1,15 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Variables ***
+${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT}
+${RSPAMD_STATS_HASH} xxhash
+
+*** Test Cases ***
+Learn
+ Learn Test
+
+Relearn
+ Relearn Test
diff --git a/test/functional/cases/110_statistics/lib.robot b/test/functional/cases/110_statistics/lib.robot
new file mode 100644
index 0000000..7928063
--- /dev/null
+++ b/test/functional/cases/110_statistics/lib.robot
@@ -0,0 +1,69 @@
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/stats.conf
+${MESSAGE_HAM} ${RSPAMD_TESTDIR}/messages/ham.eml
+${MESSAGE_SPAM} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_REDIS_SERVER} null
+${RSPAMD_SCOPE} Suite
+${RSPAMD_STATS_BACKEND} redis
+${RSPAMD_STATS_HASH} null
+${RSPAMD_STATS_KEY} null
+${RSPAMD_STATS_PER_USER} ${EMPTY}
+
+*** Keywords ***
+Broken Learn Test
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_spam ${MESSAGE_SPAM}
+ Check Rspamc ${result} Unknown statistics error
+
+Empty Part Test
+ Set Test Variable ${MESSAGE} ${RSPAMD_TESTDIR}/messages/empty_part.eml
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_spam ${MESSAGE}
+ Check Rspamc ${result}
+ Scan File ${MESSAGE}
+ Expect Symbol BAYES_SPAM
+
+Learn
+ [Arguments] ${user} ${class} ${message}
+ IF "${user}"
+ ${result} = Run Rspamc -d ${user} -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_${class} ${message}
+ ELSE
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_${class} ${message}
+ END
+ Check Rspamc ${result}
+
+Learn Test
+ [Arguments] ${user}=${EMPTY}
+ Set Suite Variable ${RSPAMD_STATS_LEARNTEST} 0
+ Set Test Variable ${kwargs} &{EMPTY}
+ IF "${user}"
+ Set To Dictionary ${kwargs} Deliver-To=${user}
+ END
+ Learn ${user} spam ${MESSAGE_SPAM}
+ Learn ${user} ham ${MESSAGE_HAM}
+ Scan File ${MESSAGE_SPAM} &{kwargs}
+ Expect Symbol BAYES_SPAM
+ Scan File ${MESSAGE_HAM} &{kwargs}
+ Expect Symbol BAYES_HAM
+ Set Suite Variable ${RSPAMD_STATS_LEARNTEST} 1
+
+Relearn Test
+ [Arguments] ${user}=${EMPTY}
+ IF ${RSPAMD_STATS_LEARNTEST} == 0
+ Fail "Learn test was not run"
+ END
+ Learn ${user} ham ${MESSAGE_SPAM}
+ Set Test Variable ${kwargs} &{EMPTY}
+ IF "${user}"
+ Set To Dictionary ${kwargs} Deliver-To=${user}
+ END
+ Scan File ${MESSAGE_SPAM} &{kwargs}
+ ${pass} = Run Keyword And Return Status Expect Symbol BAYES_HAM
+ IF ${pass}
+ Pass Execution What Me Worry
+ END
+ Do Not Expect Symbol BAYES_SPAM
diff --git a/test/functional/cases/116_dkim.robot b/test/functional/cases/116_dkim.robot
new file mode 100644
index 0000000..5c1005c
--- /dev/null
+++ b/test/functional/cases/116_dkim.robot
@@ -0,0 +1,59 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim.conf
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+DKIM PERMFAIL NXDOMAIN
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim2.eml
+ ... IP=37.48.67.26
+ Expect Symbol R_DKIM_PERMFAIL
+
+DKIM PERMFAIL BAD RECORD
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml
+ ... IP=37.48.67.26
+ Expect Symbol R_DKIM_PERMFAIL
+
+DKIM TEMPFAIL SERVFAIL UNALIGNED
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim3.eml
+ ... IP=37.48.67.26
+ Expect Symbol R_DKIM_TEMPFAIL
+
+DKIM NA NOSIG
+ Scan File ${RSPAMD_TESTDIR}/messages/utf.eml
+ ... IP=37.48.67.26
+ Expect Symbol R_DKIM_NA
+
+DKIM Ignore Unknown Tags
+ Scan File ${RSPAMD_TESTDIR}/messages/dmarc/dkim_unknown_tags.eml
+ Expect Symbol R_DKIM_ALLOW
+
+DKIM Sign
+ Set Suite Variable ${RAN_SIGNTEST} 0
+ ${result} = Scan Message With Rspamc ${RSPAMD_TESTDIR}/messages/spam_message.eml --mime --header=dodkim=1
+ Check Rspamc ${result} DKIM-Signature
+ Set Suite Variable ${SIGNED_MESSAGE} ${RSPAMD_TMPDIR}/dkim_sign_test.eml
+ Create File ${SIGNED_MESSAGE} ${result.stdout}
+ Set Suite Variable ${RAN_SIGNTEST} 1
+
+DKIM Self Verify
+ IF ${RAN_SIGNTEST} == 0
+ Fail "Sign test was not run"
+ END
+ Scan File ${SIGNED_MESSAGE}
+ Expect Symbol R_DKIM_ALLOW
+
+DKIM Verify ED25519 PASS
+ Scan File ${RSPAMD_TESTDIR}/messages/ed25519.eml
+ Expect Symbol R_DKIM_ALLOW
+
+DKIM Verify ED25519 REJECT
+ Scan File ${RSPAMD_TESTDIR}/messages/ed25519-broken.eml
+ Expect Symbol R_DKIM_REJECT
diff --git a/test/functional/cases/120_fuzzy/encrypted.robot b/test/functional/cases/120_fuzzy/encrypted.robot
new file mode 100644
index 0000000..548ea8c
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/encrypted.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Encrypted Siphash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/fasthash-keyed.robot b/test/functional/cases/120_fuzzy/fasthash-keyed.robot
new file mode 100644
index 0000000..6add33d
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/fasthash-keyed.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Keyed Fasthash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/fasthash.robot b/test/functional/cases/120_fuzzy/fasthash.robot
new file mode 100644
index 0000000..c33ede1
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/fasthash.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Plain Fasthash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/general.robot b/test/functional/cases/120_fuzzy/general.robot
new file mode 100644
index 0000000..2978ea6
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/general.robot
@@ -0,0 +1,17 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Plain Siphash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Delete
+ Fuzzy Multimessage Delete Test
+
+Fuzzy Overwrite
+ Fuzzy Multimessage Overwrite Test
+
+Fuzzy Skip Hash Test
+ Fuzzy Skip Hash Test Message \ No newline at end of file
diff --git a/test/functional/cases/120_fuzzy/lib.robot b/test/functional/cases/120_fuzzy/lib.robot
new file mode 100644
index 0000000..02e0a0a
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/lib.robot
@@ -0,0 +1,181 @@
+*** Settings ***
+Library OperatingSystem
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/fuzzy.conf
+${FLAG1_SYMBOL} R_TEST_FUZZY_DENIED
+${FLAG2_SYMBOL} R_TEST_FUZZY_WHITE
+${REDIS_SCOPE} Suite
+${RSPAMD_FLAG1_NUMBER} 50
+${RSPAMD_FLAG2_NUMBER} 51
+${RSPAMD_FUZZY_BACKEND} redis
+${RSPAMD_FUZZY_ENCRYPTED_ONLY} false
+${RSPAMD_FUZZY_ENCRYPTION_KEY} null
+${RSPAMD_FUZZY_INCLUDE} ${RSPAMD_TESTDIR}/configs/empty.conf
+${RSPAMD_FUZZY_KEY} null
+${RSPAMD_FUZZY_SHINGLES_KEY} null
+${RSPAMD_SCOPE} Suite
+${SETTINGS_FUZZY_CHECK} ${EMPTY}
+${SETTINGS_FUZZY_WORKER} ${EMPTY}
+@{MESSAGES_SKIP} ${RSPAMD_TESTDIR}/messages/priority.eml
+@{MESSAGES} ${RSPAMD_TESTDIR}/messages/spam_message.eml ${RSPAMD_TESTDIR}/messages/zip.eml
+@{RANDOM_MESSAGES} ${RSPAMD_TESTDIR}/messages/bad_message.eml ${RSPAMD_TESTDIR}/messages/zip-doublebad.eml
+
+*** Keywords ***
+Fuzzy Skip Add Test Base
+ [Arguments] ${message}
+ Set Suite Variable ${RSPAMD_FUZZY_ADD_${message}} 0
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -w 10 -f
+ ... ${RSPAMD_FLAG1_NUMBER} fuzzy_add ${message}
+ Check Rspamc ${result}
+ Sync Fuzzy Storage
+ Scan File ${message}
+ Expect Symbol R_TEST_FUZZY_DENIED
+ Create File ${RSPAMD_TMPDIR}/skip_hash.map.tmp 2d875d4737c59c4822fd01dadeba52a329de3933f766c6f167904c6a426bbfa7ea63a66bf807b25c5ee853baee58bfb18d3b423fcd13cfa7c3d77a840039a1ea
+ Move File ${RSPAMD_TMPDIR}/skip_hash.map.tmp ${RSPAMD_TMPDIR}/skip_hash.map
+ Sleep 1s Wait for reload
+ Scan File ${message}
+ Do Not Expect Symbol R_TEST_FUZZY_DENIED
+
+Fuzzy Add Test
+ [Arguments] ${message}
+ Set Suite Variable ${RSPAMD_FUZZY_ADD_${message}} 0
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -w 10 -f
+ ... ${RSPAMD_FLAG1_NUMBER} fuzzy_add ${message}
+ Check Rspamc ${result}
+ Sync Fuzzy Storage
+ Scan File ${message}
+ Expect Symbol ${FLAG1_SYMBOL}
+ Set Suite Variable ${RSPAMD_FUZZY_ADD_${message}} 1
+
+Fuzzy Delete Test
+ [Arguments] ${message}
+ IF ${RSPAMD_FUZZY_ADD_${message}} == 0
+ Fail "Fuzzy Add was not run"
+ END
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -f ${RSPAMD_FLAG1_NUMBER} fuzzy_del
+ ... ${message}
+ Check Rspamc ${result}
+ Sync Fuzzy Storage
+ Scan File ${message}
+ Do Not Expect Symbol ${FLAG1_SYMBOL}
+
+Fuzzy Fuzzy Test
+ [Arguments] ${message}
+ IF ${RSPAMD_FUZZY_ADD_${message}} != 1
+ Fail "Fuzzy Add was not run"
+ END
+ @{path_info} = Path Splitter ${message}
+ @{fuzzy_files} = List Files In Directory ${pathinfo}[0] pattern=${pathinfo}[1].fuzzy* absolute=1
+ FOR ${i} IN @{fuzzy_files}
+ Scan File ${i}
+ Expect Symbol ${FLAG1_SYMBOL}
+ END
+
+Fuzzy Miss Test
+ [Arguments] ${message}
+ Scan File ${message}
+ Do Not Expect Symbol ${FLAG1_SYMBOL}
+
+Fuzzy Overwrite Test
+ [Arguments] ${message}
+ ${flag_numbers} = Create List ${RSPAMD_FLAG1_NUMBER} ${RSPAMD_FLAG2_NUMBER}
+ FOR ${i} IN @{flag_numbers}
+ ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} -w 10
+ ... -f ${i} fuzzy_add ${message}
+ Check Rspamc ${result}
+ END
+ Sync Fuzzy Storage
+ Scan File ${message}
+ Do Not Expect Symbol ${FLAG1_SYMBOL}
+ Expect Symbol ${FLAG2_SYMBOL}
+
+Fuzzy Setup Encrypted
+ [Arguments] ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_ENCRYPTED_ONLY} true
+ Set Suite Variable ${RSPAMD_FUZZY_ENCRYPTION_KEY} ${RSPAMD_KEY_PUB1}
+ Set Suite Variable ${RSPAMD_FUZZY_INCLUDE} ${RSPAMD_TESTDIR}/configs/fuzzy-encryption-key.conf
+ Rspamd Redis Setup
+
+Fuzzy Setup Encrypted Keyed
+ [Arguments] ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_ENCRYPTED_ONLY} true
+ Set Suite Variable ${RSPAMD_FUZZY_ENCRYPTION_KEY} ${RSPAMD_KEY_PUB1}
+
+ Set Suite Variable ${RSPAMD_FUZZY_KEY} mYN888sydwLTfE32g2hN
+ Set Suite Variable ${RSPAMD_FUZZY_SHINGLES_KEY} hXUCgul9yYY3Zlk1QIT2
+ Rspamd Redis Setup
+
+Fuzzy Setup Plain
+ [Arguments] ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} ${algorithm}
+ Rspamd Redis Setup
+
+Fuzzy Setup Keyed
+ [Arguments] ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} ${algorithm}
+ Set Suite Variable ${RSPAMD_FUZZY_KEY} mYN888sydwLTfE32g2hN
+ Set Suite Variable ${RSPAMD_FUZZY_SHINGLES_KEY} hXUCgul9yYY3Zlk1QIT2
+ Rspamd Redis Setup
+
+Fuzzy Setup Plain Fasthash
+ Fuzzy Setup Plain fasthash
+
+Fuzzy Setup Plain Mumhash
+ Fuzzy Setup Plain mumhash
+
+Fuzzy Setup Plain Siphash
+ Fuzzy Setup Plain siphash
+
+Fuzzy Setup Plain Xxhash
+ Fuzzy Setup Plain xxhash
+
+Fuzzy Setup Keyed Fasthash
+ Fuzzy Setup Keyed fasthash
+
+Fuzzy Setup Keyed Mumhash
+ Fuzzy Setup Keyed mumhash
+
+Fuzzy Setup Keyed Siphash
+ Fuzzy Setup Keyed siphash
+
+Fuzzy Setup Keyed Xxhash
+ Fuzzy Setup Keyed xxhash
+
+Fuzzy Setup Encrypted Siphash
+ Fuzzy Setup Encrypted siphash
+
+Fuzzy Skip Hash Test Message
+ FOR ${i} IN @{MESSAGES_SKIP}
+ Fuzzy Skip Add Test Base ${i}
+ END
+
+Fuzzy Multimessage Add Test
+ FOR ${i} IN @{MESSAGES}
+ Fuzzy Add Test ${i}
+ END
+
+Fuzzy Multimessage Fuzzy Test
+ FOR ${i} IN @{MESSAGES}
+ Fuzzy Fuzzy Test ${i}
+ END
+
+Fuzzy Multimessage Miss Test
+ FOR ${i} IN @{RANDOM_MESSAGES}
+ Fuzzy Miss Test ${i}
+ END
+
+Fuzzy Multimessage Delete Test
+ FOR ${i} IN @{MESSAGES}
+ Fuzzy Delete Test ${i}
+ END
+
+Fuzzy Multimessage Overwrite Test
+ FOR ${i} IN @{MESSAGES}
+ Fuzzy Overwrite Test ${i}
+ END
diff --git a/test/functional/cases/120_fuzzy/mumhash-keyed.robot b/test/functional/cases/120_fuzzy/mumhash-keyed.robot
new file mode 100644
index 0000000..934a4ee
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/mumhash-keyed.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Keyed Mumhash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/mumhash.robot b/test/functional/cases/120_fuzzy/mumhash.robot
new file mode 100644
index 0000000..78abe7f
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/mumhash.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Plain Mumhash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/siphash-keyed.robot b/test/functional/cases/120_fuzzy/siphash-keyed.robot
new file mode 100644
index 0000000..a11d559
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/siphash-keyed.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Keyed Siphash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/siphash.robot b/test/functional/cases/120_fuzzy/siphash.robot
new file mode 100644
index 0000000..b477f0c
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/siphash.robot
@@ -0,0 +1,14 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Plain Siphash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/xxhash-keyed.robot b/test/functional/cases/120_fuzzy/xxhash-keyed.robot
new file mode 100644
index 0000000..84df1df
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/xxhash-keyed.robot
@@ -0,0 +1,15 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Keyed Xxhash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ [Tags] isbroken
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/120_fuzzy/xxhash.robot b/test/functional/cases/120_fuzzy/xxhash.robot
new file mode 100644
index 0000000..f7b6fa2
--- /dev/null
+++ b/test/functional/cases/120_fuzzy/xxhash.robot
@@ -0,0 +1,15 @@
+*** Settings ***
+Suite Setup Fuzzy Setup Plain Xxhash
+Suite Teardown Rspamd Redis Teardown
+Resource lib.robot
+
+*** Test Cases ***
+Fuzzy Add
+ Fuzzy Multimessage Add Test
+
+Fuzzy Fuzzy
+ [Tags] isbroken
+ Fuzzy Multimessage Fuzzy Test
+
+Fuzzy Miss
+ Fuzzy Multimessage Miss Test
diff --git a/test/functional/cases/121_json/100_preresult.robot b/test/functional/cases/121_json/100_preresult.robot
new file mode 100644
index 0000000..04aac06
--- /dev/null
+++ b/test/functional/cases/121_json/100_preresult.robot
@@ -0,0 +1,23 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource lib.robot
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/preresult.lua
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+Stat
+ Stat Test
+
+History
+ History Test soft reject
+
+Scan
+ Scan Test
diff --git a/test/functional/cases/121_json/101_simple.robot b/test/functional/cases/121_json/101_simple.robot
new file mode 100644
index 0000000..98ff0ea
--- /dev/null
+++ b/test/functional/cases/121_json/101_simple.robot
@@ -0,0 +1,23 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource lib.robot
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/simple.lua
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+Stat
+ Stat Test
+
+History
+ History Test SIMPLE_TEST
+
+Scan
+ Scan Test
diff --git a/test/functional/cases/121_json/lib.robot b/test/functional/cases/121_json/lib.robot
new file mode 100644
index 0000000..68bf566
--- /dev/null
+++ b/test/functional/cases/121_json/lib.robot
@@ -0,0 +1,22 @@
+*** Variables ***
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Keywords ***
+Stat Test
+ @{result} = HTTP GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /stat
+ Check JSON ${result}[1]
+ Should Be Equal As Integers ${result}[0] 200
+
+History Test
+ [Arguments] ${rspamc_expected_result}
+ ${result} = Scan Message With Rspamc ${MESSAGE}
+ Check Rspamc ${result} ${rspamc_expected_result}
+ @{result} = HTTP GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /history
+ Check JSON ${result}[1]
+ Should Be Equal As Integers ${result}[0] 200
+
+Scan Test
+ ${content} = Get File ${MESSAGE}
+ @{result} = HTTP POST ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_NORMAL} /check ${content}
+ Check JSON ${result}[1]
+ Should Be Equal As Integers ${result}[0] 200
diff --git a/test/functional/cases/123_whitelist.robot b/test/functional/cases/123_whitelist.robot
new file mode 100644
index 0000000..c5a672f
--- /dev/null
+++ b/test/functional/cases/123_whitelist.robot
@@ -0,0 +1,79 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/whitelist.conf
+${M_DKIM_RSPAMD_BAD} ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim_rspamd.eml
+${M_DKIM_RSPAMD_OK} ${RSPAMD_TESTDIR}/messages/dmarc/good_dkim_rspamd.eml
+${M_DMARC_BAD} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${M_DMARC_OK} ${RSPAMD_TESTDIR}/messages/dmarc/pass_none.eml
+${M_NO_DKIM_VALID_SPF} ${RSPAMD_TESTDIR}/messages/dmarc/no_dkim_valid_spf.eml
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+${UTF_MESSAGE} ${RSPAMD_TESTDIR}/messages/utf.eml
+
+*** Test Cases ***
+WHITELISTS
+ Scan File ${M_DMARC_OK} IP=8.8.4.4 From=foo@spf.cacophony.za.org
+ Expect Symbol With Score WHITELIST_DKIM -1
+ Expect Symbol With Score STRICT_DMARC -3
+ Expect Symbol With Score WHITELIST_SPF_DKIM -3
+ Expect Symbol With Score WHITELIST_DDS -3
+ Expect Symbol With Score WHITELIST_DMARC -2
+ Expect Symbol With Score WHITELIST_DMARC_DKIM -2
+ Expect Symbol With Score WHITELIST_SPF -1
+ Do Not Expect Symbol BLACKLIST_SPF
+ Do Not Expect Symbol BLACKLIST_DKIM
+ Do Not Expect Symbol BLACKLIST_DMARC
+
+BLACKLIST SHOULD FIRE IF ANY CONSTRAINT FAILED
+ Scan File ${M_DMARC_OK} IP=9.8.4.4 From=foo@spf.cacophony.za.org
+ Expect Symbol With Score BLACKLIST_DDS 3
+ Do Not Expect Symbol WHITELIST_DDS
+ Do Not Expect Symbol WHITELIST_SPF
+
+BLACKLISTS
+ Scan File ${M_DMARC_BAD} IP=9.8.4.4 From=foo@cacophony.za.org
+ Expect Symbol With Score BLACKLIST_SPF 3
+ Expect Symbol With Score BLACKLIST_SPF 3
+ Expect Symbol With Score STRICT_DMARC 3
+ Expect Symbol With Score BLACKLIST_DDS 3
+ Expect Symbol With Score BLACKLIST_DMARC 2
+ Do Not Expect Symbol WHITELIST_DDS
+ Do Not Expect Symbol WHITELIST_SPF
+ Do Not Expect Symbol WHITELIST_DKIM
+ Do Not Expect Symbol WHITELIST_DMARC
+ Do Not Expect Symbol WHITELIST_DMARC_DKIM
+
+WHITELIST_WL_ONLY - VALID SPF AND VALID DKIM
+ Scan File ${M_DKIM_RSPAMD_OK}
+ ... IP=88.99.142.95
+ Expect Symbol With Score WHITELIST_DKIM -2
+ Do Not Expect Symbol BLACKLIST_DKIM
+ Expect Symbol With Score R_SPF_ALLOW 1
+ Expect Symbol With Score R_DKIM_ALLOW 1
+ Expect Symbol With Score WHITELIST_SPF_DKIM -6
+
+BLACKLISTS_WL_ONLY - VALID SPF AND INVALID DKIM
+ Scan File ${M_DKIM_RSPAMD_BAD}
+ ... IP=88.99.142.95
+ Expect Symbol With Score R_DKIM_REJECT 1
+ Do Not Expect Symbol WHITELIST_DKIM
+ Do Not Expect Symbol BLACKLIST_DKIM
+ Expect Symbol With Score R_SPF_ALLOW 1
+ Expect Symbol With Score R_DKIM_REJECT 1
+ Do Not Expect Symbol WHITELIST_SPF_DKIM
+ Do Not Expect Symbol R_DKIM_ALLOW
+
+VALID SPF and NO DKIM
+ Scan File ${M_NO_DKIM_VALID_SPF}
+ ... IP=88.99.142.95
+ Expect Symbol With Score R_SPF_ALLOW 1
+ Expect Symbol With Score R_DKIM_NA 1
+ Do Not Expect Symbol R_DKIM_REJECT
+ Do Not Expect Symbol WHITELIST_SPF_DKIM
+ Do Not Expect Symbol R_DKIM_ALLOW
diff --git a/test/functional/cases/125_map_reload.robot b/test/functional/cases/125_map_reload.robot
new file mode 100644
index 0000000..4c05ef4
--- /dev/null
+++ b/test/functional/cases/125_map_reload.robot
@@ -0,0 +1,47 @@
+*** Settings ***
+Suite Setup Map Reload Setup
+Suite Teardown Map Reload Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MAP_WATCH_INTERVAL} 0.5s
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+CHECK HIT AND MISS
+ Scan File ${MESSAGE}
+ Expect Symbol With Score And Exact Options MAP_SET_HIT_AND_MISS 1 example.com
+
+WRITE NEW MAP
+ ${TMP_FILE} = Make Temporary File
+ Copy File ${RSPAMD_TESTDIR}/configs/maps/domains.list.2 ${TMP_FILE}
+ Move File ${TMP_FILE} ${MAP_FILE}
+
+CHECK HIT AND MISS AFTER RELOAD
+ Sleep 1s Wait for map reload
+ Scan File ${MESSAGE}
+ Expect Symbol With Score And Exact Options MAP_SET_HIT_AND_MISS 1 rspamd.com
+
+*** Keywords ***
+Map Reload Setup
+ ${MAP1} = Get File ${RSPAMD_TESTDIR}/configs/maps/domains.list
+ ${MAP_FILE} = Make Temporary File
+ ${RSPAMD_LUA_SCRIPT} = Make Temporary File
+ Set Suite Variable ${RSPAMD_LUA_SCRIPT}
+ Set Suite Variable ${MAP_FILE}
+ Set Suite Variable ${MAP1}
+ ${lua} = Get File ${RSPAMD_TESTDIR}/lua/mapreload.lua
+ ${lua} = Replace Variables ${lua}
+ Create File ${RSPAMD_LUA_SCRIPT} ${lua}
+ Create File ${MAP_FILE} ${MAP1}
+ Rspamd Setup
+
+Map Reload Teardown
+ Remove File ${MAP_FILE}
+ Remove File ${RSPAMD_LUA_SCRIPT}
+ Rspamd Teardown
diff --git a/test/functional/cases/131_dkim_signing/001_simple.robot b/test/functional/cases/131_dkim_signing/001_simple.robot
new file mode 100644
index 0000000..3929d87
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/001_simple.robot
@@ -0,0 +1,35 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/simple.conf
+${MESSAGE_FAIL} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none1.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature:
+ Should Contain ${result.stdout} DKIM_SIGNED
+
+TEST SIGNED HTTP HEADERS
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org --header=PerformDkimSign:yes --header=DkimDomain:cacophony.za.org --header=DkimSelector:dkim --header=DkimPrivateKey:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANe3EETkiI1Exyrb+VzbMSt90K8MXJA0GcyNs6MFCs9JPaTh90Zu2l7ki7m5LTUx6350AR/3hcvwjSHCZjD6fvQ8/zfjN8kaLZ6DAaqtqSlpawIM+8glkuTEkIkpBED/OtDrba4Rd29iLFVuwQZXDtTjAAZKZPmtTZ5TXLrcCU6VAgMBAAECgYEA1BFvmBsIN8Gu/+6kNupya2xUNVM0yLu/xT5lpNV3LBO325oejAq8+d87kkl/LTW3a2jGFlQ0ICuLw+2mo24QUWRyv8if3oeBMlnLqHE+6wNjFVqo5sOjKzjO363xSXwXNUrBT7jDhnZcDN8w3/FecYKjifGTVtUs1SLsYwhlc8ECQQDuCRymLZQ/imPn5eFVIydwUzg8ptZlvoA7bfIxUL9BQRX33s59kLCilA0tTed8Dd+GnxsT93XOj1ApIfBwmTSlAkEA5/63PDsN7fH+WInqVD8nU07M9S8LcGDlPbVVBr2S2I78/iwrSDAYtbkU2vEbhFK/JuKNML2j8OkzV3v1QulfMQJBALDzhx+l/HHr3+8RPhx7QKNIyiKUaAdEwbDsP8IXY8YPq1QThu9jM1v4sX7/TdkzuvoppwiFykbe1NlvCH279p0CQCmTg4Ee0DtBcCSr6rvYaZLLf329RZ6JLuwlMCy6ErQOxBZFEiiovfTrS2qFZToMnkc4uLbwdY36LQJTq7unGTECQCCok8LzBeZtAw+TJofpOM3F2Rlm2qXiBVBeubhRedsiljG0hpvvLJBMppnQ6r27p5Jk39SmaTRkxEKrxPWWLNM=
+ Check Rspamc ${result} DKIM-Signature:
+ Should Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG DOMAIN
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@example.tk
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG PUBKEY
+ ${result} = Scan Message With Rspamc ${MESSAGE_FAIL} -u bob@invalid.za.org
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
diff --git a/test/functional/cases/131_dkim_signing/002_redis.robot b/test/functional/cases/131_dkim_signing/002_redis.robot
new file mode 100644
index 0000000..6e8f888
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/002_redis.robot
@@ -0,0 +1,31 @@
+*** Settings ***
+Suite Setup DKIM Signing Setup
+Suite Teardown Rspamd Redis Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/redis.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature:
+ Should Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG DOMAIN
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@example.tk
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
+
+*** Keywords ***
+DKIM Signing Setup
+ Rspamd Redis Setup
+ Redis HSET TEST_DKIM_SELECTORS cacophony.za.org dkim
+ ${key} = Get File ${RSPAMD_TESTDIR}/configs/dkim.key
+ Redis HSET TEST_DKIM_KEYS dkim.cacophony.za.org ${key}
diff --git a/test/functional/cases/131_dkim_signing/003_eddsa.robot b/test/functional/cases/131_dkim_signing/003_eddsa.robot
new file mode 100644
index 0000000..27d1c0c
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/003_eddsa.robot
@@ -0,0 +1,30 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/eddsa.conf
+${MESSAGE_FAIL} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none1.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature:
+ Should Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG DOMAIN
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@example.tk
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG PUBKEY
+ ${result} = Scan Message With Rspamc ${MESSAGE_FAIL} -u bob@invalid.za.org
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
diff --git a/test/functional/cases/131_dkim_signing/004_invalidate_key.robot b/test/functional/cases/131_dkim_signing/004_invalidate_key.robot
new file mode 100644
index 0000000..2218c34
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/004_invalidate_key.robot
@@ -0,0 +1,50 @@
+*** Settings ***
+Force Tags isbroken
+Suite Setup Key Invalidation Setup
+Suite Teardown Key Invalidation Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/invalidate.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature:
+ Should Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - MISSING KEY
+ [Setup] Delete Key
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - KEY NO LONGER MATCHES
+ [Setup] Move Key
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
+
+*** Keywords ***
+Key Invalidation Setup
+ ${key_dir} Make Temporary Directory
+ Set Suite Variable ${RSPAMD_KEY_DIR} ${key_dir}
+ Copy File ${RSPAMD_TESTDIR}/configs/dkim-eddsa.key ${RSPAMD_KEY_DIR}/dkim-eddsa.key
+ Rspamd Setup
+
+Delete Key
+ Remove File ${RSPAMD_KEY_DIR}/dkim-eddsa.key
+
+Move Key
+ Copy File ${RSPAMD_TESTDIR}/configs/dkim.key ${RSPAMD_KEY_DIR}/dkim-eddsa.key
+ Set Modified Time ${RSPAMD_KEY_DIR}/dkim-eddsa.key NOW + 3s
+
+Key Invalidation Teardown
+ Cleanup Temporary Directory ${RSPAMD_KEY_DIR}
+ Rspamd Teardown
diff --git a/test/functional/cases/131_dkim_signing/005_multiple.robot b/test/functional/cases/131_dkim_signing/005_multiple.robot
new file mode 100644
index 0000000..067220d
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/005_multiple.robot
@@ -0,0 +1,20 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/multiple.conf
+${MESSAGE_FAIL} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none1.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST DOUBLE SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} (?s)DKIM-Signature.+DKIM-Signature re=1
+ Should Contain ${result.stdout} DKIM_SIGNED
diff --git a/test/functional/cases/131_dkim_signing/006_milter.robot b/test/functional/cases/131_dkim_signing/006_milter.robot
new file mode 100644
index 0000000..2b63d07
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/006_milter.robot
@@ -0,0 +1,31 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Test Tags miltertest
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/milter.conf
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+SINGLE SIGNATURE
+ Milter Test dkim_one.lua
+
+MULTIPLE SIGNATURES
+ Milter Test dkim_many.lua
+
+*** Keywords ***
+Milter Test
+ [Arguments] ${mtlua}
+ Skip If not ${HAVE_MILTERTEST} msg=miltertest not installed
+ ${result} = Run Process miltertest -Dport\=${RSPAMD_PORT_PROXY} -Dhost\=${RSPAMD_LOCAL_ADDR} -s ${RSPAMD_TESTDIR}/lua/miltertest/${mtlua}
+ ... cwd=${RSPAMD_TESTDIR}/lua/miltertest
+ Should Match Regexp ${result.stderr} ^$
+ Log ${result.rc}
+ Log ${result.stdout}
+ Should Be Equal As Integers ${result.rc} 0 msg=${result.stdout} values=false
diff --git a/test/functional/cases/131_dkim_signing/007_sign_maps.robot b/test/functional/cases/131_dkim_signing/007_sign_maps.robot
new file mode 100644
index 0000000..f0523cd
--- /dev/null
+++ b/test/functional/cases/131_dkim_signing/007_sign_maps.robot
@@ -0,0 +1,25 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/dkim_signing/sign_maps.conf
+${MESSAGE_FAIL} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none1.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature:
+ Should Contain ${result.stdout} DKIM_SIGNED
+
+TEST NOT SIGNED - FROM WRONG DOMAIN
+ ${result} = Scan Message With Rspamc ${MESSAGE_FAIL} -u bob@cacophony.za.org
+ Check Rspamc ${result} DKIM-Signature: inverse=1
+ Should Not Contain ${result.stdout} DKIM_SIGNED
diff --git a/test/functional/cases/135_spamassassin.robot b/test/functional/cases/135_spamassassin.robot
new file mode 100644
index 0000000..f9691fd
--- /dev/null
+++ b/test/functional/cases/135_spamassassin.robot
@@ -0,0 +1,34 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/spamassassin.conf
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+FREEMAIL
+ Scan File ${RSPAMD_TESTDIR}/messages/freemail.eml
+ ... From=faked.asdfjisiwosp372@outlook.com
+ Expect Symbol FREEMAIL_FROM
+ Expect Symbol FREEMAIL_ENVFROM_END_DIGIT
+ Expect Symbol FREEMAIL_SUBJECT
+ Expect Symbol TEST_META4
+
+WLBL WHITELIST
+ Scan File ${RSPAMD_TESTDIR}/messages/bad_message.eml
+ Expect Symbol USER_IN_WHITELIST
+ Expect Symbol USER_IN_WHITELIST_TO
+ Do Not Expect Symbol USER_IN_BLACKLIST_TO
+ Do Not Expect Symbol USER_IN_BLACKLIST
+
+WLBL BLACKLIST
+ Scan File ${RSPAMD_TESTDIR}/messages/utf.eml
+ Expect Symbol USER_IN_BLACKLIST
+ Expect Symbol USER_IN_BLACKLIST_TO
+ Do Not Expect Symbol USER_IN_WHITELIST_TO
+ Do Not Expect Symbol USER_IN_WHITELIST
diff --git a/test/functional/cases/140_proxy.robot b/test/functional/cases/140_proxy.robot
new file mode 100644
index 0000000..d43cfa4
--- /dev/null
+++ b/test/functional/cases/140_proxy.robot
@@ -0,0 +1,50 @@
+*** Settings ***
+Suite Setup Proxy Setup
+Suite Teardown Proxy Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/simple.lua
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+HTTP PROTOCOL
+ Set Test Variable ${RSPAMD_PORT_NORMAL} ${RSPAMD_PORT_PROXY}
+ Scan File ${MESSAGE}
+ Expect Symbol SIMPLE_TEST
+
+SPAMC
+ ${result} = Spamc ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_PROXY} ${MESSAGE}
+ Should Contain ${result} SPAMD/1.1 0 EX_OK
+
+RSPAMC Legacy Protocol
+ ${result} = Rspamc ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_PROXY} ${MESSAGE}
+ Should Contain ${result} RSPAMD/1.3 0 EX_OK
+
+*** Keywords ***
+Proxy Setup
+ # Run slave & copy variables
+ Set Suite Variable ${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+ Rspamd Setup
+ Set Suite Variable ${SLAVE_PID} ${RSPAMD_PID}
+ Set Suite Variable ${SLAVE_TMPDIR} ${RSPAMD_TMPDIR}
+
+ # Run proxy & copy variables
+ Set Suite Variable ${CONFIG} ${RSPAMD_TESTDIR}/configs/proxy.conf
+ Rspamd Setup
+ Set Suite Variable ${PROXY_PID} ${RSPAMD_PID}
+ Set Suite Variable ${PROXY_TMPDIR} ${RSPAMD_TMPDIR}
+
+Proxy Teardown
+ # Restore variables & run normal teardown
+ Set Suite Variable ${RSPAMD_PID} ${PROXY_PID}
+ Set Suite Variable ${RSPAMD_TMPDIR} ${PROXY_TMPDIR}
+ Rspamd Teardown
+ # Do it again for slave
+ Set Suite Variable ${RSPAMD_PID} ${SLAVE_PID}
+ Set Suite Variable ${RSPAMD_TMPDIR} ${SLAVE_TMPDIR}
+ Rspamd Teardown
diff --git a/test/functional/cases/150_rspamadm.robot b/test/functional/cases/150_rspamadm.robot
new file mode 100644
index 0000000..ef3db98
--- /dev/null
+++ b/test/functional/cases/150_rspamadm.robot
@@ -0,0 +1,45 @@
+*** Settings ***
+Library Process
+Library ../lib/rspamd.py
+
+Suite Teardown Terminate All Processes kill=True
+
+*** Test Cases ***
+Config Test
+ ${result} = Run Process ${RSPAMADM} configtest
+ Should Match Regexp ${result.stderr} ^$
+ Should Match Regexp ${result.stdout} ^syntax OK$
+ Should Be Equal As Integers ${result.rc} 0
+
+Config Help
+ ${result} = Run Process ${RSPAMADM} confighelp
+ Should Match Regexp ${result.stderr} ^$
+ Should Be Equal As Integers ${result.rc} 0
+
+Simple interpreter
+ ${handle} = Start Process ${RSPAMADM} lua stdin=PIPE
+ ${result} = Write to stdin ${handle} 1+1
+ Should Be Equal As Strings ${result} 2\n
+
+Simple interpreter, two results
+ ${handle} = Start Process ${RSPAMADM} lua stdin=PIPE
+ ${result} = Write to stdin ${handle} 1+1, 2 * 5
+ Should Be Equal ${result} 2\n10\n
+
+Process message callback
+ ${handle} = Start Process ${RSPAMADM} lua stdin=PIPE
+ ${result} = Write to stdin ${handle} .load ${RSPAMD_TESTDIR}/lua/rspamadm/test_message_callback.lua\n.message message_callback ${RSPAMD_TESTDIR}/messages/empty_part.eml
+ Should Contain ${result} n parts = 2
+ Should Contain ${result} 1\n2\n4\n6
+
+Lua batch mode
+ ${result} = Run Process ${RSPAMADM} lua -b ${RSPAMD_TESTDIR}/lua/rspamadm/test_batch.lua
+ Should Be Equal ${result.stderr} hello world
+ Should Match Regexp ${result.stdout} ^$
+ Should Be Equal As Integers ${result.rc} 0
+
+Verbose mode
+ ${result} = Run Process ${RSPAMADM} -v lua ${RSPAMD_TESTDIR}/lua/rspamadm/test_verbose.lua
+ Should Match Regexp ${result.stderr} ^$
+ Should Match Regexp ${result.stdout} hello world\n
+ Should Be Equal As Integers ${result.rc} 0
diff --git a/test/functional/cases/151_rspamadm_async.robot b/test/functional/cases/151_rspamadm_async.robot
new file mode 100644
index 0000000..6aff67a
--- /dev/null
+++ b/test/functional/cases/151_rspamadm_async.robot
@@ -0,0 +1,54 @@
+*** Settings ***
+Test Setup Rspamadm test Setup
+Test Teardown Rspamadm test Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/plugins.conf
+${REDIS_SCOPE} Test
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+#Tcp client
+# ${result} = Run Process ${RSPAMADM} lua -b ${RSPAMD_TESTDIR}/lua/rspamadm/test_tcp_client.lua
+# Should Match Regexp ${result.stderr} ^$
+# Should Be Equal As Integers ${result.rc} 0
+# Should Be Equal ${result.stdout} hello post
+
+Redis client
+ ${result} = Run Process ${RSPAMADM} lua -b ${RSPAMD_TESTDIR}/lua/rspamadm/test_redis_client.lua
+ Should Match Regexp ${result.stderr} ^$
+ Should Be Equal As Integers ${result.rc} 0
+ Should Be Equal ${result.stdout} true\thello from lua on redis
+
+# Broken due to tmpdir override
+#DNS client
+# ${result} = Run Process ${RSPAMADM} --var\=CONFDIR\=${tmpdir} lua -b ${RSPAMD_TESTDIR}/lua/rspamadm/test_dns_client.lua
+# Log ${result.stdout}
+# Log ${result.stderr}
+# Should Be Equal As Integers ${result.rc} 0
+# Should Be Equal ${result.stdout} true\tk=ed25519; p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=
+# Cleanup Temporary Directory ${tmpdir}
+
+*** Keywords ***
+
+Rspamadm test Setup
+ Run Dummy Http
+ Run Redis
+
+Rspamadm test Teardown
+ Dummy Http Teardown
+ Redis Teardown
+
+Prepare temp directory
+ [Arguments] ${CONFIG}
+ ${template} = Get File ${CONFIG}
+ ${tmpdir} = Make Temporary Directory
+ ${config} = Replace Variables ${template}
+ ${config} = Replace Variables ${config}
+ Log ${config}
+ Create File ${tmpdir}/rspamd.conf ${config}
+ [Return] ${tmpdir}
diff --git a/test/functional/cases/161_p0f.robot b/test/functional/cases/161_p0f.robot
new file mode 100644
index 0000000..9b525d4
--- /dev/null
+++ b/test/functional/cases/161_p0f.robot
@@ -0,0 +1,97 @@
+*** Settings ***
+Suite Setup p0f Setup
+Suite Teardown p0f Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/p0f.conf
+${MESSAGE2} ${RSPAMD_TESTDIR}/messages/freemail.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+p0f MISS
+ Run Dummy p0f
+ Scan File ${MESSAGE} IP=1.1.1.1
+ Do Not Expect Symbol P0F_FAIL
+ Do Not Expect Symbol WINDOWS
+ Expect Symbol With Exact Options P0F Linux 3.11 and newer link=Ethernet or modem distance=10
+ Shutdown p0f
+
+p0f HIT
+ Run Dummy p0f ${RSPAMD_P0F_SOCKET} windows
+ Scan File ${MESSAGE} IP=1.1.1.2
+ Do Not Expect Symbol P0F_FAIL
+ Expect Symbol With Exact Options P0F link=Ethernet or modem distance=10
+ Expect Symbol WINDOWS
+ Shutdown p0f
+
+p0f MISS CACHE
+ Run Dummy p0f
+ Scan File ${MESSAGE} IP=1.1.1.3
+ Do Not Expect Symbol WINDOWS
+ Shutdown p0f
+ Scan File ${MESSAGE} IP=1.1.1.3
+ Do Not Expect Symbol WINDOWS
+ Do Not Expect Symbol P0F_FAIL
+
+p0f HIT CACHE
+ Run Dummy p0f ${RSPAMD_P0F_SOCKET} windows
+ Scan File ${MESSAGE} IP=1.1.1.4
+ Expect Symbol WINDOWS
+ Shutdown p0f
+ Scan File ${MESSAGE} IP=1.1.1.4
+ Expect Symbol WINDOWS
+ Do Not Expect Symbol P0F_FAIL
+
+p0f NO REDIS
+ Shutdown Process With Children ${REDIS_PID}
+ Run Dummy p0f
+ Scan File ${MESSAGE} IP=1.1.1.5
+ Expect Symbol With Exact Options P0F Linux 3.11 and newer link=Ethernet or modem distance=10
+ Do Not Expect Symbol P0F_FAIL
+ Shutdown p0f
+
+p0f NO MATCH
+ Run Dummy p0f ${RSPAMD_P0F_SOCKET} windows no_match
+ Scan File ${MESSAGE} IP=1.1.1.6
+ Do Not Expect Symbol P0F
+ Do Not Expect Symbol WINDOWS
+ Shutdown p0f
+
+p0f BAD QUERY
+ Run Dummy p0f ${RSPAMD_P0F_SOCKET} windows bad_query
+ Scan File ${MESSAGE} IP=1.1.1.7
+ Expect Symbol With Exact Options P0F_FAIL Malformed Query: /tmp/p0f.sock
+ Do Not Expect Symbol WINDOWS
+ Shutdown p0f
+
+p0f BAD RESPONSE
+ Run Dummy p0f ${RSPAMD_P0F_SOCKET} windows bad_response
+ Scan File ${MESSAGE} IP=1.1.1.8
+ Expect Symbol With Exact Options P0F_FAIL Error getting result: IO read error: connection terminated
+ Do Not Expect Symbol WINDOWS
+ Shutdown p0f
+
+*** Keywords ***
+p0f Setup
+ Rspamd Redis Setup
+
+p0f Teardown
+ Rspamd Redis Teardown
+ Shutdown p0f
+ Terminate All Processes kill=True
+
+Shutdown p0f
+ ${p0f_pid} = Get File if exists /tmp/dummy_p0f.pid
+ Run Keyword if ${p0f_pid} Shutdown Process With Children ${p0f_pid}
+
+Run Dummy p0f
+ [Arguments] ${socket}=${RSPAMD_P0F_SOCKET} ${os}=linux ${status}=ok
+ ${result} = Start Process ${RSPAMD_TESTDIR}/util/dummy_p0f.py ${socket} ${os} ${status}
+ Wait Until Created /tmp/dummy_p0f.pid
diff --git a/test/functional/cases/162_url_redirector.robot b/test/functional/cases/162_url_redirector.robot
new file mode 100644
index 0000000..857a349
--- /dev/null
+++ b/test/functional/cases/162_url_redirector.robot
@@ -0,0 +1,34 @@
+*** Settings ***
+Suite Setup Urlredirector Setup
+Suite Teardown Urlredirector Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/url_redirector.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/redir.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+${SETTINGS} {symbols_enabled=[URL_REDIRECTOR_CHECK]}
+
+*** Test Cases ***
+RESOLVE URLS
+ Scan File ${MESSAGE} Flags=ext_urls Settings=${SETTINGS}
+ Expect Extended URL http://127.0.0.1:18080/hello
+
+RESOLVE URLS CACHED
+ Scan File ${MESSAGE} Flags=ext_urls Settings=${SETTINGS}
+ Expect Extended URL http://127.0.0.1:18080/hello
+
+*** Keywords ***
+Urlredirector Setup
+ Run Dummy Http
+ Rspamd Redis Setup
+
+Urlredirector Teardown
+ Rspamd Redis Teardown
+ Dummy Http Teardown
+ Terminate All Processes kill=True
diff --git a/test/functional/cases/180_milter.robot b/test/functional/cases/180_milter.robot
new file mode 100644
index 0000000..d41751c
--- /dev/null
+++ b/test/functional/cases/180_milter.robot
@@ -0,0 +1,40 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Test Tags miltertest
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/milter.conf
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+ACCEPT
+ Milter Test mt1.lua
+
+REJECT
+ Milter Test mt2.lua
+
+REWRITE SUBJECT
+ Milter Test mt3.lua
+
+DEFER
+ Milter Test mt4.lua
+
+COMBINED TEST
+ Milter Test combined.lua
+
+*** Keywords ***
+Milter Test
+ [Arguments] ${mtlua}
+ Skip If not ${HAVE_MILTERTEST} msg=miltertest not installed
+ ${result} = Run Process miltertest -Dport\=${RSPAMD_PORT_PROXY} -Dhost\=${RSPAMD_LOCAL_ADDR} -s ${RSPAMD_TESTDIR}/lua/miltertest/${mtlua}
+ ... cwd=${RSPAMD_TESTDIR}/lua/miltertest
+ Should Match Regexp ${result.stderr} ^$
+ Log ${result.rc}
+ Log ${result.stdout}
+ Should Be Equal As Integers ${result.rc} 0 msg=${result.stdout} values=false
diff --git a/test/functional/cases/210_clickhouse/001_migration.robot b/test/functional/cases/210_clickhouse/001_migration.robot
new file mode 100644
index 0000000..2153c23
--- /dev/null
+++ b/test/functional/cases/210_clickhouse/001_migration.robot
@@ -0,0 +1,91 @@
+*** Settings ***
+Documentation Checks if rspamd is able to upgrade migration schema from v0 (very initial) to v2
+Test Setup Clickhouse Setup
+Test Teardown Clickhosue Teardown
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Library clickhouse.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/clickhouse.conf
+${RSPAMD_SCOPE} Suite
+${CLICKHOUSE_PORT} ${18123}
+
+*** Test Cases ***
+# Usually broken
+#Migration
+ #Initial schema
+ # Prepare rspamd
+ # Sleep 2 #TODO: replace this check with waiting until migration finishes
+ # Column should exist rspamd Symbols.Scores
+ # Column should exist rspamd Attachments.Digest
+ # Column should exist rspamd Symbols.Scores
+ # Schema version should be 3
+# Upload new schema ${RSPAMD_TESTDIR}/data/initial_schema/schema.sql
+# Insert data rspamd ${RSPAMD_TESTDIR}/data/initial_schema/data.rspamd.sql
+# Insert data rspamd_asn ${RSPAMD_TESTDIR}/data/initial_schema/data.rspamd_asn.sql
+# Insert data rspamd_urls ${RSPAMD_TESTDIR}/data/initial_schema/data.rspamd_urls.sql
+# Insert data rspamd_emails ${RSPAMD_TESTDIR}/data/initial_schema/data.rspamd_emails.sql
+# Insert data rspamd_symbols ${RSPAMD_TESTDIR}/data/initial_schema/data.rspamd_symbols.sql
+# Insert data rspamd_attachments ${RSPAMD_TESTDIR}/data/initial_schema/data.rspamd_attachments.sql
+# Prepare rspamd
+# Sleep 2 #TODO: replace this check with waiting until migration finishes
+# Column should exist rspamd Symbols.Scores
+# Column should exist rspamd Attachments.Digest
+# Column should exist rspamd Symbols.Scores
+# # Added in schema version 7
+# Column should exist rspamd Helo
+# Column should exist rspamd SMTPRecipients
+# # Added in schema version 8
+# Column should exist rspamd Groups.Scores
+# Schema version should be 8
+
+# Eventually broken
+#Retention
+# Upload new schema ${RSPAMD_TESTDIR}/data/schema_2/schema.sql
+# Insert data rspamd ${RSPAMD_TESTDIR}/data/schema_2/data.rspamd.sql
+# Assert rows count rspamd 56
+# Prepare rspamd
+# Sleep 2 #TODO: replace this check with waiting until migration finishes
+# Assert rows count rspamd 30
+
+*** Keywords ***
+Clickhouse Setup
+ ${RSPAMD_TMPDIR} = Make Temporary Directory
+ Set Suite Variable ${RSPAMD_TMPDIR}
+ Set Directory Ownership ${RSPAMD_TMPDIR} ${RSPAMD_USER} ${RSPAMD_GROUP}
+ ${template} = Get File ${RSPAMD_TESTDIR}/configs/clickhouse-config.xml
+ ${config} = Replace Variables ${template}
+ Create File ${RSPAMD_TMPDIR}/clickhouse-config.xml ${config}
+ Copy File ${RSPAMD_TESTDIR}/configs/clickhouse-users.xml ${RSPAMD_TMPDIR}/users.xml
+ Create Directory ${RSPAMD_TMPDIR}/clickhouse
+ Set Directory Ownership ${RSPAMD_TMPDIR}/clickhouse clickhouse clickhouse
+ ${result} = Run Process su -s /bin/sh clickhouse -c
+ ... clickhouse-server --daemon --config-file\=${RSPAMD_TMPDIR}/clickhouse-config.xml --pid-file\=${RSPAMD_TMPDIR}/clickhouse/clickhouse.pid
+ IF ${result.rc} != 0
+ Log ${result.stderr}
+ END
+ Should Be Equal As Integers ${result.rc} 0
+ Wait Until Keyword Succeeds 5 sec 50 ms TCP Connect localhost ${CLICKHOUSE_PORT}
+ Set Suite Variable ${RSPAMD_TMPDIR} ${RSPAMD_TMPDIR}
+
+Clickhosue Teardown
+ # Sleep 30
+ ${clickhouse_pid} = Get File ${RSPAMD_TMPDIR}/clickhouse/clickhouse.pid
+ Shutdown Process With Children ${clickhouse_pid}
+ Log File ${RSPAMD_TMPDIR}/clickhouse/clickhouse-server.err.log
+ Rspamd Teardown
+
+Prepare rspamd
+ &{d} = Run Rspamd CONFIG=${RSPAMD_TESTDIR}/configs/clickhouse.conf TMPDIR=${RSPAMD_TMPDIR}
+ ${keys} = Get Dictionary Keys ${d}
+ FOR ${i} IN @{keys}
+ IF '${RSPAMD_SCOPE}' == 'Suite'
+ Set Suite Variable ${${i}} ${d}[${i}]
+ ELSE IF '${RSPAMD_SCOPE}' == 'Test'
+ Set Test Variable ${${i}} ${d}[${i}]
+ ELSE
+ Fail 'RSPAMD_SCOPE must be Test or Suite'
+ END
+ END
diff --git a/test/functional/cases/210_clickhouse/clickhouse.py b/test/functional/cases/210_clickhouse/clickhouse.py
new file mode 100644
index 0000000..69430eb
--- /dev/null
+++ b/test/functional/cases/210_clickhouse/clickhouse.py
@@ -0,0 +1,78 @@
+import requests
+import json
+from robot.libraries.BuiltIn import BuiltIn
+from robot.api import logger
+
+__client = None
+
+
+class Client:
+ def __init__(self):
+ self.port = BuiltIn().get_variable_value('${CLICKHOUSE_PORT}', default=18123)
+
+ def get_query_string(self):
+ return "http://localhost:%d/?default_format=JSONEachRow" % (self.port)
+
+ def execute(self, sql):
+ r = requests.post(self.get_query_string(), sql)
+ logger.info("Client.execute: response: %s" % str(r))
+ if r.status_code != 200:
+ raise Exception("Clickhouse request failed: " + r.content)
+ return r
+
+ def query(self, sql):
+ r = self.execute(sql)
+
+ # logger.console("decoding " + r.content)
+ # [logger.console("decoding " + _) for _ in r.content.strip().split("\n")]
+ response = [json.loads(_) for _ in r.content.strip().split("\n")]
+ return response
+
+
+def client():
+ global __client
+ if __client is None:
+ __client = Client()
+ return __client
+
+
+def upload_new_schema(schema_file):
+ with open(schema_file, 'r') as content_file:
+ content = content_file.read()
+
+ queries = content.split(";")
+ for q in queries:
+ if q.strip() == "":
+ continue
+ client().execute(q) # throws exception on error
+
+
+def insert_data(table_name, filename):
+ with open(filename, 'r') as content_file:
+ content = content_file.read()
+
+ client().execute("insert into %s format Values %s;" % (table_name, content)) # throws exception on error
+
+
+def column_should_exist(table_name, column_name):
+ sql = "select hasColumnInTable('default', '%s', '%s') as is_exist" % (table_name, column_name)
+ r = client().query(sql)
+ logger.info("response: %s" % str(r))
+ if r[0]['is_exist'] != 1:
+ raise Exception("Failed asseting that column '%s' exists in table 'default'.'%s'" % (column_name, table_name))
+
+
+def schema_version_should_be(version):
+ sql = "select max(Version) as version from rspamd_version"
+ r = client().query(sql)
+ logger.info("response: %s" % str(r))
+ if r[0]['version'] != int(version):
+ raise Exception("Failed asseting that schema version is %d, %d schema version in ClickHouse" % (int(version), int(r[0]['version'])))
+
+
+def assert_rows_count(table_name, number):
+ sql = "select count(*) as cnt from %s" % table_name
+ r = client().query(sql)
+ logger.info("response: %s" % str(r))
+ if int(r[0]['cnt']) != int(number):
+ raise Exception("Failed asserting that table '%s' has %d rows (actual number: %d)" % (table_name, int(number), int(r[0]['cnt'])))
diff --git a/test/functional/cases/220_http.robot b/test/functional/cases/220_http.robot
new file mode 100644
index 0000000..b3c42a3
--- /dev/null
+++ b/test/functional/cases/220_http.robot
@@ -0,0 +1,74 @@
+*** Settings ***
+Test Setup Http Setup
+Test Teardown Http Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/http.lua
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+Simple HTTP request
+ Scan File ${MESSAGE} Url=/request Method=get
+ ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+ Expect Symbols HTTP_DNS_200 HTTP_200 HTTP_CORO_DNS_200
+ Expect Symbol With Exact Options HTTP_CORO_200 hello world
+
+ Scan File ${MESSAGE} Url=/request Method=post
+ ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+ Expect Symbols HTTP_DNS_200 HTTP_200 HTTP_CORO_DNS_200
+ Expect Symbol With Exact Options HTTP_CORO_200 hello post
+
+HTTP request 403
+ Scan File ${MESSAGE} Url=/error_403 Method=get
+ ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+ Expect Symbols HTTP_DNS_403 HTTP_403 HTTP_CORO_DNS_403 method_get
+
+ Scan File ${MESSAGE} Url=/error_403 Method=post
+ ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+ Expect Symbols HTTP_DNS_403 HTTP_403 HTTP_CORO_DNS_403 method_post
+
+HTTP timeout
+ Scan File ${MESSAGE} Url=/timeout Method=get
+ ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+ Expect Symbols HTTP_ERROR HTTP_ERROR HTTP_CORO_DNS_ERROR HTTP_CORO_ERROR method_get
+ # FIXME: where is "IO timeout"
+
+ Scan File ${MESSAGE} Url=/timeout Method=post
+ ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+ Expect Symbols HTTP_ERROR HTTP_ERROR HTTP_CORO_DNS_ERROR HTTP_CORO_ERROR method_post
+ # FIXME: where is "IO timeout"
+
+# Broken as dummy server is now not so dummy, not sure what is expected to be tested here
+#HTTP empty response
+# Scan File ${MESSAGE} Url=/empty Method=get
+# ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+# Expect Symbols HTTP_ERROR HTTP_ERROR HTTP_CORO_DNS_ERROR HTTP_CORO_ERROR method_get
+# # FIXME: where is "IO read error: unexpected EOF"
+#
+# Scan File ${MESSAGE} Url=/empty Method=post
+# ... Settings={symbols_enabled = [SIMPLE_HTTP_TEST]}
+# Expect Symbols HTTP_ERROR HTTP_ERROR HTTP_CORO_DNS_ERROR HTTP_CORO_ERROR method_post
+# # FIXME: where is "IO read error: unexpected EOF"
+
+SSL Large HTTP request
+ Scan File ${MESSAGE}
+ ... Settings={symbols_enabled = [LARGE_HTTP_TEST]}
+ Expect Symbol HTTP_SSL_LARGE
+
+*** Keywords ***
+Http Setup
+ Run Dummy Http
+ Run Dummy Https
+ Rspamd Setup
+
+Http Teardown
+ Rspamd Teardown
+ Dummy Http Teardown
+ Dummy Https Teardown
diff --git a/test/functional/cases/230_tcp.robot b/test/functional/cases/230_tcp.robot
new file mode 100644
index 0000000..d53a7fc
--- /dev/null
+++ b/test/functional/cases/230_tcp.robot
@@ -0,0 +1,75 @@
+*** Settings ***
+Suite Setup Servers Setup
+Suite Teardown Servers Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/tcp.lua
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+Simple TCP request
+ Scan File ${MESSAGE}
+ ... Settings={symbols_enabled = [SIMPLE_TCP_ASYNC_TEST]}
+ Expect Symbol HTTP_ASYNC_RESPONSE
+ Expect Symbol HTTP_ASYNC_RESPONSE_2
+
+#SSL TCP request
+# Scan File ${MESSAGE}
+# ... Settings={symbols_enabled = [SIMPLE_TCP_ASYNC_SSL_TEST]}
+# Expect Symbol With Exact Options TCP_SSL_RESPONSE hello
+# Expect Symbol With Exact Options TCP_SSL_RESPONSE_2 hello
+
+#SSL Large TCP request
+# Scan File ${MESSAGE}
+# ... Settings={symbols_enabled = [LARGE_TCP_ASYNC_SSL_TEST]}
+# Expect Symbol TCP_SSL_LARGE
+# Expect Symbol TCP_SSL_LARGE_2
+
+Sync API TCP request
+ Scan File ${MESSAGE}
+ ... Settings={symbols_enabled = [SIMPLE_TCP_TEST]}
+ Expect Symbol HTTP_SYNC_RESPONSE
+ Should Contain ${SCAN_RESULT}[symbols][HTTP_SYNC_RESPONSE][options][0] hello world
+ Should Contain ${SCAN_RESULT}[symbols][HTTP_SYNC_RESPONSE_2][options][0] hello post
+
+Sync API TCP get request
+ #Check url /request get HTTP_SYNC_EOF_get hello world
+ Check url /request get HTTP_SYNC_CONTENT_get hello world
+
+# Broken due to dummy_https issues, disable for now
+#Sync API TCP post request
+# Check url /request post HTTP_SYNC_EOF_post hello post
+# Check url /content-length post HTTP_SYNC_CONTENT_post hello post
+
+*** Keywords ***
+Servers Setup
+ Run Dummy Http
+ Run Dummy Ssl
+ Rspamd Setup
+
+Servers Teardown
+ Dummy Http Teardown
+ Rspamd Teardown
+ Teardown Dummy Ssl
+
+Run Dummy Ssl
+ [Arguments]
+ ${result} = Start Process ${RSPAMD_TESTDIR}/util/dummy_ssl.py ${RSPAMD_TESTDIR}/util/server.pem
+ Wait Until Created /tmp/dummy_ssl.pid timeout=2 second
+
+Teardown Dummy Ssl
+ ${ssl_pid} = Get File /tmp/dummy_ssl.pid
+ Shutdown Process With Children ${ssl_pid}
+
+Check url
+ [Arguments] ${url} ${method} ${expect_symbol} @{expect_options}
+ Scan File ${MESSAGE} URL=${url} Method=${method}
+ ... Settings={symbols_enabled = [HTTP_TCP_TEST]}
+ Expect Symbol With Exact Options ${expect_symbol} @{expect_options}
diff --git a/test/functional/cases/231_tcp_down.robot b/test/functional/cases/231_tcp_down.robot
new file mode 100644
index 0000000..5d6c791
--- /dev/null
+++ b/test/functional/cases/231_tcp_down.robot
@@ -0,0 +1,28 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/lua_test.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/tcp.lua
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+
+*** Test Cases ***
+Sync API TCP get request when server is down
+ [Documentation] We don't create HTTP server here, that's why
+ ... all requests fail with "connection refused"
+ Check url /request get HTTP_ASYNC_RESPONSE Socket error detected: Connection refused
+ Check url /content-length get HTTP_SYNC_WRITE_ERROR Socket error detected: Connection refused
+
+*** Keywords ***
+Check url
+ [Arguments] ${url} ${method} ${expect_symbol} @{expect_options}
+ Scan File ${MESSAGE} URL=${url} Method=${method}
+ ... Settings={symbols_enabled = [SIMPLE_TCP_ASYNC_TEST, SIMPLE_TCP_TEST]}
+ Expect Symbol With Exact Options ${expect_symbol} @{expect_options}
diff --git a/test/functional/cases/241_redis_is_dead.robot b/test/functional/cases/241_redis_is_dead.robot
new file mode 100644
index 0000000..4c12c14
--- /dev/null
+++ b/test/functional/cases/241_redis_is_dead.robot
@@ -0,0 +1,26 @@
+*** Settings ***
+Documentation Test the case when trying to connect to nowhere
+... (i.e. redis is not running)
+Test Setup Rspamd Setup
+Test Teardown Rspamd Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/redis.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${REDIS_SCOPE} Test
+${RSPAMD_LUA_SCRIPT} ${RSPAMD_TESTDIR}/lua/redis.lua
+${RSPAMD_SCOPE} Test
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+
+*** Test Cases ***
+Dead Redis client
+ Scan File ${MESSAGE}
+ Expect Symbol With Exact Options REDIS_ERROR_3 Connection refused
+ Expect Symbol With Exact Options REDIS_ASYNC201809_ERROR Connection refused
+ Expect Symbol With Exact Options REDIS_ASYNC_ERROR Connection refused
diff --git a/test/functional/cases/260_regex.robot b/test/functional/cases/260_regex.robot
new file mode 100644
index 0000000..d5055b0
--- /dev/null
+++ b/test/functional/cases/260_regex.robot
@@ -0,0 +1,33 @@
+*** Settings ***
+Test Setup Rspamd Setup
+Test Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/regexp.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/newlines.eml
+${RSPAMD_SCOPE} Test
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+${UTF_MESSAGE} ${RSPAMD_TESTDIR}/messages/utf.eml
+
+
+*** Test Cases ***
+Newlines
+ Scan File ${MESSAGE}
+ Expect Symbol SA_BODY_WORD
+ Expect Symbol SA_BODY_WORD_WITH_SPACE
+ Do Not Expect Symbol SA_BODY_WORD_WITH_NEWLINE
+ Expect Symbol SA_BODY_WORD_WITH_SPACE_BOUNDARIES
+ Expect Symbol SA_BODY_WORD_WITH_SPACE_BOUNDARIES_2
+ Expect Symbol SA_BODY_WORD_WITH_SPACE_BOUNDARIES_3
+ Expect Symbol SA_BODY_WORD_WITH_SPACE_AND_DOT
+ Expect Symbol With Option FOUND_URL https://google.com/maps/
+ Expect Symbol With Option FOUND_URL https://www.google.com/search?q\=hello world&oq\=hello world&aqs\=chrome..69i57j0l5.3045j0j7&sourceid\=chrome&ie\=UTF-8
+ Expect Symbol With Option FOUND_URL https://github.com/google/sanitizers/wiki/AddressSanitizer
+
+Dynamic Config
+ Scan File ${MESSAGE}
+ Expect Symbol With Score SA_BODY_WORD 10
+ Expect Required Score 20
diff --git a/test/functional/cases/320_arc_signing/001_simple.robot b/test/functional/cases/320_arc_signing/001_simple.robot
new file mode 100644
index 0000000..10078c6
--- /dev/null
+++ b/test/functional/cases/320_arc_signing/001_simple.robot
@@ -0,0 +1,31 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/arc_signing/simple.conf
+${MESSAGE_FAIL} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none1.eml
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Should Contain ${result.stdout} ARC_SIGNED
+
+TEST SIGNED HTTP HEADERS
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org --header=PerformArcSign:yes --header=DkimDomain:cacophony.za.org --header=DkimSelector:dkim --header=DkimPrivateKey:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANe3EETkiI1Exyrb+VzbMSt90K8MXJA0GcyNs6MFCs9JPaTh90Zu2l7ki7m5LTUx6350AR/3hcvwjSHCZjD6fvQ8/zfjN8kaLZ6DAaqtqSlpawIM+8glkuTEkIkpBED/OtDrba4Rd29iLFVuwQZXDtTjAAZKZPmtTZ5TXLrcCU6VAgMBAAECgYEA1BFvmBsIN8Gu/+6kNupya2xUNVM0yLu/xT5lpNV3LBO325oejAq8+d87kkl/LTW3a2jGFlQ0ICuLw+2mo24QUWRyv8if3oeBMlnLqHE+6wNjFVqo5sOjKzjO363xSXwXNUrBT7jDhnZcDN8w3/FecYKjifGTVtUs1SLsYwhlc8ECQQDuCRymLZQ/imPn5eFVIydwUzg8ptZlvoA7bfIxUL9BQRX33s59kLCilA0tTed8Dd+GnxsT93XOj1ApIfBwmTSlAkEA5/63PDsN7fH+WInqVD8nU07M9S8LcGDlPbVVBr2S2I78/iwrSDAYtbkU2vEbhFK/JuKNML2j8OkzV3v1QulfMQJBALDzhx+l/HHr3+8RPhx7QKNIyiKUaAdEwbDsP8IXY8YPq1QThu9jM1v4sX7/TdkzuvoppwiFykbe1NlvCH279p0CQCmTg4Ee0DtBcCSr6rvYaZLLf329RZ6JLuwlMCy6ErQOxBZFEiiovfTrS2qFZToMnkc4uLbwdY36LQJTq7unGTECQCCok8LzBeZtAw+TJofpOM3F2Rlm2qXiBVBeubhRedsiljG0hpvvLJBMppnQ6r27p5Jk39SmaTRkxEKrxPWWLNM=
+ Should Contain ${result.stdout} ARC_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG DOMAIN
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@example.tk
+ Should Not Contain ${result.stdout} ARC_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG PUBKEY
+ ${result} = Scan Message With Rspamc ${MESSAGE_FAIL} -u bob@invalid.za.org
+ Should Not Contain ${result.stdout} ARC_SIGNED
diff --git a/test/functional/cases/320_arc_signing/002_redis.robot b/test/functional/cases/320_arc_signing/002_redis.robot
new file mode 100644
index 0000000..ca979ad
--- /dev/null
+++ b/test/functional/cases/320_arc_signing/002_redis.robot
@@ -0,0 +1,29 @@
+*** Settings ***
+Suite Setup ARC Signing Setup
+Suite Teardown Rspamd Redis Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/arc_signing/redis.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/dmarc/fail_none.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+TEST SIGNED
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@cacophony.za.org
+ Should Contain ${result.stdout} ARC_SIGNED
+
+TEST NOT SIGNED - USERNAME WRONG DOMAIN
+ ${result} = Scan Message With Rspamc ${MESSAGE} -u bob@example.tk
+ Should Not Contain ${result.stdout} ARC_SIGNED
+
+*** Keywords ***
+ARC Signing Setup
+ Rspamd Redis Setup
+ Redis HSET TEST_DKIM_SELECTORS cacophony.za.org arc
+ ${key} = Get File ${RSPAMD_TESTDIR}/configs/dkim.key
+ Redis HSET TEST_DKIM_KEYS arc.cacophony.za.org ${key}
diff --git a/test/functional/cases/330_neural/001_autotrain.robot b/test/functional/cases/330_neural/001_autotrain.robot
new file mode 100644
index 0000000..793fc75
--- /dev/null
+++ b/test/functional/cases/330_neural/001_autotrain.robot
@@ -0,0 +1,63 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/neural.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+Train
+ Sleep 2s Wait for redis mess
+ FOR ${INDEX} IN RANGE 4 14
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1", "SPAM_SYMBOL2", "SPAM_SYMBOL3", "SPAM_SYMBOL${INDEX}"]}
+ Expect Symbol SPAM_SYMBOL${INDEX}
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1", "HAM_SYMBOL2", "HAM_SYMBOL3", "HAM_SYMBOL${INDEX}"]}
+ Expect Symbol HAM_SYMBOL${INDEX}
+ END
+
+Check Neural HAM
+ Sleep 5s Wait for neural to be loaded
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1","HAM_SYMBOL2","HAM_SYMBOL3","HAM_SYMBOL8"];groups_enabled=["neural"];symbols_disabled = ["NEURAL_LEARN"]}
+ Expect Symbol NEURAL_HAM_SHORT
+ Do Not Expect Symbol NEURAL_SPAM_SHORT
+ #Expect Symbol NEURAL_HAM_SHORT_PCA
+ #Do Not Expect Symbol NEURAL_SPAM_SHORT_PCA
+
+Check Neural SPAM
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1","SPAM_SYMBOL2","SPAM_SYMBOL3","SPAM_SYMBOL8"];groups_enabled=["neural"];symbols_disabled = ["NEURAL_LEARN"]}
+ Expect Symbol NEURAL_SPAM_SHORT
+ Do Not Expect Symbol NEURAL_HAM_SHORT
+ #Expect Symbol NEURAL_SPAM_SHORT_PCA
+ #Do Not Expect Symbol NEURAL_HAM_SHORT_PCA
+
+
+Train INVERSE
+ FOR ${INDEX} IN RANGE 4 14
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1", "SPAM_SYMBOL2", "SPAM_SYMBOL3","SPAM_SYMBOL${INDEX}"]; SPAM_SYMBOL1 = -5; SPAM_SYMBOL2 = -5; SPAM_SYMBOL3 = -5; SPAM_SYMBOL${INDEX} = -5;}
+ Expect Symbol SPAM_SYMBOL${INDEX}
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1", "HAM_SYMBOL2", "HAM_SYMBOL3","HAM_SYMBOL${INDEX}"]; HAM_SYMBOL1 = 5; HAM_SYMBOL2 = 5; HAM_SYMBOL3 = 5; HAM_SYMBOL${INDEX} = 5;}
+ Expect Symbol HAM_SYMBOL${INDEX}
+ END
+
+Check Neural HAM INVERSE
+ Sleep 5s Wait for neural to be loaded
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1","HAM_SYMBOL2","HAM_SYMBOL3","HAM_SYMBOL5",];groups_enabled=["neural"]}
+ Expect Symbol NEURAL_SPAM_SHORT
+ #Expect Symbol NEURAL_SPAM_SHORT_PCA
+ Do Not Expect Symbol NEURAL_HAM_SHORT
+ #Do Not Expect Symbol NEURAL_HAM_SHORT_PCA
+
+Check Neural SPAM INVERSE
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1","SPAM_SYMBOL2","SPAM_SYMBOL3","SPAM_SYMBOL5"];groups_enabled=["neural"]}
+ Expect Symbol NEURAL_HAM_SHORT
+ #Expect Symbol NEURAL_HAM_SHORT_PCA
+ Do Not Expect Symbol NEURAL_SPAM_SHORT
+ #Do Not Expect Symbol NEURAL_SPAM_SHORT_PCA \ No newline at end of file
diff --git a/test/functional/cases/330_neural/002_manualtrain.robot b/test/functional/cases/330_neural/002_manualtrain.robot
new file mode 100644
index 0000000..bbc6c2e
--- /dev/null
+++ b/test/functional/cases/330_neural/002_manualtrain.robot
@@ -0,0 +1,72 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Library Process
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/neural_noauto.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+Collect training vectors & train manually
+ @{HAM_VEC}= Create List
+ @{SPAM_VEC}= Create List
+
+ FOR ${INDEX} IN RANGE 4 14
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1", "SPAM_SYMBOL2", "SPAM_SYMBOL3", "SPAM_SYMBOL${INDEX}", "SAVE_NN_ROW"]}
+ Expect Symbol SPAM_SYMBOL${INDEX}
+ # Save neural inputs for later
+ ${SPAM_ROW} = Get File ${SCAN_RESULT}[symbols][SAVE_NN_ROW][options][0]
+ Remove File ${SCAN_RESULT}[symbols][SAVE_NN_ROW][options][0]
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1", "HAM_SYMBOL2", "HAM_SYMBOL3", "HAM_SYMBOL${INDEX}", "SAVE_NN_ROW"]}
+ Expect Symbol HAM_SYMBOL${INDEX}
+ # Save neural inputs for later
+ ${HAM_ROW} = Get File ${SCAN_RESULT}[symbols][SAVE_NN_ROW][options][0]
+ Remove File ${SCAN_RESULT}[symbols][SAVE_NN_ROW][options][0]
+ ${HAM_ROW} = Run ${RSPAMADM} lua -a ${HAM_ROW} ${RSPAMD_TESTDIR}/util/nn_unpack.lua
+ ${HAM_ROW} = Evaluate json.loads("${HAM_ROW}")
+ ${SPAM_ROW} = Run ${RSPAMADM} lua -a ${SPAM_ROW} ${RSPAMD_TESTDIR}/util/nn_unpack.lua
+ ${SPAM_ROW} = Evaluate json.loads("${SPAM_ROW}")
+ Append To List ${HAM_VEC} ${HAM_ROW}
+ Append To List ${SPAM_VEC} ${SPAM_ROW}
+ END
+
+ ${json1} = Evaluate json.dumps({"spam_vec": ${SPAM_VEC}, "ham_vec": ${HAM_VEC}, "rule": "SHORT"})
+ HTTP POST ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /plugins/neural/learn ${json1}
+
+ # Save variables for use in inverse training
+ Set Suite Variable ${HAM_VEC}
+ Set Suite Variable ${SPAM_VEC}
+
+ Sleep 5s Wait for neural to be loaded
+
+Check Neural HAM
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1","HAM_SYMBOL2","HAM_SYMBOL3","HAM_SYMBOL8"];groups_enabled=["neural"];symbols_disabled = ["NEURAL_LEARN"]}
+ Do Not Expect Symbol NEURAL_SPAM_SHORT
+ Expect Symbol NEURAL_HAM_SHORT
+
+Check Neural SPAM
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1","SPAM_SYMBOL2","SPAM_SYMBOL3","SPAM_SYMBOL8"];groups_enabled=["neural"];symbols_disabled = ["NEURAL_LEARN"]}
+ Do Not Expect Symbol NEURAL_HAM_SHORT
+ Expect Symbol NEURAL_SPAM_SHORT
+
+Train inverse
+ ${json2} = Evaluate json.dumps({"spam_vec": ${HAM_VEC}, "ham_vec": ${SPAM_VEC}, "rule": "SHORT"})
+ HTTP POST ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /plugins/neural/learn ${json2}
+ Sleep 5s Wait for neural to be loaded
+
+Check Neural HAM - inverse
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["HAM_SYMBOL1","HAM_SYMBOL2","HAM_SYMBOL3","HAM_SYMBOL8"];groups_enabled=["neural"];symbols_disabled = ["NEURAL_LEARN"]}
+ Do Not Expect Symbol NEURAL_HAM_SHORT
+ Expect Symbol NEURAL_SPAM_SHORT
+
+Check Neural SPAM - inverse
+ Scan File ${MESSAGE} Settings={symbols_enabled = ["SPAM_SYMBOL1","SPAM_SYMBOL2","SPAM_SYMBOL3","SPAM_SYMBOL8"];groups_enabled=["neural"];symbols_disabled = ["NEURAL_LEARN"]}
+ Do Not Expect Symbol NEURAL_SPAM_SHORT
+ Expect Symbol NEURAL_HAM_SHORT \ No newline at end of file
diff --git a/test/functional/cases/360_force_actions.robot b/test/functional/cases/360_force_actions.robot
new file mode 100644
index 0000000..18edf9a
--- /dev/null
+++ b/test/functional/cases/360_force_actions.robot
@@ -0,0 +1,44 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/force_actions.conf
+${MESSAGE} ${RSPAMD_TESTDIR}/messages/url7.eml
+${RSPAMD_SCOPE} Suite
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+FORCE ACTIONS from reject to add header
+ Scan File ${MESSAGE} Settings-Id=id_reject
+ Expect Action add header
+ Expect Symbol FORCE_ACTION_FORCE_REJECT_TO_ADD_HEADER
+
+FORCE ACTIONS from reject to no action
+ Scan File ${MESSAGE} Settings-Id=id_reject_no_action
+ Expect Action no action
+ Expect Symbol FORCE_ACTION_FORCE_REJECT_TO_NO_ACTION
+
+FORCE ACTIONS from no action to reject
+ Scan File ${MESSAGE} Settings-Id=id_no_action
+ Expect Action reject
+ Expect Symbol FORCE_ACTION_FORCE_NO_ACTION_TO_REJECT
+
+FORCE ACTIONS from no action to add header
+ Scan File ${MESSAGE} Settings-Id=id_no_action_to_add_header
+ Expect Action add header
+ Expect Symbol FORCE_ACTION_FORCE_NO_ACTION_TO_ADD_HEADER
+
+FORCE ACTIONS from add header to no action
+ Scan File ${MESSAGE} Settings-Id=id_add_header
+ Expect Action no action
+ Expect Symbol FORCE_ACTION_FORCE_ADD_HEADER_TO_NO_ACTION
+
+FORCE ACTIONS from add header to reject
+ Scan File ${MESSAGE} Settings-Id=id_add_header_to_reject
+ Expect Action reject
+ Expect Symbol FORCE_ACTION_FORCE_ADD_HEADER_TO_REJECT
+
diff --git a/test/functional/cases/380_external_relay.robot b/test/functional/cases/380_external_relay.robot
new file mode 100644
index 0000000..459aaac
--- /dev/null
+++ b/test/functional/cases/380_external_relay.robot
@@ -0,0 +1,48 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/merged.conf
+${RSPAMD_EXTERNAL_RELAY_ENABLED} true
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+EXTERNAL RELAY AUTHENTICATED
+ Scan File ${RSPAMD_TESTDIR}/messages/received5.eml
+ ... Settings={symbols_enabled [EXTERNAL_RELAY_TEST, EXTERNAL_RELAY_AUTHENTICATED]}
+ ... IP=8.8.8.8 User=user@example.net
+ Expect Symbol With Exact Options EXTERNAL_RELAY_TEST
+ ... IP=192.0.2.1 HOSTNAME=mail.example.org HELO=mail.example.org
+
+EXTERNAL RELAY COUNT
+ Scan File ${RSPAMD_TESTDIR}/messages/received4.eml
+ ... Settings={symbols_enabled [EXTERNAL_RELAY_TEST, EXTERNAL_RELAY_COUNT]}
+ ... IP=8.8.8.8
+ Expect Symbol With Exact Options EXTERNAL_RELAY_TEST
+ ... IP=151.18.193.131 HOSTNAME=ca-18-193-131.service.infuturo.it
+ ... HELO=ca-18-193-131.service.infuturo.it
+
+EXTERNAL RELAY HOSTNAME MAP
+ Scan File ${RSPAMD_TESTDIR}/messages/received6.eml
+ ... Settings={symbols_enabled [EXTERNAL_RELAY_TEST, EXTERNAL_RELAY_HOSTNAME_MAP]}
+ ... Hostname=lame.example.net IP=192.0.2.10
+ Expect Symbol With Exact Options EXTERNAL_RELAY_TEST
+ ... IP=192.0.2.1 HOSTNAME=mail.example.org HELO=mail.example.org
+
+EXTERNAL RELAY IP MAP
+ Scan File ${RSPAMD_TESTDIR}/messages/received7.eml
+ ... Settings={symbols_enabled [EXTERNAL_RELAY_TEST, EXTERNAL_RELAY_IP_MAP]}
+ ... IP=198.51.100.1
+ Expect Symbol With Exact Options EXTERNAL_RELAY_TEST
+ ... IP=4.31.198.44 HOSTNAME=foobar.example.org HELO=foobar.example.org
+
+EXTERNAL RELAY LOCAL
+ Scan File ${RSPAMD_TESTDIR}/messages/ham.eml
+ ... Settings={symbols_enabled [EXTERNAL_RELAY_TEST, EXTERNAL_RELAY_LOCAL]}
+ ... IP=127.0.0.1
+ Expect Symbol With Exact Options EXTERNAL_RELAY_TEST
+ ... IP=4.31.198.44 HOSTNAME=mail.ietf.org HELO=mail.ietf.org
diff --git a/test/functional/cases/400_known_senders.robot b/test/functional/cases/400_known_senders.robot
new file mode 100644
index 0000000..f258113
--- /dev/null
+++ b/test/functional/cases/400_known_senders.robot
@@ -0,0 +1,36 @@
+*** Settings ***
+Suite Setup Rspamd Redis Setup
+Suite Teardown Rspamd Redis Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/known_senders.conf
+${REDIS_SCOPE} Suite
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+UNKNOWN SENDER
+ Scan File ${RSPAMD_TESTDIR}/messages/spam_message.eml
+ ... Settings={symbols_enabled [KNOWN_SENDER]}
+ Do Not Expect Symbol KNOWN_SENDER
+ Expect Symbol UNKNOWN_SENDER
+
+UNKNOWN SENDER BECOMES KNOWN
+ Scan File ${RSPAMD_TESTDIR}/messages/spam_message.eml
+ ... Settings={symbols_enabled [KNOWN_SENDER]}
+ Expect Symbol KNOWN_SENDER
+ Do Not Expect Symbol UNKNOWN_SENDER
+
+UNKNOWN SENDER WRONG DOMAIN
+ Scan File ${RSPAMD_TESTDIR}/messages/empty_part.eml
+ ... Settings={symbols_enabled [KNOWN_SENDER]}
+ Do Not Expect Symbol KNOWN_SENDER
+ Do Not Expect Symbol UNKNOWN_SENDER
+
+UNKNOWN SENDER WRONG DOMAIN RESCAN
+ Scan File ${RSPAMD_TESTDIR}/messages/empty_part.eml
+ ... Settings={symbols_enabled [KNOWN_SENDER]}
+ Do Not Expect Symbol KNOWN_SENDER
+ Do Not Expect Symbol UNKNOWN_SENDER
diff --git a/test/functional/cases/410_logging/000_console/000_systemd_logger.robot b/test/functional/cases/410_logging/000_console/000_systemd_logger.robot
new file mode 100644
index 0000000..8817846
--- /dev/null
+++ b/test/functional/cases/410_logging/000_console/000_systemd_logger.robot
@@ -0,0 +1,24 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Systemd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/loggingtest.conf
+${RSPAMD_LOGGINGTYPE} console
+${RSPAMD_JSON} false
+${RSPAMD_SYSTEMD} true
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+EMPTY TEST
+ Pass Execution No worries
+
+*** Keywords ***
+Systemd Teardown
+ Touch ${RSPAMD_TMPDIR}/rspamd.log
+ Rspamd Teardown
+ ${log} = Get File ${EXECDIR}/robot-save/rspamd.stderr.last
+ Should Match Regexp ${log} \\n\\(main\\) lua; lua_cfg_transform\\.lua:\\d+: overriding actions from the legacy metric settings\\n
diff --git a/test/functional/cases/410_logging/000_console/001_timestamps.robot b/test/functional/cases/410_logging/000_console/001_timestamps.robot
new file mode 100644
index 0000000..bd8e2c3
--- /dev/null
+++ b/test/functional/cases/410_logging/000_console/001_timestamps.robot
@@ -0,0 +1,24 @@
+*** Settings ***
+Suite Setup Rspamd Setup
+Suite Teardown Console Timestamps Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/loggingtest.conf
+${RSPAMD_LOGGINGTYPE} console
+${RSPAMD_JSON} false
+${RSPAMD_SYSTEMD} false
+${RSPAMD_SCOPE} Suite
+
+*** Test Cases ***
+EMPTY TEST
+ Pass Execution No worries
+
+*** Keywords ***
+Console Timestamps Teardown
+ Touch ${RSPAMD_TMPDIR}/rspamd.log
+ Rspamd Teardown
+ ${log} = Get File ${EXECDIR}/robot-save/rspamd.stderr.last
+ Should Match Regexp ${log} \\n\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} #\\d+\\(main\\) lua; lua_cfg_transform\\.lua:\\d+: overriding actions from the legacy metric settings\\n
diff --git a/test/functional/cases/410_logging/001_file/000_json.robot b/test/functional/cases/410_logging/001_file/000_json.robot
new file mode 100644
index 0000000..a2f04e8
--- /dev/null
+++ b/test/functional/cases/410_logging/001_file/000_json.robot
@@ -0,0 +1,17 @@
+*** Settings ***
+Test Setup Rspamd Setup
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/loggingtest.conf
+${RSPAMD_LOGGINGTYPE} file
+${RSPAMD_JSON} true
+${RSPAMD_SYSTEMD} true
+${RSPAMD_SCOPE} Test
+
+*** Test Cases ***
+JSON LOGS
+ Rspamd Teardown
+ Check JSON Log ${EXECDIR}/robot-save/rspamd.log.last
diff --git a/test/functional/cases/__init__.robot b/test/functional/cases/__init__.robot
new file mode 100644
index 0000000..51192d7
--- /dev/null
+++ b/test/functional/cases/__init__.robot
@@ -0,0 +1,19 @@
+*** Settings ***
+Suite Setup Export Global Variables
+Library ../lib/rspamd.py
+Variables ../lib/vars.py
+
+*** Keywords ***
+Export Global Variables
+ ${RSPAMD_TESTDIR} = Get Test Directory
+ ${TOPDIR} = Get Top Dir
+ ${RSPAMADM} = Get Rspamadm
+ ${RSPAMC} = Get Rspamc
+ ${RSPAMD} = Get Rspamd
+ ${RSPAMD_INSTALLROOT} = Get Install Root
+ Set Global Variable ${RSPAMD_INSTALLROOT}
+ Set Global Variable ${RSPAMADM}
+ Set Global Variable ${RSPAMC}
+ Set Global Variable ${RSPAMD}
+ Set Global Variable ${RSPAMD_TESTDIR}
+ Set Global Variable ${TOPDIR}