Validate watchlist add JSON body shape

This commit is contained in:
Dymas
2026-05-16 11:25:53 +02:00
parent 8a2184f6f3
commit 839154aa5c
6 changed files with 17 additions and 4 deletions
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
## 0.29.4 - 2026-05-16
- Fixed `/api/watchlist/add` malformed-body handling so non-object JSON payloads now return `400 Bad Request` instead of surfacing as an internal server error.
- Expanded regression coverage for invalid watchlist add payload shapes.
## 0.29.3 - 2026-05-16 ## 0.29.3 - 2026-05-16
- Fixed duplicate watchlist add responses so already-tracked shows now return `200 OK` with `created: false` instead of the misleading `201 Created`. - Fixed duplicate watchlist add responses so already-tracked shows now return `200 OK` with `created: false` instead of the misleading `201 Created`.
+2 -1
View File
@@ -2,7 +2,7 @@
A local web UI for a system-wide `ani-cli` install. A local web UI for a system-wide `ani-cli` install.
Current version: `0.29.3` Current version: `0.29.4`
## Project Layout ## Project Layout
@@ -63,6 +63,7 @@ The app currently includes:
- A paged SQLite-backed download queue with retry, cancel, remove, retry-failed, and clear-finished actions. - A paged SQLite-backed download queue with retry, cancel, remove, retry-failed, and clear-finished actions.
- A paged SQLite-backed watchlist with summary cards, per-show refresh, bulk refresh, source links, and thumbnail tools. - A paged SQLite-backed watchlist with summary cards, per-show refresh, bulk refresh, source links, and thumbnail tools.
- Duplicate watchlist adds now return `200 OK` with `created: false`, keeping the API status aligned with the no-op result. - Duplicate watchlist adds now return `200 OK` with `created: false`, keeping the API status aligned with the no-op result.
- Malformed watchlist add requests now also return `400 Bad Request` when the JSON body is not an object.
- Watchlist category tabs for `Watching`, `Planned`, `Finished`, and `Dropped`, with per-anime category editing. - Watchlist category tabs for `Watching`, `Planned`, `Finished`, and `Dropped`, with per-anime category editing.
- AnimeSchedule-backed watchlist completion tracking that compares live sub/dub availability with the expected total episode count when known. - AnimeSchedule-backed watchlist completion tracking that compares live sub/dub availability with the expected total episode count when known.
- Automatic watchlist sync after successful downloads, including downloaded-vs-manual finished markers. - Automatic watchlist sync after successful downloads, including downloaded-vs-manual finished markers.
+1 -1
View File
@@ -1 +1 @@
0.29.3 0.29.4
+1 -1
View File
@@ -16,7 +16,7 @@ from uuid import uuid4
PROJECT_ROOT = Path(__file__).resolve().parent PROJECT_ROOT = Path(__file__).resolve().parent
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
APP_NAME = "ani-cli-web" APP_NAME = "ani-cli-web"
VERSION = "0.29.3" VERSION = "0.29.4"
ALLANIME_BASE = "allanime.day" ALLANIME_BASE = "allanime.day"
ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_API = f"https://api.{ALLANIME_BASE}"
ALLANIME_REFERER = "https://allmanga.to" ALLANIME_REFERER = "https://allmanga.to"
+1 -1
View File
@@ -242,7 +242,7 @@ class Handler(BaseHTTPRequestHandler):
self.json(actions[action](job_id)) self.json(actions[action](job_id))
elif parsed.path in {"/api/watchlist", "/api/watchlist/add"}: elif parsed.path in {"/api/watchlist", "/api/watchlist/add"}:
Handler._runtime(self) Handler._runtime(self)
result = Handler._context(self).add_to_watchlist(self.body_json()) result = Handler._context(self).add_to_watchlist(Handler.require_json_object(self, self.body_json()))
status = HTTPStatus.CREATED if result.get("created") else HTTPStatus.OK status = HTTPStatus.CREATED if result.get("created") else HTTPStatus.OK
self.json(result, status) self.json(result, status)
elif parsed.path == "/api/watchlist/ensure-thumbnails": elif parsed.path == "/api/watchlist/ensure-thumbnails":
+7
View File
@@ -1125,6 +1125,13 @@ class HandlerRouteTests(unittest.TestCase):
self.assertEqual(handler.json_payload, result) self.assertEqual(handler.json_payload, result)
self.assertEqual(handler.json_status, HTTPStatus.OK) self.assertEqual(handler.json_status, HTTPStatus.OK)
def test_watchlist_add_rejects_non_object_json_body(self):
body = b"[]"
handler = DummyHandler("/api/watchlist/add", body=body, content_length=len(body))
APP.Handler.do_POST(handler)
self.assertEqual(handler.error_status, HTTPStatus.BAD_REQUEST)
self.assertIn("json object", handler.error_message.lower())
def test_ensure_thumbnails_rejects_non_array_show_ids(self): def test_ensure_thumbnails_rejects_non_array_show_ids(self):
body = b'{"show_ids":"show-1"}' body = b'{"show_ids":"show-1"}'
handler = DummyHandler("/api/watchlist/ensure-thumbnails", body=body, content_length=len(body)) handler = DummyHandler("/api/watchlist/ensure-thumbnails", body=body, content_length=len(body))