diff --git a/CHANGELOG.md b/CHANGELOG.md index 131fde3..31a2985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 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 - Fixed duplicate watchlist add responses so already-tracked shows now return `200 OK` with `created: false` instead of the misleading `201 Created`. diff --git a/README.md b/README.md index 11312fa..f3d60b5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A local web UI for a system-wide `ani-cli` install. -Current version: `0.29.3` +Current version: `0.29.4` ## 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 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. +- 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. - 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. diff --git a/VERSION b/VERSION index 5540b6e..35b1b3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.29.3 +0.29.4 diff --git a/app_support.py b/app_support.py index 9a50553..4456bbf 100644 --- a/app_support.py +++ b/app_support.py @@ -16,7 +16,7 @@ from uuid import uuid4 PROJECT_ROOT = Path(__file__).resolve().parent ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" APP_NAME = "ani-cli-web" -VERSION = "0.29.3" +VERSION = "0.29.4" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/http_handler.py b/http_handler.py index 8edbb14..baf0ac5 100644 --- a/http_handler.py +++ b/http_handler.py @@ -242,7 +242,7 @@ class Handler(BaseHTTPRequestHandler): self.json(actions[action](job_id)) elif parsed.path in {"/api/watchlist", "/api/watchlist/add"}: 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 self.json(result, status) elif parsed.path == "/api/watchlist/ensure-thumbnails": diff --git a/test_app.py b/test_app.py index ebdaa10..d62fb4d 100644 --- a/test_app.py +++ b/test_app.py @@ -1125,6 +1125,13 @@ class HandlerRouteTests(unittest.TestCase): self.assertEqual(handler.json_payload, result) 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): body = b'{"show_ids":"show-1"}' handler = DummyHandler("/api/watchlist/ensure-thumbnails", body=body, content_length=len(body))