feat: extend RSS item retention from 14 to 90 days
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONTENT_MAX_CHARS = 2000
|
CONTENT_MAX_CHARS = 2000
|
||||||
# Keep only items from the last N days to avoid unbounded growth
|
# Keep only items from the last N days to avoid unbounded growth
|
||||||
ITEM_MAX_AGE_DAYS = 14
|
ITEM_MAX_AGE_DAYS = 90
|
||||||
|
|
||||||
|
|
||||||
def extract_item(entry) -> dict:
|
def extract_item(entry) -> dict:
|
||||||
@@ -135,7 +135,7 @@ async def _prune_old_items(feed_id: int) -> None:
|
|||||||
text("""
|
text("""
|
||||||
DELETE FROM rss_items
|
DELETE FROM rss_items
|
||||||
WHERE feed_id = :feed_id
|
WHERE feed_id = :feed_id
|
||||||
AND published_at < NOW() - INTERVAL '14 days'
|
AND published_at < NOW() - INTERVAL '90 days'
|
||||||
""").bindparams(feed_id=feed_id)
|
""").bindparams(feed_id=feed_id)
|
||||||
)
|
)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""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_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
|
||||||
Reference in New Issue
Block a user