beh-11: addressed-only channels — silent unless mentioned, replied to, or named; scheduled tasks skip them

This commit is contained in:
Oleksandr Kozachuk
2026-07-23 15:26:06 +02:00
parent f3c25de310
commit 1cdd240d98
3 changed files with 112 additions and 2 deletions
+75 -2
View File
@@ -4,12 +4,12 @@ import hashlib
import tempfile
import unittest
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from unittest.mock import AsyncMock, MagicMock, Mock, PropertyMock, patch
from discord import DMChannel, TextChannel
from fjerkroa_bot.ai_responder import AIMessage, AIResponse
from fjerkroa_bot.discord_bot import quiet_hours_active, split_answer
from fjerkroa_bot.discord_bot import FjerkroaBot, quiet_hours_active, split_answer
from fjerkroa_bot.openai_responder import OpenAIResponder
from fjerkroa_bot.persistence import PersistentStore
@@ -290,3 +290,76 @@ class TestPinsListing(OpsBase):
self.assertIn("Kanalregel", listing)
self.assertIn("1", listing)
self.assertIn("2", listing)
class TestAddressedOnlyChannels(ClassifierGateBase):
FAMILY = "🐾𝕱𝖆𝖒𝖎𝖑𝖎𝖊"
def family_msg(self, content):
message = self.public_msg(content)
message.channel.name = self.FAMILY
message.add_reaction = AsyncMock()
return message
def family_setup(self):
self.gate_setup({"reply": True, "factual": False, "emoji": None})
self.bot.config["addressed-only-channels"] = ["*𝕱𝖆𝖒𝖎𝖑𝖎𝖊*"]
def _user(self, name="Luma"):
user = MagicMock()
user.name = name
return user
async def test_unaddressed_message_stays_silent(self):
"""BEH-11: pattern hit + not addressed -> no classifier, no reaction, no reply."""
self.family_setup()
message = self.family_msg("wie war euer tag so?")
await self.bot.on_message(message)
self.bot.airesponder.classify.assert_not_awaited()
message.add_reaction.assert_not_awaited()
self.bot.respond.assert_not_awaited()
async def test_mention_is_answered(self):
"""BEH-11: an @mention in an addressed-only channel is answered."""
self.family_setup()
user = self._user()
message = self.family_msg("was meinst du dazu?")
message.mentions = [user]
with patch.object(FjerkroaBot, "user", new_callable=PropertyMock) as mock_user:
mock_user.return_value = user
await self.bot.on_message(message)
self.bot.respond.assert_awaited_once()
async def test_name_in_text_is_answered(self):
"""BEH-11: the bot's name in the text counts as addressed (case-insensitive)."""
self.family_setup()
message = self.family_msg("luma, was haeltst du davon?")
with patch.object(FjerkroaBot, "user", new_callable=PropertyMock) as mock_user:
mock_user.return_value = self._user("Luma")
await self.bot.on_message(message)
self.bot.respond.assert_awaited_once()
async def test_reply_to_bot_is_answered(self):
"""BEH-11: a Discord reply to one of the bot's messages counts as addressed."""
self.family_setup()
user = self._user()
message = self.family_msg("ja genau so!")
message.reference.resolved.author = user
message.reference.resolved.content = "earlier bot text"
with patch.object(FjerkroaBot, "user", new_callable=PropertyMock) as mock_user:
mock_user.return_value = user
await self.bot.on_message(message)
self.bot.respond.assert_awaited_once()
async def test_other_channels_unaffected(self):
"""BEH-11: non-matching channels keep the normal classifier path."""
self.family_setup()
await self.bot.on_message(self.public_msg("hallo zusammen"))
self.bot.respond.assert_awaited_once()
async def test_tasks_skip_addressed_only_channels(self):
"""BEH-11: scheduled tasks never post into addressed-only channels."""
self.family_setup()
self.bot.channel_by_name = Mock(return_value=MagicMock(spec=TextChannel))
await self.bot._execute_task(self.FAMILY, "share a thought")
self.bot.respond.assert_not_awaited()