beatsaber-playlist-tool/tests/playlist_builder.py

260 lines
9.4 KiB
Python

import pytest
import shutil
import os
import json
from pathlib import Path
from unittest import mock
from helpers.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."