diff --git a/main.py b/main.py
index 111c7bc..ee6fee4 100755
--- a/main.py
+++ b/main.py
@@ -354,6 +354,7 @@ class CategoryResult:
articles: list[ArticleWithoutImage]
gcmcontinue: str | None
+ error: str | None = None
# Common non-content images to ignore when checking if an article has images
@@ -435,6 +436,7 @@ def get_articles_without_images(
articles_without_images: list[ArticleWithoutImage] = []
seen_pageids: set[int] = set()
next_gcmcontinue: str | None = None
+ error: str | None = None
# Build initial continue params from the external pagination token
continue_params: dict[str, str] = {}
@@ -451,8 +453,20 @@ def get_articles_without_images(
)
response.raise_for_status()
data = response.json()
+ if api_error := data.get("error"):
+ raise requests.RequestException(
+ f"{api_error.get('code', 'unknown')}: "
+ f"{api_error.get('info', 'unknown Wikipedia API error')}"
+ )
except (requests.RequestException, json.JSONDecodeError) as e:
print(f"Wikipedia API error: {e}")
+ if articles_without_images:
+ error = (
+ "Wikipedia stopped responding before the whole category could "
+ "be checked. Showing partial results; please try again."
+ )
+ else:
+ error = "Unable to load articles from Wikipedia. Please try again."
break
pages = data.get("query", {}).get("pages", {})
@@ -491,6 +505,7 @@ def get_articles_without_images(
return CategoryResult(
articles=articles_without_images,
gcmcontinue=next_gcmcontinue,
+ error=error,
)
@@ -891,6 +906,7 @@ def category_search() -> str:
category_name=category_name,
articles=result.articles,
gcmcontinue=result.gcmcontinue,
+ error=result.error,
)
diff --git a/templates/category.html b/templates/category.html
index 62fcb1a..2fe8df8 100644
--- a/templates/category.html
+++ b/templates/category.html
@@ -50,7 +50,7 @@
{% endif %}
- {% else %}
+ {% elif not error %}
All articles in this category have images!
diff --git a/tests/test_category_search.py b/tests/test_category_search.py
new file mode 100644
index 0000000..8a22e1f
--- /dev/null
+++ b/tests/test_category_search.py
@@ -0,0 +1,85 @@
+"""Tests for Wikipedia category searches."""
+
+from unittest.mock import patch
+
+import requests
+
+from main import CategoryResult, app, get_articles_without_images
+
+
+def test_api_failure_is_not_reported_as_an_empty_success():
+ with patch("main.requests.get", side_effect=requests.ConnectionError("offline")):
+ result = get_articles_without_images("Category:Example")
+
+ assert result.articles == []
+ assert result.error == "Unable to load articles from Wikipedia. Please try again."
+
+
+def test_api_error_response_is_not_reported_as_an_empty_success():
+ error_response = type(
+ "Response",
+ (),
+ {
+ "raise_for_status": lambda self: None,
+ "json": lambda self: {
+ "error": {"code": "readonly", "info": "Wikipedia is read-only"}
+ },
+ },
+ )()
+
+ with patch("main.requests.get", return_value=error_response):
+ result = get_articles_without_images("Category:Example")
+
+ assert result.articles == []
+ assert result.error == "Unable to load articles from Wikipedia. Please try again."
+
+
+def test_api_failure_after_results_marks_them_as_partial():
+ first_response = type(
+ "Response",
+ (),
+ {
+ "raise_for_status": lambda self: None,
+ "json": lambda self: {
+ "query": {
+ "pages": {
+ "1": {"pageid": 1, "title": "No image", "images": []}
+ }
+ },
+ "continue": {
+ "gcmcontinue": "next-page",
+ "continue": "gcmcontinue||",
+ },
+ },
+ },
+ )()
+
+ with patch(
+ "main.requests.get",
+ side_effect=[first_response, requests.ConnectionError("offline")],
+ ):
+ result = get_articles_without_images("Category:Example")
+
+ assert [article.title for article in result.articles] == ["No image"]
+ assert result.error is not None
+ assert "partial results" in result.error
+
+
+def test_category_page_does_not_claim_success_after_api_failure():
+ result = CategoryResult(
+ articles=[],
+ gcmcontinue=None,
+ error="Unable to load articles from Wikipedia. Please try again.",
+ )
+
+ with (
+ patch("main.get_articles_without_images", return_value=result),
+ patch("main.log_interaction"),
+ app.test_client() as client,
+ ):
+ response = client.get("/category?cat=Category:Example")
+
+ page = response.get_data(as_text=True)
+ assert response.status_code == 200
+ assert "Unable to load articles from Wikipedia" in page
+ assert "All articles in this category have images" not in page