"""Tests for the RSS/Atom feed endpoints (api/routes/feeds.py).""" import xml.etree.ElementTree as ET from datetime import datetime, timedelta, timezone from unittest.mock import MagicMock import pytest from redis.exceptions import ConnectionError as RedisConnectionError from meshcore_hub.common.models import Advertisement, Channel, Message, Node ATOM_NS = "{http://www.w3.org/2005/Atom}" NOW = datetime(2026, 8, 30, 12, 0, 0, tzinfo=timezone.utc) def _channel_idx(channel: Channel) -> int: return int(channel.channel_hash, 16) def _text(element: ET.Element, path: str) -> str: """findtext with Optional narrowed for mypy.""" value = element.findtext(path) assert value is not None, f"missing text at {path}" return value def _attr(element: ET.Element, path: str, name: str) -> str: """Element attribute lookup with Optionals narrowed for mypy.""" child = element.find(path) assert child is not None, f"missing element at {path}" value = child.get(name) assert value is not None, f"missing attribute {name} at {path}" return value def _add_message( session, *, text="hello", message_type="channel", channel_idx=17, pubkey_prefix=None, received_at=NOW, packet_hash=None, spam_score=None, ): message = Message( message_type=message_type, channel_idx=channel_idx, pubkey_prefix=pubkey_prefix, text=text, received_at=received_at, packet_hash=packet_hash, spam_score=spam_score, ) session.add(message) session.commit() return message class TestFeedWellFormed: """RSS/Atom documents parse and carry the right roots/namespaces.""" @pytest.mark.parametrize( "path,root_tag", [ ("/api/v1/feeds/messages.xml", "rss"), ("/api/v1/feeds/adverts.xml", "rss"), ("/api/v1/feeds/nodes.xml", "rss"), ("/api/v1/feeds/channels/17.xml", "rss"), ], ) def test_rss_documents_parse(self, client_no_auth, path, root_tag): response = client_no_auth.get(path) assert response.status_code == 200 root = ET.fromstring(response.text) assert root.tag == root_tag assert root.get("version") == "2.0" assert root.find("channel/title") is not None assert root.find("channel/link") is not None @pytest.mark.parametrize( "path", [ "/api/v1/feeds/messages.atom", "/api/v1/feeds/adverts.atom", "/api/v1/feeds/nodes.atom", "/api/v1/feeds/channels/17.atom", ], ) def test_atom_documents_parse(self, client_no_auth, path): response = client_no_auth.get(path) assert response.status_code == 200 root = ET.fromstring(response.text) assert root.tag == f"{ATOM_NS}feed" assert root.find(f"{ATOM_NS}title") is not None assert root.find(f"{ATOM_NS}updated") is not None class TestMessagesFeedVisibility: def test_community_message_present(self, client_no_auth, api_db_session): _add_message(api_db_session, text="community hello", channel_idx=17) response = client_no_auth.get("/api/v1/feeds/messages.xml") assert response.status_code == 200 assert "community hello" in response.text def test_member_and_admin_channels_absent_even_for_admin_header( self, client_no_auth, api_db_session, sample_member_channel, sample_admin_channel, ): """Feeds are anonymous-pinned: a spoofed/real admin role header must never unlock member/admin channel content in a feed.""" _add_message( api_db_session, text="secret member", channel_idx=_channel_idx(sample_member_channel), ) _add_message( api_db_session, text="secret admin", channel_idx=_channel_idx(sample_admin_channel), ) response = client_no_auth.get( "/api/v1/feeds/messages.xml", headers={"X-User-Roles": "admin"} ) assert response.status_code == 200 assert "secret member" not in response.text assert "secret admin" not in response.text def test_non_channel_messages_included(self, client_no_auth, api_db_session): _add_message( api_db_session, text="direct note", message_type="direct", channel_idx=None, pubkey_prefix="abc123def456", ) response = client_no_auth.get("/api/v1/feeds/messages.xml") assert "direct note" in response.text def test_spam_excluded_when_detection_enabled(self, client_spam, api_db_session): _add_message(api_db_session, text="clean one", spam_score=0.1) _add_message(api_db_session, text="spammy one", spam_score=0.9) response = client_spam.get("/api/v1/feeds/messages.xml") assert response.status_code == 200 assert "clean one" in response.text assert "spammy one" not in response.text def test_spam_master_switch_off_shows_everything( self, client_no_auth, api_db_session ): """With detection disabled, stored scores are ignored entirely.""" _add_message(api_db_session, text="scored while on", spam_score=0.99) response = client_no_auth.get("/api/v1/feeds/messages.xml") assert "scored while on" in response.text class TestFeedEscaping: def test_script_and_ampersand_escaped(self, client_no_auth, api_db_session): _add_message( api_db_session, text=' & bold', channel_idx=17, ) response = client_no_auth.get("/api/v1/feeds/messages.xml") assert response.status_code == 200 root = ET.fromstring(response.text) descriptions = [ el.text or "" for el in root.findall("channel/item/description") ] assert any('' in d for d in descriptions) # Raw markup must never appear in the serialized document. assert "