Fix the antispam test: pin the rules it scores against, stop live Pyzor
It failed everywhere but upstream's machines, for two reasons: - The spam rules, which carry every score, came from a path on an upstream developer's own disk. Without SPAM_RULES_URL none loaded, every score was 0.00 and the combined case came out ham instead of spam at 13.70. The published rules of spam-filter v3.0.2 are now pinned beside the test cases (Apache-2.0 or MIT, taken as MIT; in THIRD-PARTY.md). SPAM_RULES_URL still overrides. - The first combined case expects a Pyzor hit, and its digest (that of an empty body) wasn't among the three the test mode answers, so it went to a public Pyzor server: it failed offline and would drift with that server's counts. Test mode now answers every digest from a fixed table, with the empty body's added, and never reaches the network. The test passes online and offline, alone and with the rest of the SMTP tests. queue_retry, unrelated, still fails when it runs after the others in one process, though it passes alone every time.
This commit is contained in:
@@ -24,6 +24,7 @@ carry their own license files.
|
|||||||
| `crates/common/src/network/acme/directory.rs`, `crates/common/src/network/acme/jose.rs`, `crates/common/src/network/acme/order.rs` | [rustls-acme](https://github.com/FlorianUekermann/rustls-acme) (MIT or Apache-2.0) | Copyright (c) Florian Uekermann |
|
| `crates/common/src/network/acme/directory.rs`, `crates/common/src/network/acme/jose.rs`, `crates/common/src/network/acme/order.rs` | [rustls-acme](https://github.com/FlorianUekermann/rustls-acme) (MIT or Apache-2.0) | Copyright (c) Florian Uekermann |
|
||||||
| `crates/types/src/id.rs` | [crockford](https://github.com/archer884/crockford) (MIT or Apache-2.0) | Copyright (c) 2017 J/A <archer884@gmail.com> |
|
| `crates/types/src/id.rs` | [crockford](https://github.com/archer884/crockford) (MIT or Apache-2.0) | Copyright (c) 2017 J/A <archer884@gmail.com> |
|
||||||
| `crates/nlp/src/tokenizers/types.rs` | test cases from [linkify](https://github.com/robinst/linkify) (MIT or Apache-2.0) | Copyright (c) 2017 Robin Stocker |
|
| `crates/nlp/src/tokenizers/types.rs` | test cases from [linkify](https://github.com/robinst/linkify) (MIT or Apache-2.0) | Copyright (c) 2017 Robin Stocker |
|
||||||
|
| `tests/resources/smtp/antispam/spam-filter-rules.json.gz` | the published rules of [spam-filter](https://github.com/stalwartlabs/spam-filter) v3.0.2, unmodified, for the spam filter's tests (MIT or Apache-2.0) | Copyright (C) 2024, Stalwart Labs LLC |
|
||||||
|
|
||||||
Each notice above applies with this permission notice:
|
Each notice above applies with this permission notice:
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use common::config::mailstore::spamfilter::PyzorConfig;
|
use common::config::mailstore::spamfilter::PyzorConfig;
|
||||||
@@ -43,35 +45,14 @@ pub(crate) async fn pyzor_check(
|
|||||||
// Hash message
|
// Hash message
|
||||||
let request = message.pyzor_check_message();
|
let request = message.pyzor_check_message();
|
||||||
|
|
||||||
|
// Send message to address. inbuxa: in tests, a fixed table answers
|
||||||
|
// instead of a public server (test_response).
|
||||||
|
#[cfg(not(feature = "test_mode"))]
|
||||||
|
let response = pyzor_send_message(config.address, config.timeout, &request).await;
|
||||||
#[cfg(feature = "test_mode")]
|
#[cfg(feature = "test_mode")]
|
||||||
{
|
let response = std::io::Result::Ok(test_response(&request));
|
||||||
if request.contains("b5b476f0b5ba6e1c038361d3ded5818dd39c90a2") {
|
|
||||||
return Ok(PyzorResponse {
|
|
||||||
code: 200,
|
|
||||||
count: 1000,
|
|
||||||
wl_count: 0,
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
} else if request.contains("d67d4b8bfc3860449e3418bb6017e2612f3e2a99") {
|
|
||||||
return Ok(PyzorResponse {
|
|
||||||
code: 200,
|
|
||||||
count: 60,
|
|
||||||
wl_count: 10,
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
} else if request.contains("81763547012b75e57a20d18ce0b93014208cdfdb") {
|
|
||||||
return Ok(PyzorResponse {
|
|
||||||
code: 200,
|
|
||||||
count: 50,
|
|
||||||
wl_count: 20,
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send message to address
|
response
|
||||||
pyzor_send_message(config.address, config.timeout, &request)
|
|
||||||
.await
|
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
trc::SpamEvent::PyzorError
|
trc::SpamEvent::PyzorError
|
||||||
@@ -82,6 +63,32 @@ pub(crate) async fn pyzor_check(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// inbuxa: the answers tests get, by digest, instead of a public server's,
|
||||||
|
/// whose counts change and which a test may not be able to reach. Upstream
|
||||||
|
/// answered the first three here and sent every other digest to the network.
|
||||||
|
#[cfg(feature = "test_mode")]
|
||||||
|
fn test_response(request: &str) -> PyzorResponse {
|
||||||
|
let (count, wl_count) = if request.contains("b5b476f0b5ba6e1c038361d3ded5818dd39c90a2")
|
||||||
|
// The digest of an empty body, as an HTML-only message with no text
|
||||||
|
// to hash produces; public servers report it widely.
|
||||||
|
|| request.contains("da39a3ee5e6b4b0d3255bfef95601890afd80709")
|
||||||
|
{
|
||||||
|
(1000, 0)
|
||||||
|
} else if request.contains("d67d4b8bfc3860449e3418bb6017e2612f3e2a99") {
|
||||||
|
(60, 10)
|
||||||
|
} else if request.contains("81763547012b75e57a20d18ce0b93014208cdfdb") {
|
||||||
|
(50, 20)
|
||||||
|
} else {
|
||||||
|
(0, 0)
|
||||||
|
};
|
||||||
|
PyzorResponse {
|
||||||
|
code: 200,
|
||||||
|
count,
|
||||||
|
wl_count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "test_mode", allow(dead_code))]
|
||||||
async fn pyzor_send_message(
|
async fn pyzor_send_message(
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
|
|||||||
Binary file not shown.
@@ -81,9 +81,18 @@ async fn antispam() {
|
|||||||
admin
|
admin
|
||||||
.registry_create_object(SpamSettings {
|
.registry_create_object(SpamSettings {
|
||||||
score_spam: Float::new(5.0),
|
score_spam: Float::new(5.0),
|
||||||
|
// inbuxa: the rules carry the scores the expectations are written
|
||||||
|
// against, so they're pinned (spam-filter v3.0.2, beside the test
|
||||||
|
// cases) rather than read from a developer's own checkout, which
|
||||||
|
// left every score at zero. SPAM_RULES_URL still overrides.
|
||||||
spam_filter_rules_url: std::env::var("SPAM_RULES_URL")
|
spam_filter_rules_url: std::env::var("SPAM_RULES_URL")
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
"file:///Users/me/code/spam-filter/spam-filter-rules.json.gz".to_string()
|
concat!(
|
||||||
|
"file://",
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/resources/smtp/antispam/spam-filter-rules.json.gz"
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -156,7 +165,7 @@ async fn antispam() {
|
|||||||
}))
|
}))
|
||||||
.await;
|
.await;
|
||||||
// inbuxa: a rules file that can't be read is retried later; don't wait
|
// inbuxa: a rules file that can't be read is retried later; don't wait
|
||||||
// for that retry (the path above is a developer's own checkout)
|
// for that retry (SPAM_RULES_URL may name one that isn't there)
|
||||||
test.wait_for_tasks_skip_not_due().await;
|
test.wait_for_tasks_skip_not_due().await;
|
||||||
admin.reload_settings().await;
|
admin.reload_settings().await;
|
||||||
admin.reload_lookup_stores().await;
|
admin.reload_lookup_stores().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user