import sqlite3 import tempfile import unittest from contextlib import contextmanager from pathlib import Path from unittest.mock import patch from fastapi.testclient import TestClient from music_server.app import create_app from tests.support import auth_headers class MfDetailRouteTests(unittest.TestCase): def _prepare_catalog_db(self, db_path: Path) -> None: conn = sqlite3.connect(db_path) conn.executescript( """ create table catalog_playlists ( playlist_id integer primary key, platform text not null, remote_playlist_id text not null, name text not null, description text, cover_url text, play_count integer not null, song_count integer not null, playable_song_count integer not null ); create table catalog_tracks ( song_id integer primary key, platform text not null, remote_song_id text not null, name text not null, singers text, album text, cover_url text, duration_ms integer not null, source_meta text not null ); create table catalog_playlist_tracks ( playlist_id integer not null, song_id integer not null, position integer not null, primary key (playlist_id, song_id) ); create table catalog_toplists ( toplist_id text primary key, platform text not null, name text not null, description text, cover_url text, play_count integer not null, song_count integer not null, playable_song_count integer not null, group_name text not null ); create table catalog_toplist_tracks ( toplist_id text not null, song_id integer not null, position integer not null, primary key (toplist_id, song_id) ); create table catalog_track_files ( song_id integer not null, quality_label text not null, ext text not null, file_size_bytes integer, 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_playlists ( playlist_id, platform, remote_playlist_id, name, description, cover_url, play_count, song_count, playable_song_count ) values (?, ?, ?, ?, ?, ?, ?, ?, ?) """, (1, "netease", "18165", "playlist-1", "desc", "https://img/p.jpg", 2000, 2, 2), ) conn.executemany( """ insert into catalog_tracks ( song_id, platform, remote_song_id, name, singers, album, cover_url, duration_ms, source_meta ) values (?, ?, ?, ?, ?, ?, ?, ?, ?) """, [ ( 3476, "netease", "65800", "song-3476", "artist-a", "album-a", "https://img/s-3476.jpg", 220000, "{}", ), ( 4001, "qq", "4001", "song-4001", "artist-b", "album-b", "https://img/s-4001.jpg", 180000, "{}", ), ], ) conn.executemany( """ insert into catalog_playlist_tracks (playlist_id, song_id, position) values (?, ?, ?) """, [(1, 3476, 1), (1, 4001, 2)], ) conn.execute( """ insert into catalog_toplists ( toplist_id, platform, name, description, cover_url, play_count, song_count, playable_song_count, group_name ) values (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( "kuwo_top_16", "kuwo", "\u9177\u6211\u98d9\u5347\u699c", "desc", "https://img/t.jpg", 1000, 2, 2, "\u9177\u6211", ), ) conn.executemany( """ insert into catalog_toplist_tracks (toplist_id, song_id, position) values (?, ?, ?) """, [("kuwo_top_16", 3476, 1), ("kuwo_top_16", 4001, 2)], ) conn.executemany( """ 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", 100, "object_storage", "cdn", "song-3476.flac", "https://cdn/song-3476.flac", "active", 1, ), ( 4001, "standard", "mp3", 90, "object_storage", "cdn", "song-4001.mp3", "https://cdn/song-4001.mp3", "active", 1, ), ], ) conn.commit() conn.close() @contextmanager def _catalog_client(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", { "PUBLIC_MUSIC_ACCESS_TOKEN": "dev-token", "CATALOG_DB_PATH": str(db_path), "PLAYER_DB_PATH": str(player_db_path), }, clear=False, ): yield TestClient(create_app()), player_db_path def test_get_toplist_detail_returns_musicfree_shape(self): with self._catalog_client() as (client, player_db_path): response = client.get( "/mf/v1/toplists/kuwo_top_16", headers=auth_headers(player_db_path), ) self.assertEqual(200, response.status_code) payload = response.json() self.assertEqual("catalogsync:toplist:kuwo_top_16", payload["id"]) self.assertEqual("catalogsync", payload["platform"]) self.assertEqual("\u9177\u6211\u98d9\u5347\u699c", payload["title"]) def test_get_toplist_tracks_pagination(self): with self._catalog_client() as (client, player_db_path): first_page = client.get( "/mf/v1/toplists/kuwo_top_16/tracks?page=1&page_size=1", headers=auth_headers(player_db_path), ) second_page = client.get( "/mf/v1/toplists/kuwo_top_16/tracks?page=2&page_size=1", headers=auth_headers(player_db_path), ) self.assertEqual(200, first_page.status_code) self.assertFalse(first_page.json()["isEnd"]) self.assertEqual("catalogsync:song:3476", first_page.json()["musicList"][0]["id"]) self.assertEqual(200, second_page.status_code) self.assertTrue(second_page.json()["isEnd"]) self.assertEqual("catalogsync:song:4001", second_page.json()["musicList"][0]["id"]) def test_get_toplist_detail_returns_404_when_missing(self): with self._catalog_client() as (client, player_db_path): response = client.get( "/mf/v1/toplists/missing_toplist", headers=auth_headers(player_db_path), ) self.assertEqual(404, response.status_code) def test_get_toplist_tracks_returns_404_when_missing(self): with self._catalog_client() as (client, player_db_path): response = client.get( "/mf/v1/toplists/missing_toplist/tracks?page=1&page_size=1", headers=auth_headers(player_db_path), ) self.assertEqual(404, response.status_code) def test_get_toplist_routes_require_token(self): with self._catalog_client() as (client, _player_db_path): detail_response = client.get("/mf/v1/toplists/kuwo_top_16") tracks_response = client.get( "/mf/v1/toplists/kuwo_top_16/tracks?page=1&page_size=1" ) self.assertEqual(401, detail_response.status_code) self.assertEqual(401, tracks_response.status_code) def test_legacy_playlist_tracks_and_toplists_routes_still_work(self): with self._catalog_client() as (client, player_db_path): playlist_tracks_response = client.get( "/mf/v1/playlists/1/tracks?page=1&page_size=20", headers=auth_headers(player_db_path), ) toplists_response = client.get( "/mf/v1/toplists", headers=auth_headers(player_db_path), ) self.assertEqual(200, playlist_tracks_response.status_code) playlist_payload = playlist_tracks_response.json() self.assertTrue(playlist_payload["musicList"]) self.assertEqual("catalogsync:song:3476", playlist_payload["musicList"][0]["id"]) self.assertEqual(200, toplists_response.status_code) toplists_payload = toplists_response.json() self.assertEqual("\u9177\u6211", toplists_payload[0]["title"]) if __name__ == "__main__": unittest.main()