"""Tests for news API — retention constant and endpoint formatting.""" import pytest from unittest.mock import AsyncMock, MagicMock from datetime import datetime, timezone def test_rss_item_max_age_is_90_days(): """Retention window should be 90 days.""" from fabledassistant.services.rss import ITEM_MAX_AGE_DAYS assert ITEM_MAX_AGE_DAYS == 90 def _make_mock_session(rows): """Return a mock async context manager whose session.execute returns rows.""" mock_result = MagicMock() mock_result.mappings.return_value.all.return_value = rows mock_session = AsyncMock() mock_session.execute = AsyncMock(return_value=mock_result) mock_cm = MagicMock() mock_cm.__aenter__ = AsyncMock(return_value=mock_session) mock_cm.__aexit__ = AsyncMock(return_value=False) return mock_cm def test_list_news_item_serialisation(): """News item serialisation should truncate content to 300 chars and include reaction.""" pub = datetime(2026, 3, 28, 8, 0, tzinfo=timezone.utc) item = { "id": 1, "title": "EU AI Act deadline", "url": "https://example.com/ai", "content": "x" * 500, "published_at": pub, "topics": ["tech"], "feed_title": "TechCrunch", "reaction": "up", } result = { "id": item["id"], "title": item["title"], "url": item["url"], "snippet": (item["content"] or "")[:300], "published_at": item["published_at"].isoformat() if item["published_at"] else None, "topics": item["topics"] or [], "source": item["feed_title"], "reaction": item["reaction"], } assert result["snippet"] == "x" * 300 assert result["source"] == "TechCrunch" assert result["reaction"] == "up" assert result["published_at"] == pub.isoformat() def test_build_briefing_article_context_metadata_extraction(): """Verify the rss_item_ids extraction logic from message metadata.""" import json # Simulates the logic in _build_briefing_article_context def extract_ids(msg_metadata): meta = msg_metadata or {} if isinstance(meta, str): try: meta = json.loads(meta) except Exception: return [] return meta.get("rss_item_ids") or [] assert extract_ids({}) == [] assert extract_ids(None) == [] assert extract_ids({"rss_item_ids": [1, 2, 3]}) == [1, 2, 3] assert extract_ids(json.dumps({"rss_item_ids": [4, 5]})) == [4, 5] assert extract_ids("not json") == [] def test_list_news_null_content_serialisation(): """Null content should produce empty snippet without error.""" item = { "id": 2, "title": "No content article", "url": "https://example.com/b", "content": None, "published_at": None, "topics": None, "feed_title": "Hacker News", "reaction": None, } result = { "id": item["id"], "title": item["title"], "url": item["url"], "snippet": (item["content"] or "")[:300], "published_at": item["published_at"].isoformat() if item["published_at"] else None, "topics": item["topics"] or [], "source": item["feed_title"], "reaction": item["reaction"], } assert result["snippet"] == "" assert result["topics"] == [] assert result["published_at"] is None assert result["reaction"] is None