1
0

test_bot_streaks.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """Tests des conversations avec d'autres bots : pas de boucle sans fin. Lancer depuis la racine du projet : python -m unittest"""
  2. import unittest
  3. from datetime import datetime, timedelta, timezone
  4. from unittest import mock
  5. import discord
  6. from bot.discord_bot import BOT_STREAK_RESET, MAX_BOT_REPLIES, BotStreaks, ChatBot
  7. HOME = 1000
  8. CHANNEL_ID = 10
  9. START = datetime(2026, 11, 3, 18, 0, tzinfo=timezone.utc)
  10. class BotStreaksTest(unittest.TestCase):
  11. def test_counts_per_channel(self):
  12. streaks = BotStreaks()
  13. self.assertEqual(streaks.count(1, START), 1)
  14. self.assertEqual(streaks.count(1, START), 2)
  15. self.assertEqual(streaks.count(2, START), 1)
  16. def test_human_resets(self):
  17. streaks = BotStreaks()
  18. streaks.count(1, START)
  19. streaks.human_spoke(1)
  20. self.assertEqual(streaks.count(1, START), 1)
  21. def test_quiet_channel_resets(self):
  22. streaks = BotStreaks()
  23. streaks.count(1, START)
  24. self.assertEqual(streaks.count(1, START + BOT_STREAK_RESET), 2)
  25. self.assertEqual(streaks.count(1, START + 2 * BOT_STREAK_RESET + timedelta(seconds=1)), 1)
  26. class OnMessageTest(unittest.IsolatedAsyncioTestCase):
  27. def setUp(self):
  28. self.bot = mock.Mock()
  29. self.bot.settings.channel_id = CHANNEL_ID
  30. self.bot.home_guild_id.return_value = HOME
  31. self.bot._bot_streaks = BotStreaks()
  32. self.bot.handle_main_channel = mock.AsyncMock()
  33. self.bot.register_members = mock.AsyncMock()
  34. async def receive(self, from_bot: bool, when: datetime = START) -> None:
  35. message = mock.Mock(guild=mock.Mock(id=HOME), webhook_id=None, type=discord.MessageType.reply,
  36. created_at=when)
  37. message.channel.id = CHANNEL_ID
  38. message.author.bot = from_bot
  39. message.mentions = [self.bot.user] # « Répondre » avec ping
  40. message.raw_mentions = [] # pas de <@id> dans le texte
  41. await ChatBot.on_message(self.bot, message)
  42. async def test_bot_loop_stopped(self):
  43. # Un bot qui répond à chacun de ses messages : le bot s'arrête après MAX_BOT_REPLIES réponses.
  44. with self.assertLogs("chatbot.discord", "WARNING") as logs:
  45. for _ in range(MAX_BOT_REPLIES + 3):
  46. await self.receive(from_bot=True)
  47. self.assertEqual(self.bot.handle_main_channel.await_count, MAX_BOT_REPLIES)
  48. self.assertEqual(len(logs.records), 1) # un seul avertissement par série
  49. async def test_member_message_restarts(self):
  50. with self.assertLogs("chatbot.discord", "WARNING"):
  51. for _ in range(MAX_BOT_REPLIES + 1):
  52. await self.receive(from_bot=True)
  53. await self.receive(from_bot=False)
  54. await self.receive(from_bot=True)
  55. self.assertEqual(self.bot.handle_main_channel.await_count, MAX_BOT_REPLIES + 2)
  56. async def test_quiet_channel_restarts(self):
  57. with self.assertLogs("chatbot.discord", "WARNING"):
  58. for _ in range(MAX_BOT_REPLIES + 1):
  59. await self.receive(from_bot=True)
  60. await self.receive(from_bot=True, when=START + BOT_STREAK_RESET + timedelta(seconds=1))
  61. self.assertEqual(self.bot.handle_main_channel.await_count, MAX_BOT_REPLIES + 1)
  62. class MentionLogTest(unittest.IsolatedAsyncioTestCase):
  63. """Journal de diagnostic : un bot qui écrit <@id> du bot, avec ou sans mention transmise par Discord."""
  64. def setUp(self):
  65. self.bot = mock.Mock()
  66. self.bot.settings.channel_id = CHANNEL_ID
  67. self.bot.home_guild_id.return_value = HOME
  68. self.bot._bot_streaks = BotStreaks()
  69. self.bot.handle_main_channel = mock.AsyncMock()
  70. self.bot.register_members = mock.AsyncMock()
  71. self.bot.replies_to_me = mock.AsyncMock(return_value=False)
  72. async def receive(self, listed: bool) -> None:
  73. message = mock.Mock(guild=mock.Mock(id=HOME), webhook_id=None, type=discord.MessageType.default,
  74. created_at=START)
  75. message.channel.id = CHANNEL_ID
  76. message.author.bot = True
  77. message.mentions = [self.bot.user] if listed else []
  78. message.raw_mentions = [self.bot.user.id]
  79. await ChatBot.on_message(self.bot, message)
  80. async def test_mention_missing_from_discord_list(self):
  81. with self.assertLogs("chatbot.discord", "INFO") as logs:
  82. await self.receive(listed=False)
  83. self.assertIn("mention absente de la liste de Discord, message ignoré", logs.output[0])
  84. self.bot.handle_main_channel.assert_not_awaited()
  85. async def test_mention_listed(self):
  86. with self.assertLogs("chatbot.discord", "INFO") as logs:
  87. await self.receive(listed=True)
  88. self.assertIn("mention transmise par Discord", logs.output[0])
  89. self.bot.handle_main_channel.assert_awaited_once()
  90. async def test_no_log_without_mention_in_text(self):
  91. # « Répondre » à un message du bot, sans <@id> dans le texte : rien de plus dans le journal.
  92. message = mock.Mock(guild=mock.Mock(id=HOME), webhook_id=None, type=discord.MessageType.reply,
  93. created_at=START, mentions=[], raw_mentions=[])
  94. message.channel.id = CHANNEL_ID
  95. message.author.bot = True
  96. self.bot.replies_to_me.return_value = True
  97. with self.assertNoLogs("chatbot.discord", "INFO"):
  98. await ChatBot.on_message(self.bot, message)
  99. self.bot.handle_main_channel.assert_awaited_once()
  100. if __name__ == "__main__":
  101. unittest.main()