Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ dependencies = [

[dependency-groups]
dev = [
"httpx2>=2.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Regenerate the lockfile for the new test dependency

The checked workflow .github/workflows/docker.yml installs with uv sync --frozen before running pytest; uv sync --help defines --frozen as “Sync without updating the uv.lock file.” Because this commit does not add httpx2 to uv.lock, that install omits it, and the new from fastapi.testclient import TestClient fails during collection with Starlette's RuntimeError requiring httpx2. Update and commit uv.lock so the test job can run.

Useful? React with 👍 / 👎.

"pytest>=8.0.0",
]
35 changes: 35 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient
import requests

ROOT = Path(__file__).resolve().parents[1]
Expand Down Expand Up @@ -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")
Expand Down
Loading