Files
musicdl-catalog-sync-suite/Music_Server/tests/test_mf_media_routes.py
T

579 lines
24 KiB
Python

import sqlite3
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from fastapi.testclient import TestClient
from music_server.app import create_app
from music_server.services.cache_service import CacheService
from tests.support import auth_headers
class MfMediaRouteTests(unittest.TestCase):
def _prepare_catalog_db(
self,
db_path: Path,
*,
backend_type: str = "object_storage",
backend_name: str = "main-s3",
locator: str = "music/netease/test.flac",
public_url: str | None = "https://cdn.example/test.flac",
file_size_bytes: int = 42345678,
) -> None:
conn = sqlite3.connect(db_path)
conn.execute(
"""
create table catalog_track_files (
song_id integer not null,
quality_label text not null,
ext text not null,
file_size_bytes integer not null,
backend_type text not null,
backend_name text not null,
locator text not null,
public_url text,
status text not null,
is_primary integer not null
)
"""
)
conn.execute(
"""
insert into catalog_track_files (
song_id,
quality_label,
ext,
file_size_bytes,
backend_type,
backend_name,
locator,
public_url,
status,
is_primary
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
3476,
"super",
"flac",
file_size_bytes,
backend_type,
backend_name,
locator,
public_url,
"active",
1,
),
)
conn.commit()
conn.close()
def _prepare_active_cache(
self,
*,
player_db_path: Path,
catalog_db_path: Path,
song_id: int = 3476,
cache_url: str = "https://cache.example/test.flac",
status: str = "active",
) -> None:
service = CacheService(
player_db_path=str(player_db_path),
catalog_db_path=str(catalog_db_path),
secret_encryption_key="test-secret",
)
target = service.create_cache_target(
name="edge-cache",
kind="s3",
order_index=1,
capacity_songs=10,
public_base_url="https://cache.example",
path_prefix="songs",
enabled=True,
secrets={"bucket": "music", "region": "test"},
)
service.upsert_cache_object(
song_id=song_id,
target_id=target["id"],
quality_label="super",
source_locator="cache/test.flac",
remote_key="songs/test.flac",
public_url=cache_url,
status=status,
last_rank=1,
uploaded_at="2026-04-23T00:00:00+00:00",
last_verified_at="2026-04-23T00:00:00+00:00",
evictable=False,
)
def test_media_stream_extension_route_registers_before_generic_route(self):
app = create_app()
stream_paths = [
route.path
for route in app.routes
if getattr(route, "path", "").startswith("/mf/v1/media/stream/")
]
self.assertEqual(
[
"/mf/v1/media/stream/{token}.{ext}",
"/mf/v1/media/stream/{token}",
],
stream_paths,
)
def test_media_resolve_returns_selected_source_and_signed_stream_url(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
},
clear=False,
):
client = TestClient(create_app())
response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, response.status_code)
payload = response.json()
self.assertEqual("object_storage", payload["selected_source"]["kind"])
self.assertEqual("main-s3", payload["selected_source"]["backend"])
self.assertEqual("super", payload["selected_source"]["quality"])
self.assertEqual("flac", payload["selected_source"]["ext"])
self.assertEqual(42345678, payload["selected_source"]["size_bytes"])
self.assertIn("/mf/v1/media/stream/", payload["stream"]["url"])
self.assertTrue(payload["stream"]["url"].endswith(".flac"))
def test_media_stream_redirects_to_public_url(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
stream_url = resolve_response.json()["stream"]["url"]
stream_response = client.get(
stream_url,
follow_redirects=False,
)
self.assertEqual(307, stream_response.status_code)
self.assertEqual(
"https://cdn.example/test.flac",
stream_response.headers.get("location"),
)
def test_media_resolve_prefers_active_cache_object(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
self._prepare_active_cache(player_db_path=player_db_path, catalog_db_path=db_path)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"MUSIC_SERVER_SECRET_ENCRYPTION_KEY": "test-secret",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
self.assertEqual("cache", resolve_response.json()["selected_source"]["kind"])
with patch("music_server.routes.mf_media._is_cache_url_reachable", return_value=True):
stream_response = client.get(
resolve_response.json()["stream"]["url"],
follow_redirects=False,
)
self.assertEqual(307, stream_response.status_code)
self.assertEqual(
"https://cache.example/test.flac",
stream_response.headers.get("location"),
)
def test_media_resolve_falls_back_when_cache_object_is_not_active(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
self._prepare_active_cache(
player_db_path=player_db_path,
catalog_db_path=db_path,
status="failed",
)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"MUSIC_SERVER_SECRET_ENCRYPTION_KEY": "test-secret",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
self.assertEqual("object_storage", resolve_response.json()["selected_source"]["kind"])
stream_response = client.get(
resolve_response.json()["stream"]["url"],
follow_redirects=False,
)
self.assertEqual(307, stream_response.status_code)
self.assertEqual(
"https://cdn.example/test.flac",
stream_response.headers.get("location"),
)
def test_media_resolve_and_stream_ignore_cache_when_cache_relay_disabled(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
self._prepare_active_cache(player_db_path=player_db_path, catalog_db_path=db_path)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"MUSIC_SERVER_SECRET_ENCRYPTION_KEY": "test-secret",
"MUSIC_SERVER_CACHE_RELAY_ENABLED": "0",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
self.assertEqual("object_storage", resolve_response.json()["selected_source"]["kind"])
with patch("music_server.routes.mf_media._is_cache_url_reachable", return_value=True):
stream_response = client.get(
resolve_response.json()["stream"]["url"],
follow_redirects=False,
)
heat_service = CacheService(
player_db_path=str(player_db_path),
catalog_db_path=str(db_path),
secret_encryption_key="test-secret",
)
summary = heat_service.get_heat_summary(song_id=3476)
self.assertEqual(307, stream_response.status_code)
self.assertEqual(
"https://cdn.example/test.flac",
stream_response.headers.get("location"),
)
self.assertEqual(0, summary["play_count_total"])
self.assertEqual(0, summary["play_count_30d"])
def test_media_stream_falls_back_when_cached_public_url_is_unreachable(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
self._prepare_active_cache(player_db_path=player_db_path, catalog_db_path=db_path)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"MUSIC_SERVER_SECRET_ENCRYPTION_KEY": "test-secret",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
with patch("music_server.routes.mf_media._is_cache_url_reachable", return_value=False):
stream_response = client.get(
resolve_response.json()["stream"]["url"],
follow_redirects=False,
)
self.assertEqual(307, stream_response.status_code)
self.assertEqual(
"https://cdn.example/test.flac",
stream_response.headers.get("location"),
)
def test_media_stream_falls_back_when_cached_public_url_is_unreachable(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
self._prepare_catalog_db(db_path)
self._prepare_active_cache(player_db_path=player_db_path, catalog_db_path=db_path)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"MUSIC_SERVER_SECRET_ENCRYPTION_KEY": "test-secret",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
with patch("music_server.routes.mf_media._is_cache_url_reachable", return_value=False):
stream_response = client.get(
resolve_response.json()["stream"]["url"],
follow_redirects=False,
)
self.assertEqual(307, stream_response.status_code)
self.assertEqual(
"https://cdn.example/test.flac",
stream_response.headers.get("location"),
)
def test_media_stream_serves_local_file_when_public_url_missing(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
library_root = Path(tmpdir) / "library"
local_file = library_root / "music" / "netease" / "test.flac"
local_file.parent.mkdir(parents=True, exist_ok=True)
local_file.write_bytes(b"flac-bytes")
self._prepare_catalog_db(
db_path,
backend_type="local_fs",
backend_name="default-local",
locator="music/netease/test.flac",
public_url=None,
file_size_bytes=10,
)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"LOCAL_LIBRARY_ROOT": str(library_root),
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
stream_url = resolve_response.json()["stream"]["url"]
stream_response = client.get(stream_url)
self.assertEqual(200, stream_response.status_code)
self.assertEqual(b"flac-bytes", stream_response.content)
self.assertEqual("bytes", stream_response.headers.get("accept-ranges"))
self.assertEqual("10", stream_response.headers.get("content-length"))
self.assertEqual("audio/flac", stream_response.headers.get("content-type"))
def test_media_stream_local_single_range_returns_206_and_partial_content(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
library_root = Path(tmpdir) / "library"
local_file = library_root / "music" / "netease" / "test.flac"
local_file.parent.mkdir(parents=True, exist_ok=True)
local_file.write_bytes(b"0123456789")
self._prepare_catalog_db(
db_path,
backend_type="local_fs",
backend_name="default-local",
locator="music/netease/test.flac",
public_url=None,
file_size_bytes=10,
)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"LOCAL_LIBRARY_ROOT": str(library_root),
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
stream_url = resolve_response.json()["stream"]["url"]
stream_response = client.get(stream_url, headers={"Range": "bytes=2-5"})
self.assertEqual(206, stream_response.status_code)
self.assertEqual(b"2345", stream_response.content)
self.assertEqual("bytes", stream_response.headers.get("accept-ranges"))
self.assertEqual("bytes 2-5/10", stream_response.headers.get("content-range"))
self.assertEqual("4", stream_response.headers.get("content-length"))
self.assertEqual("audio/flac", stream_response.headers.get("content-type"))
def test_media_stream_local_invalid_range_returns_416(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
library_root = Path(tmpdir) / "library"
local_file = library_root / "music" / "netease" / "test.flac"
local_file.parent.mkdir(parents=True, exist_ok=True)
local_file.write_bytes(b"0123456789")
self._prepare_catalog_db(
db_path,
backend_type="local_fs",
backend_name="default-local",
locator="music/netease/test.flac",
public_url=None,
file_size_bytes=10,
)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"LOCAL_LIBRARY_ROOT": str(library_root),
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
stream_url = resolve_response.json()["stream"]["url"]
stream_response = client.get(stream_url, headers={"Range": "bytes=99-100"})
self.assertEqual(416, stream_response.status_code)
self.assertEqual("bytes", stream_response.headers.get("accept-ranges"))
self.assertEqual("bytes */10", stream_response.headers.get("content-range"))
def test_media_stream_counts_heat_once_for_repeated_stream_requests(self):
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "catalog_read.db"
player_db_path = Path(tmpdir) / "player.db"
library_root = Path(tmpdir) / "library"
local_file = library_root / "music" / "netease" / "test.flac"
local_file.parent.mkdir(parents=True, exist_ok=True)
local_file.write_bytes(b"0123456789")
self._prepare_catalog_db(
db_path,
backend_type="local_fs",
backend_name="default-local",
locator="music/netease/test.flac",
public_url=None,
file_size_bytes=10,
)
with patch.dict(
"os.environ",
{
"CATALOG_DB_PATH": str(db_path),
"PLAYER_DB_PATH": str(player_db_path),
"PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token",
"LOCAL_LIBRARY_ROOT": str(library_root),
"MUSIC_SERVER_SECRET_ENCRYPTION_KEY": "test-secret",
},
clear=False,
):
client = TestClient(create_app())
resolve_response = client.post(
"/mf/v1/media/resolve",
headers=auth_headers(player_db_path),
json={"song_id": "catalogsync:song:3476", "quality": "super"},
)
self.assertEqual(200, resolve_response.status_code)
stream_url = resolve_response.json()["stream"]["url"]
full_response = client.get(stream_url)
range_response = client.get(stream_url, headers={"Range": "bytes=2-5"})
heat_service = CacheService(
player_db_path=str(player_db_path),
catalog_db_path=str(db_path),
secret_encryption_key="test-secret",
)
summary = heat_service.get_heat_summary(song_id=3476)
self.assertEqual(200, full_response.status_code)
self.assertEqual(206, range_response.status_code)
self.assertEqual(1, summary["play_count_total"])
self.assertEqual(1, summary["play_count_30d"])
if __name__ == "__main__":
unittest.main()