"""The guarantee that a test run cannot reach a real subscriber. tests/conftest.py hard-blocks every outbound transport. That block is worth nothing unless something asserts it, because it fails in exactly one direction: if a future edit weakens it, every other test still passes and the only symptom is a real Discord message, a real email, or a real push to somebody's phone — delivered silently, because notify.py and discord.py swallow send exceptions. So this file tests the test harness. If anything here fails, do not weaken the assertion; fix the block in conftest.py. """ import smtplib import pytest import indexnow from notifications import discord, mailer, push from tests.conftest import OutboundBlocked # --- layer 1: every send gate reports "not configured" ------------------------ def test_no_discord_surface_is_enabled(): """A stray THERMOGRAPH_DISCORD_* in the operator's shell must not arm the bot. These four gates are what stand between run_pass() and a real post to the subscription channel, which notifies real people.""" assert discord.enabled() is False assert discord.dm_enabled() is False assert discord.subscription_enabled() is False assert discord.weather_enabled() is False def test_no_discord_credentials_are_live(): assert discord.BOT_TOKEN == "" assert discord.WEBHOOK_URL == "" assert discord.SUBSCRIPTION_CHANNEL_ID == "" assert discord.WEATHER_CHANNEL_ID == "" def test_mail_never_goes_over_smtp(): """`console` prints; `smtp` hands the message to a real host. digest.py branches on exactly this value.""" assert mailer.BACKEND == "console" assert mailer.enabled() is False # --- layer 2: the transports themselves raise -------------------------------- # # Layer 1 is config, and config can be re-enabled by a test that monkeypatches a # token to exercise a branch. These are the backstop for that case. def test_discord_transport_raises_rather_than_posting(): with pytest.raises(OutboundBlocked): discord._client.post("https://discord.com/api/v10/channels/1/messages", json={"content": "should never leave the box"}) def test_indexnow_transport_raises_rather_than_posting(): with pytest.raises(OutboundBlocked): indexnow._client.post("https://api.indexnow.org/indexnow", json={}) def test_push_transport_raises_rather_than_sending(): with pytest.raises(OutboundBlocked): push.webpush(subscription_info={}, data="{}") def test_smtp_raises_rather_than_connecting(): with pytest.raises(OutboundBlocked): smtplib.SMTP("127.0.0.1", 25) with pytest.raises(OutboundBlocked): smtplib.SMTP_SSL("127.0.0.1", 465) # --- the paths that used to be able to send silently ------------------------- def test_bot_post_cannot_reach_discord_even_with_a_token(monkeypatch): """_bot_post is the funnel for DMs and channel posts. Re-arm the token the way a careless test might and confirm the transport still refuses. Note _bot_post swallows exceptions by design, so it returns None rather than raising — the assertion that matters is that the sentinel ran at all, i.e. that no HTTP request was attempted against the real API. """ monkeypatch.setattr(discord, "BOT_TOKEN", "not-a-real-token") attempted = [] monkeypatch.setattr(discord._client, "post", lambda *a, **kw: attempted.append(a) or (_ for _ in ()).throw( OutboundBlocked("blocked"))) assert discord._bot_post("/channels/1/messages", {"content": "x"}) is None assert attempted, "_bot_post should have gone through _client.post" def test_run_pass_sends_nothing_when_nothing_is_configured(): """The regression this whole block exists for: notify.run_pass() reaches discord.post_subscription_alert() for every notification it creates, and test_notify.py calls it seven times without patching discord. With the gates closed it must complete without touching a transport.""" from accounts import db from notifications import notify # The app lifespan (which normally creates these) does not run under # TestClient-less tests, so build the schema the way test_notify.py does. db.Base.metadata.create_all(db.sync_engine) # No subscriptions in the throwaway DB => no candidates, no sends. The point # is that this completes without raising OutboundBlocked. notify.run_pass()