"""Discord gateway bot: the message-handling logic that decides when to reply and with what. No live gateway — we feed MESSAGE_CREATE payloads straight to the pure handler and stub the shared /grade lookup so these stay data-independent.""" import pytest from notifications import discord from notifications import discord_bot as bot from notifications import discord_interactions as di BOT_ID = "999" @pytest.fixture(autouse=True) def _stub_grade(monkeypatch): """Reply payload for /grade, mirroring the real shape (embeds + an ephemeral flag the gateway path must drop).""" monkeypatch.setattr( di, "_grade_message", lambda query: {"embeds": [{"title": f"grade:{query}"}], "flags": 64}) def _msg(content="", *, author_id="1", bot_author=False, mentions=(), guild=True): m = { "content": content, "id": "42", "channel_id": "77", "author": {"id": author_id, "bot": bot_author}, "mentions": [{"id": mid} for mid in mentions], } if guild: m["guild_id"] = "5" return m # --- when the bot stays silent ---------------------------------------------- def test_ignores_other_bots(): assert bot._response_for_message( _msg("<@999> Phoenix", author_id="8", bot_author=True, mentions=[BOT_ID]), BOT_ID) is None def test_ignores_its_own_messages(): assert bot._response_for_message( _msg("hello", author_id=BOT_ID, mentions=[BOT_ID]), BOT_ID) is None def test_ignores_guild_message_without_a_mention(): assert bot._response_for_message(_msg("just chatting", mentions=[]), BOT_ID) is None # --- when the bot replies ---------------------------------------------------- def test_mention_with_city_returns_grade_without_ephemeral_flag(): out = bot._response_for_message(_msg("<@999> Phoenix", mentions=[BOT_ID]), BOT_ID) assert out == {"embeds": [{"title": "grade:Phoenix"}]} # flags dropped assert "flags" not in out def test_legacy_nickname_mention_is_stripped(): out = bot._response_for_message(_msg("<@!999> Tokyo", mentions=[BOT_ID]), BOT_ID) assert out["embeds"][0]["title"] == "grade:Tokyo" def test_dm_needs_no_mention(): out = bot._response_for_message(_msg("Berlin", guild=False, mentions=[]), BOT_ID) assert out["embeds"][0]["title"] == "grade:Berlin" def test_mention_with_no_query_gives_help(): out = bot._response_for_message(_msg("<@999>", mentions=[BOT_ID]), BOT_ID) assert "content" in out and "mention me" in out["content"].lower() # --- helpers ----------------------------------------------------------------- def test_strip_mentions_handles_both_forms(): assert bot._strip_mentions("<@999> <@!999> Sao Paulo", BOT_ID) == "Sao Paulo" def test_is_dm_is_true_without_guild_id(): assert bot._is_dm(_msg("x", guild=False)) is True assert bot._is_dm(_msg("x", guild=True)) is False # --- enabled() gating -------------------------------------------------------- def test_enabled_is_off_by_default(monkeypatch): monkeypatch.delenv("THERMOGRAPH_DISCORD_BOT", raising=False) monkeypatch.setattr(discord, "BOT_TOKEN", "a-token") assert bot.enabled() is False def test_enabled_needs_both_flag_and_token(monkeypatch): monkeypatch.setenv("THERMOGRAPH_DISCORD_BOT", "1") monkeypatch.setattr(discord, "BOT_TOKEN", "") assert bot.enabled() is False monkeypatch.setattr(discord, "BOT_TOKEN", "a-token") assert bot.enabled() is True