"""What the antispam hook refuses to touch (SPEC §C.4). The gating matters more than the scoring: `message.new` fires for imported history and for every folder, so a hook that acts on all of it would file years-old mail into Junk and stall backfill with inline HTTP. """ from __future__ import annotations import uuid from typing import Any import pytest from webmail_plugin_antispam import hook, policy LIVE_INBOX = { "message_id": "m", "account_id": "acc-1", "mailbox_special_use": "inbox", "account_state": "live", "from_email": "someone@example.test", "subject": "hello", "to": [], "flags": [], } class _FakeMessages: def __init__(self, env: dict[str, Any] | None) -> None: self._env = env self.raw_calls = 0 async def envelope(self, *_a: object, **_k: object) -> dict[str, Any] | None: return self._env async def raw(self, *_a: object, **_k: object) -> bytes | None: self.raw_calls += 1 return b"From: someone@example.test\r\n\r\nbody\r\n" class _FakeActions: def __init__(self) -> None: self.moves: list[str] = [] async def move_to_special(self, _u: object, _m: object, special_use: str) -> bool: self.moves.append(special_use) return True class _FakeSdk: def __init__(self, env: dict[str, Any] | None) -> None: self.messages = _FakeMessages(env) self.actions = _FakeActions() self.published: list[tuple[str, dict[str, Any]]] = [] async def publish(self, event: str, payload: dict[str, Any]) -> None: self.published.append((event, payload)) @pytest.fixture def wired(monkeypatch: pytest.MonkeyPatch): # type: ignore[no-untyped-def] """Swap the hook's SDK and its database/engine calls for fakes.""" recorded: list[dict[str, Any]] = [] async def _record(_u: object, _m: object, **kw: Any) -> None: recorded.append(kw) async def _settings(_u: object) -> policy.Settings: return policy.Settings( enabled_accounts=[], move_threshold=12.0, banner_threshold=6.0, network_checks=False, learn_from_provider_junk=True, ) async def _rules(_u: object) -> dict[str, set[str]]: return {"allow": set(), "deny": set()} monkeypatch.setattr(hook, "_record", _record) monkeypatch.setattr(policy, "load_settings", _settings) monkeypatch.setattr(policy, "load_rules", _rules) def _install(env: dict[str, Any] | None, score: float | None = None) -> _FakeSdk: fake = _FakeSdk(env) monkeypatch.setattr(hook, "_SDK", fake) async def _check(*_a: object, **_k: object) -> object: if score is None: return None return type("V", (), {"score": score, "symbols": ["X"]})() monkeypatch.setattr(hook.rspamd, "check", _check) return fake return _install, recorded async def _fire() -> None: await hook.on_message_new({"user_id": str(uuid.uuid4()), "message_id": str(uuid.uuid4())}) async def test_skips_while_the_account_is_backfilling(wired) -> None: # type: ignore[no-untyped-def] """Imported history must not be scored, moved, or slowed down.""" install, recorded = wired fake = install({**LIVE_INBOX, "account_state": "backfilling"}, score=99.0) await _fire() assert fake.messages.raw_calls == 0 # never even fetched the body assert fake.actions.moves == [] assert recorded == [] @pytest.mark.parametrize("special_use", ["sent", "junk", "trash", "drafts", None]) async def test_skips_everything_outside_the_inbox(wired, special_use) -> None: # type: ignore[no-untyped-def] install, recorded = wired fake = install({**LIVE_INBOX, "mailbox_special_use": special_use}, score=99.0) await _fire() assert fake.actions.moves == [] assert recorded == [] async def test_classifies_a_live_inbox_arrival(wired) -> None: # type: ignore[no-untyped-def] install, recorded = wired fake = install(LIVE_INBOX, score=99.0) await _fire() assert fake.actions.moves == ["junk"] assert recorded and recorded[0]["action"] == "moved" assert fake.published and fake.published[0][0] == "plugin.antispam.classified" async def test_unreachable_engine_delivers_normally(wired) -> None: # type: ignore[no-untyped-def] """Acceptance 2: no engine, no interference.""" install, recorded = wired fake = install(LIVE_INBOX, score=None) await _fire() assert fake.actions.moves == [] assert recorded and recorded[0]["action"] == "none"