diff --git a/README.md b/README.md index 3349281..d40b010 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The API provides interactive documentation at: - `GET /freshrss/unread` - Fetch unread RSS items from FreshRSS - Optional query parameters: - - `n` (integer, default `10`) - Number of unread items to return + - `n` (integer, default `10`, valid range `1`–`100`) - Number of unread items to return - `category` (string) - FreshRSS category label to scope unread items, e.g. `/freshrss/unread?category=Tech` ## Testing diff --git a/main.py b/main.py index 627c74a..af3d1d3 100644 --- a/main.py +++ b/main.py @@ -71,7 +71,7 @@ def health(): @app.get("/freshrss/unread") def freshrss_unread( - n: int = Query(default=10, ge=1), + n: int = Query(default=10, ge=1, le=100), category: str | None = Query(default=None), ): token = get_greader_token() diff --git a/pyproject.toml b/pyproject.toml index 1309bfc..9fa2bfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,5 +13,6 @@ dependencies = [ [dependency-groups] dev = [ + "httpx2>=2.0.0", "pytest>=8.0.0", ] diff --git a/tests/test_main.py b/tests/test_main.py index 8e624e9..6e9b6cf 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -4,6 +4,7 @@ import pytest from fastapi import HTTPException +from fastapi.testclient import TestClient import requests ROOT = Path(__file__).resolve().parents[1] @@ -163,6 +164,40 @@ def fake_get(url, headers, params, timeout): assert result[0]["display"].startswith("Release shipped • ") +@pytest.mark.parametrize("n", [0, 101]) +def test_freshrss_unread_rejects_out_of_range_n_without_contacting_freshrss(monkeypatch, n): + main = import_app(monkeypatch) + + def unexpected_request(*args, **kwargs): + pytest.fail("FreshRSS must not be contacted for an invalid n value") + + monkeypatch.setattr(main.requests, "post", unexpected_request) + monkeypatch.setattr(main.requests, "get", unexpected_request) + + response = TestClient(main.app).get("/freshrss/unread", params={"n": n}) + + assert response.status_code == 422 + + +@pytest.mark.parametrize("n", [1, 100]) +def test_freshrss_unread_accepts_boundary_n_values(monkeypatch, n): + main = import_app(monkeypatch) + monkeypatch.setattr(main, "get_greader_token", lambda: "token-123") + captured = {} + + def fake_get(url, headers, params, timeout): + captured["n"] = params["n"] + return FakeResponse(payload={"items": []}) + + monkeypatch.setattr(main.requests, "get", fake_get) + + response = TestClient(main.app).get("/freshrss/unread", params={"n": n}) + + assert response.status_code == 200 + assert response.json() == [] + assert captured["n"] == n + + def test_freshrss_unread_scopes_to_category_and_handles_missing_url(monkeypatch): main = import_app(monkeypatch) monkeypatch.setattr(main, "get_greader_token", lambda: "token-123")