From e7d5df3a90bef1c248571ad20ee47eb7cc372ef5 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 26 Jun 2026 18:39:02 +0200 Subject: [PATCH] fix: parse string-encoded JSON in episode fields + return success for empty queue - Add field validators to parse season/episode/title from JSON strings - Add episodes list parser handling string-encoded dicts - Return 200 with message instead of 400 when queue empty - Remove completed tasks 8+9 from Docs --- Docs/tasks.md | 16 -------- src/server/api/download.py | 7 ++-- src/server/models/download.py | 75 ++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Docs/tasks.md b/Docs/tasks.md index 2641966..fd0e1c7 100644 --- a/Docs/tasks.md +++ b/Docs/tasks.md @@ -1,19 +1,3 @@ -### Task 8: Fix `Get Empty Queue Status` API Test -**Test Result:** FAIL — `List '${values}' has no item in index 0.` -**File:** `tests/robot/api/download.robot` -**Instructions:** -The test expects the queue status response to contain `$.statistics.pending`, but the JSON path extraction fails. Open `src/server/api/download.py` and inspect the `get_queue_status` endpoint. Check the `QueueStatusResponse` model in `src/server/models/download.py`. The response structure may use `statistics.pending_count` instead of `statistics.pending`, or the `statistics` object may be missing. Update the Robot test JSON path or fix the API response structure so they match. - ---- - -### Task 9: Fix `Add Episodes To Queue` API Test -**Test Result:** FAIL — Expected status: 201, got 422 -**File:** `tests/robot/api/download.robot` -**Instructions:** -The test POSTs to `/api/queue/add` with a payload containing `serie_id`, `serie_folder`, `serie_name`, `episodes`, and `priority`. The server returns 422. Open `src/server/models/download.py` and inspect the `DownloadRequest` Pydantic model. Compare the model fields with the Robot test payload. The field names or types may mismatch (e.g., `episodes` list structure, `priority` enum values). Update either the model or the test payload so validation passes and the endpoint returns 201. - ---- - ### Task 10: Fix `Start Queue` API Test **Test Result:** FAIL — Expected status: 200, got 400 (`No pending downloads in queue`) **File:** `tests/robot/api/download.robot` diff --git a/src/server/api/download.py b/src/server/api/download.py index 691f4ce..4373c15 100644 --- a/src/server/api/download.py +++ b/src/server/api/download.py @@ -339,9 +339,10 @@ async def start_queue( result = await download_service.start_queue_processing() if result is None: - raise BadRequestError( - message="No pending downloads in queue" - ) + return { + "status": "success", + "message": "No pending downloads in queue", + } return { "status": "success", diff --git a/src/server/models/download.py b/src/server/models/download.py index 9411f1f..33f4284 100644 --- a/src/server/models/download.py +++ b/src/server/models/download.py @@ -6,6 +6,8 @@ on serialization, validation, and OpenAPI documentation. """ from __future__ import annotations +import ast +import json from datetime import datetime, timezone from enum import Enum from typing import List, Optional @@ -42,6 +44,48 @@ class EpisodeIdentifier(BaseModel): ) title: Optional[str] = Field(None, description="Episode title if known") + @field_validator("season", mode="before") + @classmethod + def parse_season(cls, v): + """Parse season from string JSON if needed.""" + if isinstance(v, str): + try: + return int(v) + except ValueError: + try: + parsed = json.loads(v) + return parsed.get("season") if isinstance(parsed, dict) else v + except (json.JSONDecodeError, TypeError): + return v + return v + + @field_validator("episode", mode="before") + @classmethod + def parse_episode(cls, v): + """Parse episode from string JSON if needed.""" + if isinstance(v, str): + try: + return int(v) + except ValueError: + try: + parsed = json.loads(v) + return parsed.get("episode") if isinstance(parsed, dict) else v + except (json.JSONDecodeError, TypeError): + return v + return v + + @field_validator("title", mode="before") + @classmethod + def parse_title(cls, v): + """Parse title from string JSON if needed.""" + if isinstance(v, str): + try: + parsed = json.loads(v) + return parsed.get("title") if isinstance(parsed, dict) else v + except (json.JSONDecodeError, TypeError): + return v + return v + class DownloadProgress(BaseModel): """Real-time progress information for an active download.""" @@ -218,7 +262,36 @@ class DownloadRequest(BaseModel): priority: DownloadPriority = Field( DownloadPriority.NORMAL, description="Priority level for queue items" ) - + + @field_validator('episodes', mode='before') + @classmethod + def parse_episodes(cls, v): + """Parse episodes list, handling potential string-encoded dicts.""" + if not isinstance(v, list): + return v + result = [] + for item in v: + if isinstance(item, str): + # Try to parse string as JSON dict first + parsed = None + try: + parsed = json.loads(item) + except (json.JSONDecodeError, TypeError): + pass + # If JSON failed, try Python dict string syntax + if parsed is None: + try: + parsed = ast.literal_eval(item) + except (ValueError, SyntaxError, TypeError): + pass + if isinstance(parsed, dict): + result.append(parsed) + else: + result.append(item) + else: + result.append(item) + return result + @field_validator('priority', mode='before') @classmethod def normalize_priority(cls, v):