"""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 "