diff --git a/app/scouts/connectors/gmail.py b/app/scouts/connectors/gmail.py index ee3bf96..8dd7c65 100644 --- a/app/scouts/connectors/gmail.py +++ b/app/scouts/connectors/gmail.py @@ -211,3 +211,29 @@ class GmailConnector: async def renew_watch(self, scout) -> None: """Renew an existing Gmail Pub/Sub watch (same as setup_watch).""" await self.setup_watch(scout) + + async def list_labels(self, scout) -> list[dict]: + """Return the account's Gmail labels as [{id, name}]. Empty if no token.""" + if not scout.oauth_token_encrypted: + return [] + + def _sync() -> list[dict]: + service = _get_gmail_service(scout) + resp = service.users().labels().list(userId="me").execute() + return [{"id": lbl["id"], "name": lbl["name"]} for lbl in resp.get("labels", [])] + + return await asyncio.to_thread(_sync) + + async def stop_watch(self, scout) -> None: + """Stop Gmail push notifications. Swallows errors (watch may be gone).""" + if not scout.oauth_token_encrypted: + return + + def _sync() -> None: + service = _get_gmail_service(scout) + service.users().stop(userId="me").execute() + + try: + await asyncio.to_thread(_sync) + except Exception: + logger.exception("stop_watch failed for scout %s", scout.id) diff --git a/tests/test_scout_connectors_gmail.py b/tests/test_scout_connectors_gmail.py index f54edd6..3db32a4 100644 --- a/tests/test_scout_connectors_gmail.py +++ b/tests/test_scout_connectors_gmail.py @@ -82,3 +82,27 @@ async def test_archive_calls_trash(): with patch("app.scouts.connectors.gmail._get_gmail_service") as mock_svc: await conn.archive(scout, ItemRef(source_msg_ref="msg-1")) mock_svc.return_value.users().messages().trash.assert_called() + + +@pytest.mark.asyncio +async def test_list_labels_returns_id_and_name(): + scout = _make_scout() + conn = GmailConnector() + fake = {"labels": [ + {"id": "INBOX", "name": "INBOX", "type": "system"}, + {"id": "Label_1", "name": "Work", "type": "user"}, + ]} + with patch("app.scouts.connectors.gmail._get_gmail_service") as mock_svc: + mock_svc.return_value.users().labels().list().execute.return_value = fake + labels = await conn.list_labels(scout) + assert {"id": "INBOX", "name": "INBOX"} in labels + assert {"id": "Label_1", "name": "Work"} in labels + + +@pytest.mark.asyncio +async def test_stop_watch_calls_stop(): + scout = _make_scout() + conn = GmailConnector() + with patch("app.scouts.connectors.gmail._get_gmail_service") as mock_svc: + await conn.stop_watch(scout) + mock_svc.return_value.users().stop.assert_called()