import pytest import shutil import os import json from pathlib import Path from unittest import mock from saberlist.PlaylistBuilder import PlaylistBuilder @pytest.fixture def temp_environment(tmp_path): """ Fixture to set up a temporary environment for testing. Creates temporary directories for covers and output, copies the sample cover image, and initializes PlaylistBuilder. """ # Define paths within the temporary directory covers_dir = tmp_path / "covers" history_file = tmp_path / "playlist_history.json" output_dir = tmp_path / "output" # Create necessary directories covers_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True) # Path to the sample cover image in the repository sample_cover_src = Path(__file__).parent / 'assets' / 'sample_cover.jpg' # Ensure the sample cover exists assert sample_cover_src.exists(), f"Sample cover not found at {sample_cover_src}" # Copy the sample cover to the covers directory shutil.copy(sample_cover_src, covers_dir) # Initialize the PlaylistBuilder with the test paths builder = PlaylistBuilder( covers_dir=str(covers_dir), history_file=str(history_file), output_dir=str(output_dir) ) # Sample song data sample_song = { "hash": "7c9e0a7c523395c7ef9d79006b9d42dc6ab8b44a", "key": "2a44e", "levelId": "custom_level_7c9e0a7c523395c7ef9d79006b9d42dc6ab8b44a", "songName": "Gleodream", "difficulties": [ { "name": "normal", "characteristic": "Standard" } ] } yield { "builder": builder, "covers_dir": covers_dir, "history_file": history_file, "output_dir": output_dir, "sample_song": sample_song, "sample_cover_src": sample_cover_src } # Teardown is handled automatically by pytest's tmp_path fixture def test_create_playlist(temp_environment, caplog): """ Test the creation of a playlist with a sample song and cover. """ env = temp_environment builder = env["builder"] covers_dir = env["covers_dir"] history_file = env["history_file"] output_dir = env["output_dir"] sample_song = env["sample_song"] # Create the playlist playlist_title = "Test Playlist" playlist_author = "Test Author" playlist_path = builder.create_playlist( playlist_data=[sample_song], playlist_title=playlist_title, playlist_author=playlist_author ) # Verify that the playlist file exists assert os.path.exists(playlist_path), "Playlist file was not created." # Load the playlist content with open(playlist_path, 'r') as f: playlist_content = json.load(f) # Check playlist metadata assert playlist_content['playlistTitle'] == playlist_title assert playlist_content['playlistAuthor'] == playlist_author assert len(playlist_content['songs']) == 1 # Verify song details song = playlist_content['songs'][0] assert song['hash'] == sample_song['hash'] assert song['key'] == sample_song['key'] assert song['levelId'] == sample_song['levelId'] assert song['songName'] == sample_song['songName'] assert len(song['difficulties']) == 1 assert song['difficulties'][0]['name'] == 'normal' assert song['difficulties'][0]['characteristic'] == 'Standard' # Check that an image is encoded and included assert 'image' in playlist_content assert playlist_content['image'] is not None, "Image was not encoded in the playlist." assert playlist_content['image'].startswith("data:image/png;base64,"), "Image encoding is incorrect." # Verify coverImage path assert 'coverImage' in playlist_content expected_cover_path = str(covers_dir / 'sample_cover.jpg') assert playlist_content['coverImage'] == expected_cover_path # Verify description assert 'description' in playlist_content assert 'Playlist created by SaberList Tool on' in playlist_content['description'] # Check that duplicates are not allowed assert not playlist_content['allowDuplicates'], "Duplicates should not be allowed." # Check customData fields are present but None assert 'customData' in playlist_content assert playlist_content['customData'] is not None assert playlist_content['customData']['syncURL'] is None assert playlist_content['customData']['owner'] is None assert playlist_content['customData']['id'] is None assert playlist_content['customData']['hash'] is None assert playlist_content['customData']['shared'] is None # Verify that the cover was marked as used in history with open(history_file, 'r') as f: history = json.load(f) assert 'sample_cover.jpg' in history['used_covers'], "Cover image was not marked as used in history." def test_no_available_covers(temp_environment, caplog): """ Test behavior when no unused cover images are available. """ env = temp_environment builder = env["builder"] covers_dir = env["covers_dir"] history_file = env["history_file"] output_dir = env["output_dir"] sample_song = env["sample_song"] # Remove all covers to simulate no available covers shutil.rmtree(covers_dir) covers_dir.mkdir(parents=True, exist_ok=True) # Recreate covers directory without any covers # Attempt to create a playlist playlist_path = builder.create_playlist( playlist_data=[sample_song], playlist_title="No Cover Playlist", playlist_author="Test Author" ) # Verify that the playlist file exists assert os.path.exists(playlist_path), "Playlist file was not created when no covers are available." # Load the playlist content with open(playlist_path, 'r') as f: playlist_content = json.load(f) # Check that image and coverImage are None or not set assert playlist_content['image'] is None, "Image should be None when no covers are available." assert playlist_content['coverImage'] is None, "coverImage should be None when no covers are available." # Create another playlist to trigger logging builder.create_playlist( playlist_data=[sample_song], playlist_title="Another No Cover Playlist", playlist_author="Test Author" ) # Verify that a warning was logged assert any("No unused cover images available. Using no cover." in message for message in caplog.text.split('\n')), \ "Expected warning was not logged." def test_history_persistence(temp_environment): """ Test that the history is persisted correctly across multiple playlist creations. """ env = temp_environment builder = env["builder"] covers_dir = env["covers_dir"] history_file = env["history_file"] sample_song = env["sample_song"] # Create first playlist builder.create_playlist( playlist_data=[sample_song], playlist_title="First Playlist", playlist_author="Author A" ) # Attempt to create a second playlist; since there's only one cover, it should warn and not use any cover playlist_path = builder.create_playlist( playlist_data=[sample_song], playlist_title="Second Playlist", playlist_author="Author B" ) # Load the second playlist with open(playlist_path, 'r') as f: playlist_content = json.load(f) # Check that no image is set since the cover was already used assert playlist_content['image'] is None, "Image should be None when cover is already used." assert playlist_content['coverImage'] is None, "coverImage should be None when cover is already used." # Verify history contains the cover with open(history_file, 'r') as f: history = json.load(f) assert 'sample_cover.jpg' in history['used_covers'], "Cover image was not marked as used in history." @mock.patch('saberlist.PlaylistBuilder.random.choice') def test_random_cover_selection(mock_choice, temp_environment): """ Test that a specific cover is selected when random.choice is mocked. """ env = temp_environment builder = env["builder"] covers_dir = env["covers_dir"] history_file = env["history_file"] sample_song = env["sample_song"] sample_cover_src = env["sample_cover_src"] # Add another cover to the covers directory additional_cover = covers_dir / 'additional_cover.jpg' shutil.copy(sample_cover_src, additional_cover) # Mock random.choice to return 'additional_cover.jpg' mock_choice.return_value = 'additional_cover.jpg' # Create the playlist playlist_path = builder.create_playlist( playlist_data=[sample_song], playlist_title="Mocked Cover Playlist", playlist_author="Mock Author" ) # Verify that the mocked cover was used by checking 'coverImage' expected_cover_path = str(covers_dir / 'additional_cover.jpg') with open(playlist_path, 'r') as f: playlist_content = json.load(f) assert playlist_content['coverImage'] == expected_cover_path, "The coverImage should point to 'additional_cover.jpg'." # Optionally, verify that 'image' is correctly encoded by checking the data URL prefix assert playlist_content['image'].startswith("data:image/png;base64,"), "Image encoding is incorrect." # Verify that the history includes the mocked cover with open(history_file, 'r') as f: history = json.load(f) assert 'additional_cover.jpg' in history['used_covers'], "Cover image was not marked as used in history."