"""Tests for the inbound operator inbox (harness/operator_inbox.py). No network, no database: the Telegram fetch/send and the storage layer are injected with fakes, mirroring the injection style of test_notify.py / test_vision.py. """ from __future__ import annotations import pytest from harness import operator_inbox as inbox class FakeStore: """In-memory stand-in for MongoStore with the same tiny method surface.""" def __init__(self) -> None: self.offset = 0 self.suggestions: dict[int, dict] = {} self.indexed = False def ensure_indexes(self) -> None: self.indexed = True def read_offset(self) -> int: return self.offset def write_offset(self, next_offset: int) -> None: self.offset = int(next_offset) def upsert_suggestion(self, doc: dict) -> None: self.suggestions.setdefault(doc["update_id"], dict(doc)) # $setOnInsert semantics def list_unconsumed(self, limit: int) -> list[dict]: items = [d for d in self.suggestions.values() if not d.get("consumed")] items.sort(key=lambda d: d.get("ts", "")) return [dict(d) for d in items[:limit]] def mark_consumed(self, update_ids, plan_decision: str) -> None: for uid in update_ids: if uid in self.suggestions: self.suggestions[uid]["consumed"] = True self.suggestions[uid]["plan_decision"] = plan_decision def _update(update_id: int, text: str, chat_id: str = "555", date: int = 1_700_000_000) -> dict: return { "update_id": update_id, "message": {"message_id": update_id, "date": date, "text": text, "chat": {"id": int(chat_id)}}, } @pytest.fixture def configured(monkeypatch): """Satisfy the env guards so the storage path runs (values are dummies — store is injected).""" monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "dummy-token") monkeypatch.setenv("ADMIN_TELEGRAM_CHAT_ID", "555") monkeypatch.setenv("MONGO_URL", "mongodb://injected") monkeypatch.delenv("OPERATOR_INBOX", raising=False) monkeypatch.delenv("TELEGRAM_PROXY_DOMAIN", raising=False) def test_poll_filters_to_admin_and_text(configured): store = FakeStore() updates = [ _update(10, "make the hook punchier"), _update(11, "spam from a stranger", chat_id="999"), # wrong chat → dropped {"update_id": 12, "message": {"date": 1, "chat": {"id": 555}}}, # no text → dropped _update(13, "try a top-5 list format"), ] stored = inbox.poll_into_mongo(store=store, fetch_updates=lambda offset: updates) assert stored == 2 texts = {d["text"] for d in store.suggestions.values()} assert texts == {"make the hook punchier", "try a top-5 list format"} # cursor advances past the highest update_id seen (even the dropped ones) assert store.offset == 14 def test_poll_passes_stored_offset_to_fetch(configured): store = FakeStore() store.offset = 42 seen = {} def fetch(offset): seen["offset"] = offset return [] inbox.poll_into_mongo(store=store, fetch_updates=fetch) assert seen["offset"] == 42 def test_unconsumed_then_mark_consumed_roundtrip(configured): store = FakeStore() inbox.poll_into_mongo( store=store, fetch_updates=lambda offset: [_update(1, "first"), _update(2, "second")], ) items = inbox.unconsumed_suggestions(store=store) assert [i["text"] for i in items] == ["first", "second"] inbox.mark_consumed([i["update_id"] for i in items], plan_decision="SMALL_TWEAK", store=store) assert inbox.unconsumed_suggestions(store=store) == [] assert all(d["plan_decision"] == "SMALL_TWEAK" for d in store.suggestions.values()) def test_poll_is_noop_without_config(monkeypatch): monkeypatch.delenv("TELEGRAM_BOT_TOKEN", raising=False) monkeypatch.setenv("ADMIN_TELEGRAM_CHAT_ID", "555") monkeypatch.setenv("MONGO_URL", "mongodb://injected") called = {"n": 0} def fetch(offset): called["n"] += 1 return [_update(1, "ignored")] store = FakeStore() assert inbox.poll_into_mongo(store=store, fetch_updates=fetch) == 0 assert called["n"] == 0 # never even reached Telegram assert store.suggestions == {} def test_kill_switch_disables(monkeypatch): monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "t") monkeypatch.setenv("ADMIN_TELEGRAM_CHAT_ID", "555") monkeypatch.setenv("MONGO_URL", "mongodb://injected") monkeypatch.setenv("OPERATOR_INBOX", "false") store = FakeStore() assert inbox.poll_into_mongo(store=store, fetch_updates=lambda o: [_update(1, "x")]) == 0 assert inbox.unconsumed_suggestions(store=store) == [] def test_render_markdown_empty_and_populated(): empty = inbox.render_markdown([]) assert "No operator messages" in empty block = inbox.render_markdown([{"ts": "2026-06-30T00:00:00+00:00", "text": "punchier hook"}]) assert "punchier hook" in block assert "OPERATOR SUGGESTIONS" in block # the trust banner is always present def test_render_markdown_is_bounded(): items = [{"ts": "t", "text": "x" * 1000} for _ in range(50)] block = inbox.render_markdown(items) assert len(block) <= inbox._RENDER_CHAR_CAP + 200 # cap + banner/closing line slack assert "omitted to fit context" in block def test_ack_swallows_send_errors(configured): def boom(chat_id, text): raise RuntimeError("telegram down") inbox.ack("hello", send=boom) # must not raise def test_ack_sends_to_admin(configured): sent = [] inbox.ack("done", send=lambda chat_id, text: sent.append((chat_id, text))) assert sent == [("555", "done")]