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
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."""
|
||||
@@ -219,6 +263,35 @@ class DownloadRequest(BaseModel):
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user