import os import tempfile import unittest from pathlib import Path from unittest.mock import patch os.environ['MUSIC_WORKSHOP_DB_PATH'] = str( Path(tempfile.gettempdir()) / f'music_workshop_test_{next(tempfile._get_candidate_names())}.db' ) import backend.app.main as main_module import backend.app.metadata_status as metadata_status_module from backend.app.schemas import ConfigPayload from backend.app.storage import ConfigStore class ConfigApiTests(unittest.TestCase): def setUp(self): self.db_path = Path(os.environ['MUSIC_WORKSHOP_DB_PATH']) if self.db_path.exists(): self.db_path.unlink() self.store = ConfigStore(self.db_path) self.previous_store = main_module.store main_module.store = self.store def tearDown(self): main_module.store = self.previous_store def test_get_config_returns_defaults(self): data = main_module.get_config() self.assertIn('advancedStrategy', data) self.assertNotIn('metadataStatus', data) self.assertEqual(data['schedule']['cron'], '0 2 * * *') self.assertEqual(data['metadata']['acoustidUrl'], 'https://api.acoustid.org/v2') def test_put_config_persists_changes(self): payload = self.store.get_config() payload['input'] = '/tmp/input' payload['advancedStrategy']['replaceLowQualityDuplicates'] = True expected_statuses = build_metadata_statuses() with patch.object(main_module, 'probe_metadata_services', return_value=expected_statuses): saved_payload = main_module.update_config(ConfigPayload.model_validate(payload)) read_payload = self.store.get_config() self.assertEqual(saved_payload['config']['input'], '/tmp/input') self.assertEqual(read_payload['input'], '/tmp/input') self.assertTrue(read_payload['advancedStrategy']['replaceLowQualityDuplicates']) self.assertEqual(saved_payload['metadataStatus'], expected_statuses) def test_get_metadata_status_returns_probe_results(self): expected_statuses = build_metadata_statuses() with patch.object(main_module, 'probe_metadata_services', return_value=expected_statuses) as probe: response = main_module.get_config_metadata_status() self.assertEqual(response, {'metadataStatus': expected_statuses}) probe.assert_called_once_with(self.store.get_config()['metadata']) def test_probe_metadata_services_skips_missing_credentials(self): payload = self.store.get_config() with patch.object( metadata_status_module, 'probe_url', return_value={'status': 'online', 'latencyMs': 100, 'message': '可达 (HTTP 200)'} ) as probe_url: statuses = metadata_status_module.probe_metadata_services(payload['metadata']) self.assertEqual(statuses['acoustid']['status'], 'none') self.assertEqual(statuses['musicbrainz']['status'], 'online') self.assertEqual(statuses['netease']['status'], 'online') self.assertEqual(statuses['qq']['status'], 'online') self.assertEqual(statuses['spotify']['status'], 'none') self.assertEqual(statuses['discogs']['status'], 'none') self.assertEqual(statuses['lastfm']['status'], 'none') self.assertEqual(statuses['genius']['status'], 'none') self.assertEqual(probe_url.call_count, 3) def build_metadata_statuses(): return { 'acoustid': {'status': 'none', 'latencyMs': None, 'message': '缺失凭据,跳过测试'}, 'musicbrainz': {'status': 'online', 'latencyMs': 123, 'message': '可达 (HTTP 200)'}, 'netease': {'status': 'online', 'latencyMs': 42, 'message': '可达 (HTTP 200)'}, 'qq': {'status': 'warning', 'latencyMs': 680, 'message': '高延迟 (HTTP 200)'}, 'spotify': {'status': 'none', 'latencyMs': None, 'message': '缺失凭据,跳过测试'}, 'discogs': {'status': 'none', 'latencyMs': None, 'message': '缺失凭据,跳过测试'}, 'lastfm': {'status': 'none', 'latencyMs': None, 'message': '缺失凭据,跳过测试'}, 'genius': {'status': 'none', 'latencyMs': None, 'message': '缺失凭据,跳过测试'} } if __name__ == '__main__': unittest.main()