diff --git a/tests/unit/rss/test_views.py b/tests/unit/rss/test_views.py index f4f5edb4221e..151900b63e18 100644 --- a/tests/unit/rss/test_views.py +++ b/tests/unit/rss/test_views.py @@ -13,8 +13,12 @@ import datetime import pretend +import pytest + +from pyramid.httpexceptions import HTTPBadRequest from warehouse.rss import views as rss +from warehouse.utils import now from ...common.db.packaging import ProjectFactory, ReleaseFactory @@ -44,6 +48,72 @@ def test_rss_updates(db_request): assert db_request.response.content_type == "text/xml" +def test_rss_updates_limit(db_request): + db_request.params = {"limit": 2} + + db_request.find_service = pretend.call_recorder( + lambda *args, **kwargs: pretend.stub( + enabled=False, csp_policy=pretend.stub(), merge=lambda _: None + ) + ) + + db_request.session = pretend.stub() + + project1 = ProjectFactory.create() + project2 = ProjectFactory.create() + + release1 = ReleaseFactory.create(project=project1) + release1.created = datetime.date(2011, 1, 1) + release2 = ReleaseFactory.create(project=project2) + release2.created = datetime.date(2012, 1, 1) + release3 = ReleaseFactory.create(project=project1) + release3.created = datetime.date(2013, 1, 1) + + assert rss.rss_updates(db_request) == {"latest_releases": [release3, release2]} + assert db_request.response.content_type == "text/xml" + + +def test_rss_updates_max_age(db_request): + db_request.params = {"max_age": 150} + + db_request.find_service = pretend.call_recorder( + lambda *args, **kwargs: pretend.stub( + enabled=False, csp_policy=pretend.stub(), merge=lambda _: None + ) + ) + + db_request.session = pretend.stub() + + project1 = ProjectFactory.create() + + release1 = ReleaseFactory.create(project=project1) + release1.created = now() - datetime.timedelta(seconds=100) + release2 = ReleaseFactory.create(project=project1) + release2.created = now() - datetime.timedelta(seconds=200) + + assert rss.rss_updates(db_request) == {"latest_releases": [release1]} + assert db_request.response.content_type == "text/xml" + + +def test_rss_updates_max_age_invalid(db_request): + db_request.params = {"max_age": "foo"} + + db_request.find_service = pretend.call_recorder( + lambda *args, **kwargs: pretend.stub( + enabled=False, csp_policy=pretend.stub(), merge=lambda _: None + ) + ) + + db_request.session = pretend.stub() + + with pytest.raises(HTTPBadRequest) as excinfo: + rss.rss_updates(db_request) + + resp = excinfo.value + + assert resp.status_code == 400 + + def test_rss_packages(db_request): db_request.find_service = pretend.call_recorder( lambda *args, **kwargs: pretend.stub( @@ -66,3 +136,49 @@ def test_rss_packages(db_request): assert rss.rss_packages(db_request) == {"newest_projects": [project3, project1]} assert db_request.response.content_type == "text/xml" + + +def test_rss_packages_limit(db_request): + db_request.params = {"limit": 1} + + db_request.find_service = pretend.call_recorder( + lambda *args, **kwargs: pretend.stub( + enabled=False, csp_policy=pretend.stub(), merge=lambda _: None + ) + ) + + db_request.session = pretend.stub() + + project1 = ProjectFactory.create() + project1.created = datetime.date(2011, 1, 1) + ReleaseFactory.create(project=project1) + + project2 = ProjectFactory.create() + project2.created = datetime.date(2012, 1, 1) + ReleaseFactory.create(project=project2) + + assert rss.rss_packages(db_request) == {"newest_projects": [project2]} + assert db_request.response.content_type == "text/xml" + + +def test_rss_packages_max_age(db_request): + db_request.params = {"max_age": 150} + + db_request.find_service = pretend.call_recorder( + lambda *args, **kwargs: pretend.stub( + enabled=False, csp_policy=pretend.stub(), merge=lambda _: None + ) + ) + + db_request.session = pretend.stub() + + project1 = ProjectFactory.create() + project1.created = now() - datetime.timedelta(seconds=100) + ReleaseFactory.create(project=project1) + + project2 = ProjectFactory.create() + project2.created = now() - datetime.timedelta(seconds=200) + ReleaseFactory.create(project=project2) + + assert rss.rss_packages(db_request) == {"newest_projects": [project1]} + assert db_request.response.content_type == "text/xml" diff --git a/warehouse/rss/views.py b/warehouse/rss/views.py index 46fd24fd2ce0..4778870532ec 100644 --- a/warehouse/rss/views.py +++ b/warehouse/rss/views.py @@ -10,13 +10,31 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import timedelta + +from pyramid.httpexceptions import HTTPBadRequest from pyramid.view import view_config from sqlalchemy.orm import joinedload from warehouse.cache.origin import origin_cache from warehouse.packaging.models import Project, Release +from warehouse.utils import now from warehouse.xml import XML_CSP +DEFAULT_RESULTS = 40 +MAX_RESULTS = 200 + + +def _get_int_query_param(request, param, default=None): + value = request.params.get(param) + if not value: + # Return default if 'param' is absent or has an empty value. + return default + try: + return int(value) + except ValueError: + raise HTTPBadRequest(f"'{param}' must be an integer.") from None + @view_config( route_name="rss.updates", @@ -39,11 +57,17 @@ def rss_updates(request): request.db.query(Release) .options(joinedload(Release.project)) .order_by(Release.created.desc()) - .limit(40) - .all() ) - return {"latest_releases": latest_releases} + max_age = _get_int_query_param(request, "max_age") + if max_age is not None: + created_since = now() - timedelta(seconds=max_age) + latest_releases = latest_releases.filter(Release.created > created_since) + + limit = min(_get_int_query_param(request, "limit", DEFAULT_RESULTS), MAX_RESULTS) + latest_releases = latest_releases.limit(limit) + + return {"latest_releases": latest_releases.all()} @view_config( @@ -67,8 +91,14 @@ def rss_packages(request): request.db.query(Project) .options(joinedload(Project.releases, innerjoin=True)) .order_by(Project.created.desc()) - .limit(40) - .all() ) - return {"newest_projects": newest_projects} + max_age = _get_int_query_param(request, "max_age") + if max_age is not None: + created_since = now() - timedelta(seconds=max_age) + newest_projects = newest_projects.filter(Project.created > created_since) + + limit = min(_get_int_query_param(request, "limit", DEFAULT_RESULTS), MAX_RESULTS) + newest_projects = newest_projects.limit(limit) + + return {"newest_projects": newest_projects.all()}