feat(subsonic): response envelope, id scheme, and error mapping

- envelope: one serializer emitting the <subsonic-response> wrapper in XML
  (default) and JSON (f=json), carrying status/version/type/serverVersion
- ids: stable, reversible type-prefixed ids (tr-/al-/ar-/pl-) ↔ UUIDs
- errors: /rest requests render the Subsonic error envelope (always HTTP 200)
  with standard codes (10 missing param, 40 wrong creds, 50, 70 not found)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Senko-san
2026-06-08 18:23:30 +03:00
parent 7a17e3babd
commit b975164fc2
5 changed files with 352 additions and 4 deletions
+72
View File
@@ -0,0 +1,72 @@
"""Unit tests for the Subsonic response envelope (XML + JSON shapes)."""
import json
from xml.etree import ElementTree as ET
from app.api.rest.envelope import (
SUBSONIC_API_VERSION,
subsonic_error,
subsonic_response,
)
def _xml_root(body: bytes) -> ET.Element:
return ET.fromstring(body)
def _local(tag: str) -> str:
return tag.rsplit("}", 1)[-1] # strip namespace
def test_ok_xml_shape() -> None:
resp = subsonic_response({"license": {"valid": True}}, fmt="xml")
assert resp.media_type.startswith("application/xml")
root = _xml_root(resp.body)
assert _local(root.tag) == "subsonic-response"
assert root.attrib["status"] == "ok"
assert root.attrib["version"] == SUBSONIC_API_VERSION
assert root.attrib["type"] == "mcma"
child = root[0]
assert _local(child.tag) == "license"
assert child.attrib["valid"] == "true"
def test_ok_json_shape() -> None:
resp = subsonic_response({"license": {"valid": True}}, fmt="json")
assert resp.media_type.startswith("application/json")
payload = json.loads(resp.body)["subsonic-response"]
assert payload["status"] == "ok"
assert payload["version"] == SUBSONIC_API_VERSION
assert payload["type"] == "mcma"
assert payload["license"] == {"valid": True}
def test_error_xml_shape() -> None:
resp = subsonic_error(40, "Wrong username or password.", fmt="xml")
root = _xml_root(resp.body)
assert root.attrib["status"] == "failed"
error = root[0]
assert _local(error.tag) == "error"
assert error.attrib["code"] == "40"
assert error.attrib["message"] == "Wrong username or password."
def test_error_json_shape() -> None:
resp = subsonic_error(70, "Not found.", fmt="json")
payload = json.loads(resp.body)["subsonic-response"]
assert payload["status"] == "failed"
assert payload["error"] == {"code": 70, "message": "Not found."}
def test_default_format_is_xml() -> None:
resp = subsonic_response(fmt=None)
assert resp.media_type.startswith("application/xml")
assert _xml_root(resp.body).attrib["status"] == "ok"
def test_list_renders_repeated_elements() -> None:
payload = {"genres": {"genre": [{"value": "Rock"}, {"value": "Jazz"}]}}
root = _xml_root(subsonic_response(payload, fmt="xml").body)
genres = root[0]
values = [g.text for g in genres]
assert values == ["Rock", "Jazz"]