Implement GuardDen Discord moderation bot
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
This commit is contained in:
153
tests/test_automod.py
Normal file
153
tests/test_automod.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user