import os import tempfile import unittest from pathlib import Path os.environ['MUSIC_WORKSHOP_DB_PATH'] = str( Path(tempfile.gettempdir()) / f'music_workshop_repair_preview_{next(tempfile._get_candidate_names())}.db' ) from backend.app.exception_service import ExceptionService from backend.app.repair_runner import RepairService from backend.app.task_store import TaskStore class RepairPreviewTests(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.output_root = Path(tempfile.gettempdir()) / f'music-workshop-output-{next(tempfile._get_candidate_names())}' self.task_store = TaskStore(self.db_path) self.exception_service = ExceptionService(self.task_store) self.repair_service = RepairService( self.task_store, self.exception_service, matcher=None, preprocessor=None, task_stream=None ) self.task = self.task_store.create_task_if_idle( { 'input': '/tmp/input', 'output': str(self.output_root), 'trash': '/tmp/trash' } ) def test_save_and_organize_preview_returns_final_library_preview(self): item = self._insert_metadata_exception( original_tags_json={ 'title': 'Raw Title', 'artist': 'Raw Artist', 'album': 'Raw Album', 'year': 1999 }, matched_metadata_json={ 'title': 'Matched Title', 'artist': 'Matched Artist', 'album': 'Matched Album', 'album_artist': 'Matched Artist', 'track_number': 7 }, match_source='musicbrainz' ) preview = self.repair_service.preview( { 'exception_ids': [item['id']], 'action': 'save_and_organize', 'params': {'metadata_patch': {'title': 'Manual Title'}} }, {'output': str(self.output_root), 'trash': '/tmp/trash'} ) item_preview = preview['items'][0]['final_library_preview'] self.assertEqual(item_preview['metadata']['title'], 'Manual Title') self.assertEqual(item_preview['metadata']['artist'], 'Matched Artist') self.assertEqual(item_preview['metadata']['album_artist'], 'Matched Artist') self.assertEqual(item_preview['metadata']['year'], 1999) self.assertEqual(item_preview['metadata_sources']['title'], '手动编辑') self.assertEqual(item_preview['metadata_sources']['artist'], 'MusicBrainz') self.assertEqual(item_preview['metadata_sources']['year'], '原始标签') self.assertEqual(item_preview['target_relative_path'], 'M/Matched Artist/Matched Album/07 - Manual Title.flac') self.assertEqual( item_preview['target_file_path'], str(self.output_root / 'M/Matched Artist/Matched Album/07 - Manual Title.flac') ) def test_save_and_organize_preview_rejects_missing_required_metadata(self): item = self._insert_metadata_exception( original_tags_json={'title': 'Raw Title'}, matched_metadata_json={'title': 'Matched Title'}, match_source='netease' ) with self.assertRaisesRegex(ValueError, 'title、artist、album_artist'): self.repair_service.preview( { 'exception_ids': [item['id']], 'action': 'save_and_organize', 'params': {'metadata_patch': {}} }, {'output': str(self.output_root), 'trash': '/tmp/trash'} ) def test_save_and_organize_preview_manual_patch_overrides_candidate_and_raw(self): item = self._insert_metadata_exception( original_tags_json={ 'title': 'Raw Title', 'artist': 'Raw Artist', 'album_artist': 'Raw Album Artist' }, matched_metadata_json={ 'title': 'Candidate Title', 'artist': 'Candidate Artist', 'album_artist': 'Candidate Album Artist', 'album': 'Candidate Album' }, match_source='netease' ) preview = self.repair_service.preview( { 'exception_ids': [item['id']], 'action': 'save_and_organize', 'params': { 'metadata_patch': { 'artist': 'Manual Artist', 'album_artist': 'Manual Album Artist' } } }, {'output': str(self.output_root), 'trash': '/tmp/trash'} ) item_preview = preview['items'][0]['final_library_preview'] self.assertEqual(item_preview['metadata']['title'], 'Candidate Title') self.assertEqual(item_preview['metadata']['artist'], 'Manual Artist') self.assertEqual(item_preview['metadata']['album_artist'], 'Manual Album Artist') self.assertEqual(item_preview['metadata_sources']['title'], '网易云') self.assertEqual(item_preview['metadata_sources']['artist'], '手动编辑') self.assertEqual(item_preview['metadata_sources']['album_artist'], '手动编辑') def _insert_metadata_exception(self, **overrides): filename = overrides.pop('filename', f'item-{next(tempfile._get_candidate_names())}.flac') extension = Path(filename).suffix or '.flac' return self.task_store.insert_task_item( self.task['task_id'], original_path=f'/tmp/input/{filename}', current_file_path=f'/tmp/input/{filename}', relative_path=f'Artist/Album/{filename}', filename=filename, extension=extension, size_bytes=123456, modified_at='2024-01-01T00:00:00Z', local_cover=None, local_lyric=None, scan_status='queued', scan_reason=None, scan_message=None, match_status='low_score', match_reason='score_gap_too_small', match_message='匹配候选分数过低', **overrides ) if __name__ == '__main__': unittest.main()