use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use reddb_server::runtime::ai::cdc_enrichment::{CdcEnrichmentConsumer, EnrichmentKind};
use reddb_server::runtime::ai::moderation::{
install_local_moderation_backend, LocalModerationBackend, ModerationOutcome, ModerationRequest,
};
use reddb_server::{RedDBOptions, RedDBRuntime};
const TOXIC_MARKER: &str = "TOXIC";
static PROVIDER_DOWN: AtomicBool = AtomicBool::new(false);
struct MockModerationProvider;
impl LocalModerationBackend for MockModerationProvider {
fn moderate(
&self,
request: &ModerationRequest,
) -> reddb_server::RedDBResult<ModerationOutcome> {
if PROVIDER_DOWN.load(Ordering::SeqCst) {
return Ok(ModerationOutcome::ProviderDown {
reason: "mock provider down".to_string(),
});
}
if request.text.contains(TOXIC_MARKER) {
return Ok(ModerationOutcome::Reject {
categories: vec!["harassment".to_string()],
});
}
Ok(ModerationOutcome::Allow)
}
}
fn test_guard() -> MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[must_use]
fn install_mock() -> MutexGuard<'static, ()> {
let guard = test_guard();
PROVIDER_DOWN.store(false, Ordering::SeqCst);
install_local_moderation_backend(Arc::new(MockModerationProvider));
guard
}
fn row_count(rt: &RedDBRuntime, sql: &str) -> usize {
rt.execute_query(sql)
.unwrap_or_else(|e| panic!("query failed: {sql}\n err: {e}"))
.result
.records
.len()
}
fn create_moderated_table(rt: &RedDBRuntime, name: &str, extra: &str) {
rt.execute_query(&format!(
"CREATE TABLE {name} (id INT, body TEXT) \
WITH (MODERATE (fields = ('body'), provider = 'local', \
model = 'mock-moderation', sync = true{extra}))"
))
.unwrap_or_else(|e| panic!("create {name}: {e}"));
}
#[test]
fn reject_blocks_write() {
let _guard = install_mock();
let rt = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime boots");
create_moderated_table(&rt, "posts", "");
rt.execute_query("INSERT INTO posts (id, body) VALUES (1, 'a friendly hello')")
.expect("clean insert commits");
assert_eq!(row_count(&rt, "SELECT * FROM posts"), 1);
let err = rt
.execute_query(&format!(
"INSERT INTO posts (id, body) VALUES (2, 'this is {TOXIC_MARKER} content')"
))
.expect_err("toxic insert must be rejected");
assert!(
err.to_string().to_lowercase().contains("moderation"),
"reject error should mention moderation: {err}"
);
assert_eq!(
row_count(&rt, "SELECT * FROM posts"),
1,
"rejected row must not persist"
);
}
#[test]
fn provider_down_quarantine_then_clear_on_remoderation() {
let _guard = install_mock();
let rt = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime boots");
create_moderated_table(&rt, "msgs", "");
PROVIDER_DOWN.store(true, Ordering::SeqCst);
rt.execute_query("INSERT INTO msgs (id, body) VALUES (1, 'benign while provider down')")
.expect("quarantined insert still commits");
assert_eq!(
row_count(&rt, "SELECT * FROM msgs"),
0,
"quarantine-pending row must be hidden from normal reads"
);
PROVIDER_DOWN.store(false, Ordering::SeqCst);
let mut consumer = CdcEnrichmentConsumer::with_defaults();
let stats = consumer.tick(&rt, 1_000).expect("tick");
assert!(stats.ingested >= 1, "the quarantined row must be ingested");
assert!(stats.attached >= 1, "re-moderation must complete");
assert_eq!(consumer.pending_len(), 0);
assert_eq!(
row_count(&rt, "SELECT * FROM msgs"),
1,
"cleared row must surface after a clean re-moderation"
);
let again = consumer.tick(&rt, 2_000).expect("second tick");
assert_eq!(again.ingested, 0, "cleared row must not re-enqueue");
}
#[test]
fn quarantine_then_remoderation_reject_tombstones() {
let _guard = install_mock();
let rt = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime boots");
create_moderated_table(&rt, "notes", "");
PROVIDER_DOWN.store(true, Ordering::SeqCst);
rt.execute_query(&format!(
"INSERT INTO notes (id, body) VALUES (1, 'sneaky {TOXIC_MARKER} note')"
))
.expect("quarantined insert commits");
assert_eq!(
row_count(&rt, "SELECT * FROM notes"),
0,
"quarantine-pending row hidden"
);
PROVIDER_DOWN.store(false, Ordering::SeqCst);
let mut consumer = CdcEnrichmentConsumer::with_defaults();
let stats = consumer.tick(&rt, 1_000).expect("tick");
assert!(stats.ingested >= 1);
assert!(stats.attached >= 1, "re-moderation must complete");
assert_eq!(
row_count(&rt, "SELECT * FROM notes"),
0,
"rejected-tombstone row must remain hidden"
);
let again = consumer.tick(&rt, 2_000).expect("second tick");
assert_eq!(again.ingested, 0, "tombstoned row must not re-enqueue");
}
#[test]
fn quarantine_reject_hard_delete_removes_row() {
let _guard = install_mock();
let rt = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime boots");
create_moderated_table(&rt, "ephemeral", ", hard_delete = true");
PROVIDER_DOWN.store(true, Ordering::SeqCst);
rt.execute_query(&format!(
"INSERT INTO ephemeral (id, body) VALUES (1, 'bad {TOXIC_MARKER} stuff')"
))
.expect("quarantined insert commits");
PROVIDER_DOWN.store(false, Ordering::SeqCst);
let mut consumer = CdcEnrichmentConsumer::with_defaults();
let stats = consumer.tick(&rt, 1_000).expect("tick");
assert!(stats.attached >= 1, "re-moderation must complete");
assert_eq!(row_count(&rt, "SELECT * FROM ephemeral"), 0);
let again = consumer.tick(&rt, 2_000).expect("second tick");
assert_eq!(again.ingested, 0);
}
#[test]
fn fail_closed_blocks_write_when_provider_down() {
let _guard = install_mock();
let rt = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime boots");
create_moderated_table(&rt, "strict", ", degraded = closed");
PROVIDER_DOWN.store(true, Ordering::SeqCst);
let err = rt
.execute_query("INSERT INTO strict (id, body) VALUES (1, 'anything at all')")
.expect_err("fail-closed must block the write when provider is down");
assert!(
err.to_string().to_lowercase().contains("moderation")
|| err.to_string().to_lowercase().contains("blocked"),
"fail-closed error should explain the block: {err}"
);
PROVIDER_DOWN.store(false, Ordering::SeqCst);
assert_eq!(
row_count(&rt, "SELECT * FROM strict"),
0,
"fail-closed write must not persist"
);
let mut consumer = CdcEnrichmentConsumer::with_defaults();
let stats = consumer.tick(&rt, 1_000).expect("tick");
assert_eq!(stats.ingested, 0, "no quarantined row exists to ingest");
rt.execute_query("INSERT INTO strict (id, body) VALUES (2, 'now it is fine')")
.expect("provider-up write commits");
assert_eq!(row_count(&rt, "SELECT * FROM strict"), 1);
}
#[test]
fn moderate_enrichment_kind_is_public() {
let kind = EnrichmentKind::Moderate;
assert_eq!(kind, EnrichmentKind::Moderate);
}