Features: - Core moderation: warn, kick, ban, timeout, strike system - Automod: banned words filter, scam detection, anti-spam, link filtering - AI moderation: Claude/OpenAI integration, NSFW detection, phishing analysis - Verification system: button, captcha, math, emoji challenges - Rate limiting system with configurable scopes - Event logging: joins, leaves, message edits/deletes, voice activity - Per-guild configuration with caching - Docker deployment support Bug fixes applied: - Fixed await on session.delete() in guild_config.py - Fixed memory leak in AI moderation message tracking (use deque) - Added error handling to bot shutdown - Added error handling to timeout command - Removed unused Literal import - Added prefix validation - Added image analysis limit (3 per message) - Fixed test mock for SQLAlchemy model
154 lines
5.5 KiB
Python
154 lines
5.5 KiB
Python
"""Tests for the automod service."""
|
|
|
|
import pytest
|
|
|
|
from guardden.models import BannedWord
|
|
from guardden.services.automod import AutomodService
|
|
|
|
|
|
@pytest.fixture
|
|
def automod() -> AutomodService:
|
|
"""Create an automod service instance."""
|
|
return AutomodService()
|
|
|
|
|
|
class TestBannedWords:
|
|
"""Tests for banned word filtering."""
|
|
|
|
def test_simple_match(self, automod: AutomodService) -> None:
|
|
"""Test simple text matching."""
|
|
banned = [_make_banned_word("badword")]
|
|
result = automod.check_banned_words("This contains badword in it", banned)
|
|
assert result is not None
|
|
assert result.should_delete
|
|
|
|
def test_case_insensitive(self, automod: AutomodService) -> None:
|
|
"""Test case insensitive matching."""
|
|
banned = [_make_banned_word("BadWord")]
|
|
result = automod.check_banned_words("this contains BADWORD here", banned)
|
|
assert result is not None
|
|
|
|
def test_no_match(self, automod: AutomodService) -> None:
|
|
"""Test no match returns None."""
|
|
banned = [_make_banned_word("badword")]
|
|
result = automod.check_banned_words("This is a clean message", banned)
|
|
assert result is None
|
|
|
|
def test_regex_pattern(self, automod: AutomodService) -> None:
|
|
"""Test regex pattern matching."""
|
|
banned = [_make_banned_word(r"bad\w+", is_regex=True)]
|
|
result = automod.check_banned_words("This is badword and badstuff", banned)
|
|
assert result is not None
|
|
|
|
def test_action_warn(self, automod: AutomodService) -> None:
|
|
"""Test warn action is set."""
|
|
banned = [_make_banned_word("badword", action="warn")]
|
|
result = automod.check_banned_words("badword", banned)
|
|
assert result is not None
|
|
assert result.should_warn
|
|
|
|
def test_action_strike(self, automod: AutomodService) -> None:
|
|
"""Test strike action is set."""
|
|
banned = [_make_banned_word("badword", action="strike")]
|
|
result = automod.check_banned_words("badword", banned)
|
|
assert result is not None
|
|
assert result.should_strike
|
|
|
|
|
|
class TestScamDetection:
|
|
"""Tests for scam/phishing detection."""
|
|
|
|
def test_discord_nitro_scam(self, automod: AutomodService) -> None:
|
|
"""Test detection of fake Discord Nitro links."""
|
|
result = automod.check_scam_links("Free nitro at discord-nitro.gift")
|
|
assert result is not None
|
|
assert result.should_delete
|
|
|
|
def test_steam_scam(self, automod: AutomodService) -> None:
|
|
"""Test detection of Steam scam patterns."""
|
|
result = automod.check_scam_links("Check out this steam-community-giveaway.xyz")
|
|
assert result is not None
|
|
|
|
def test_legitimate_discord_link(self, automod: AutomodService) -> None:
|
|
"""Test that legitimate Discord links pass."""
|
|
result = automod.check_scam_links("Join us at discord.gg/example")
|
|
assert result is None
|
|
|
|
def test_suspicious_tld_with_keyword(self, automod: AutomodService) -> None:
|
|
"""Test suspicious TLD with impersonation keyword."""
|
|
result = automod.check_scam_links("Visit discord-verify.xyz to claim")
|
|
assert result is not None
|
|
|
|
def test_normal_url(self, automod: AutomodService) -> None:
|
|
"""Test normal URLs pass."""
|
|
result = automod.check_scam_links("Check out https://github.com/example")
|
|
assert result is None
|
|
|
|
|
|
class TestInviteLinks:
|
|
"""Tests for Discord invite link detection."""
|
|
|
|
def test_discord_gg_invite(self, automod: AutomodService) -> None:
|
|
"""Test discord.gg invite detection."""
|
|
result = automod.check_invite_links("Join discord.gg/example", allow_invites=False)
|
|
assert result is not None
|
|
assert result.should_delete
|
|
|
|
def test_discordapp_invite(self, automod: AutomodService) -> None:
|
|
"""Test discordapp.com invite detection."""
|
|
result = automod.check_invite_links(
|
|
"Join https://discordapp.com/invite/abc123", allow_invites=False
|
|
)
|
|
assert result is not None
|
|
|
|
def test_allowed_invites(self, automod: AutomodService) -> None:
|
|
"""Test invites pass when allowed."""
|
|
result = automod.check_invite_links("Join discord.gg/example", allow_invites=True)
|
|
assert result is None
|
|
|
|
|
|
class TestCapsDetection:
|
|
"""Tests for excessive caps detection."""
|
|
|
|
def test_excessive_caps(self, automod: AutomodService) -> None:
|
|
"""Test detection of all caps message."""
|
|
result = automod.check_all_caps("THIS IS ALL CAPS MESSAGE HERE")
|
|
assert result is not None
|
|
|
|
def test_normal_caps(self, automod: AutomodService) -> None:
|
|
"""Test normal message passes."""
|
|
result = automod.check_all_caps("This is a Normal Message with Some Caps")
|
|
assert result is None
|
|
|
|
def test_short_message_ignored(self, automod: AutomodService) -> None:
|
|
"""Test short messages are ignored."""
|
|
result = automod.check_all_caps("HI THERE")
|
|
assert result is None
|
|
|
|
|
|
class MockBannedWord:
|
|
"""Mock BannedWord for testing without database."""
|
|
|
|
def __init__(
|
|
self,
|
|
pattern: str,
|
|
is_regex: bool = False,
|
|
action: str = "delete",
|
|
) -> None:
|
|
self.id = 1
|
|
self.guild_id = 123
|
|
self.pattern = pattern
|
|
self.is_regex = is_regex
|
|
self.action = action
|
|
self.reason = None
|
|
self.added_by = 456
|
|
|
|
|
|
def _make_banned_word(
|
|
pattern: str,
|
|
is_regex: bool = False,
|
|
action: str = "delete",
|
|
) -> MockBannedWord:
|
|
"""Create a mock BannedWord object for testing."""
|
|
return MockBannedWord(pattern, is_regex, action)
|