From c1959f37a06e12781da0a6951c3805c312c5412d Mon Sep 17 00:00:00 2001 From: Leon Date: Sun, 5 Apr 2026 16:58:27 +0200 Subject: [PATCH 1/2] feat: Limit master feed size to N recent items. Fixes #24 --- backend/app/core/config.py | 6 +++ backend/app/services/feed_generator.py | 2 +- .../app/tests/services/test_feed_generator.py | 48 +++++++++++++++++++ backend/pyproject.toml | 3 ++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 backend/app/tests/services/test_feed_generator.py diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 6862aac..18d5afa 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -40,6 +40,12 @@ class Settings(BaseSettings): ) algorithm: str = "HS256" access_token_expire_minutes: int = 30 + master_feed_limit: int = Field( + 100, + validation_alias=AliasChoices( + "MASTER_FEED_LIMIT", "LETTERFEED_MASTER_FEED_LIMIT" + ), + ) settings = Settings() diff --git a/backend/app/services/feed_generator.py b/backend/app/services/feed_generator.py index de036a9..f1ffa2a 100644 --- a/backend/app/services/feed_generator.py +++ b/backend/app/services/feed_generator.py @@ -77,7 +77,7 @@ def generate_feed(db: Session, feed_identifier: str): def generate_master_feed(db: Session): """Generate a master Atom feed for all newsletters.""" - entries = get_all_entries(db) + entries = get_all_entries(db, limit=settings.master_feed_limit) feed_url = f"{settings.app_base_url}/feeds/all" diff --git a/backend/app/tests/services/test_feed_generator.py b/backend/app/tests/services/test_feed_generator.py new file mode 100644 index 0000000..ae1feba --- /dev/null +++ b/backend/app/tests/services/test_feed_generator.py @@ -0,0 +1,48 @@ +from unittest.mock import MagicMock, patch + +import pytest +from app.services.feed_generator import generate_master_feed +from feedgen.feed import FeedGenerator + + +@pytest.fixture +def mock_db_session(): + """Fixture for a mock database session.""" + return MagicMock() + + +@patch("app.services.feed_generator.settings") +def test_generate_master_feed_respects_limit(mock_settings, mock_db_session): + """Test that the master feed generation respects the MASTER_FEED_LIMIT.""" + # Arrange + limit = 5 + mock_settings.master_feed_limit = limit + + # Create more mock entries than the limit + mock_entries = [MagicMock() for _ in range(limit + 5)] + + with ( + patch( + "app.services.feed_generator.get_all_entries", return_value=mock_entries + ) as mock_get_all_entries, + patch( + "app.services.feed_generator._create_feed_generator" + ) as mock_create_feed_generator, + patch( + "app.services.feed_generator._add_entries_to_feed" + ) as mock_add_entries_to_feed, + ): + mock_fg = MagicMock(spec=FeedGenerator) + mock_create_feed_generator.return_value = mock_fg + mock_fg.atom_str.return_value = "fake_atom_string" + + # Act + result = generate_master_feed(mock_db_session) + + # Assert + mock_get_all_entries.assert_called_once_with(mock_db_session, limit=limit) + mock_create_feed_generator.assert_called_once() + mock_add_entries_to_feed.assert_called_once_with( + mock_fg, mock_entries, is_master_feed=True + ) + assert result == "fake_atom_string" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index d2d4762..5bbf51f 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -50,6 +50,9 @@ lint.ignore = [ [tool.ruff.lint.pydocstyle] convention = "google" +[tool.ruff.lint.isort] +known-first-party = ["app"] + [tool.mypy] python_executable=".venv/bin/python" From 1884ec1f459cacc97f3d54c8c9565e433dc944d8 Mon Sep 17 00:00:00 2001 From: Leon Date: Sun, 5 Apr 2026 17:25:17 +0200 Subject: [PATCH 2/2] chore: reformat --- backend/app/tests/services/test_feed_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/app/tests/services/test_feed_generator.py b/backend/app/tests/services/test_feed_generator.py index ae1feba..f5f9abc 100644 --- a/backend/app/tests/services/test_feed_generator.py +++ b/backend/app/tests/services/test_feed_generator.py @@ -1,9 +1,10 @@ from unittest.mock import MagicMock, patch import pytest -from app.services.feed_generator import generate_master_feed from feedgen.feed import FeedGenerator +from app.services.feed_generator import generate_master_feed + @pytest.fixture def mock_db_session():