579 lines
24 KiB
Python
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()
|