305 lines
11 KiB
Python
305 lines
11 KiB
Python
"""Unit tests for mood engine and audio services."""
|
|
import pytest
|
|
import sys
|
|
import os
|
|
import json
|
|
import tempfile
|
|
import shutil
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
from app.services.mood_engine import analyze_lyrics, MOOD_NAMES, CONFIDENCE_THRESHOLD
|
|
|
|
class TestMoodEngine:
|
|
"""Test the mood classification engine."""
|
|
|
|
def test_analyze_sad_lyrics(self):
|
|
lyrics = "I cry alone in the dark, tears falling down, my heart is broken and I feel so lonely"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Sad"] > 0
|
|
assert scores["Sad"] >= scores["Happy"]
|
|
|
|
def test_analyze_happy_lyrics(self):
|
|
lyrics = "I'm so happy and joyful, dancing in the sunshine, having fun and feeling free"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Happy"] > 0
|
|
assert scores["Happy"] >= scores["Sad"]
|
|
|
|
def test_analyze_energetic_lyrics(self):
|
|
lyrics = "Fire burning, power rising, strong and fast, thunder and storm"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Energetic"] > 0
|
|
assert scores["Energetic"] >= scores["Chill"]
|
|
|
|
def test_analyze_romantic_lyrics(self):
|
|
lyrics = "I love you baby, my heart beats for you, forever together, my darling"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Romantic"] > 0
|
|
assert scores["Romantic"] >= scores["Angry"]
|
|
|
|
def test_analyze_angry_lyrics(self):
|
|
lyrics = "I feel rage and fury, hate and betrayal, screaming and destroying everything"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Angry"] > 0
|
|
assert scores["Angry"] >= scores["Happy"]
|
|
|
|
def test_analyze_chill_lyrics(self):
|
|
lyrics = "Just relax and chill, smooth vibes and easy groove, mellow and laid back"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Chill"] > 0
|
|
assert scores["Chill"] >= scores["Energetic"]
|
|
|
|
def test_analyze_focused_lyrics(self):
|
|
lyrics = "Clear mind, deep flow, steady focus and control, peace and quiet"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Focused"] > 0
|
|
|
|
def test_analyze_nostalgic_lyrics(self):
|
|
lyrics = "I remember the past, yesterday was golden, childhood memories at home"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Nostalgic"] > 0
|
|
assert scores["Nostalgic"] >= scores["Energetic"]
|
|
|
|
def test_analyze_melancholy_lyrics(self):
|
|
lyrics = "Sorrow and grief, fading shadows, silence and cold, heavy darkness"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Melancholy"] > 0
|
|
assert scores["Melancholy"] >= scores["Happy"]
|
|
|
|
def test_analyze_dreamy_lyrics(self):
|
|
lyrics = "Dreaming of stars and sky, floating through space, magic and wonder, ethereal glow"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Dreamy"] > 0
|
|
assert scores["Dreamy"] >= scores["Angry"]
|
|
|
|
def test_empty_lyrics(self):
|
|
scores = analyze_lyrics("")
|
|
for mood in MOOD_NAMES:
|
|
assert scores[mood] == 0.0
|
|
|
|
def test_none_lyrics(self):
|
|
scores = analyze_lyrics(None)
|
|
for mood in MOOD_NAMES:
|
|
assert scores[mood] == 0.0
|
|
|
|
def test_scores_are_normalized(self):
|
|
lyrics = "I cry alone in the dark, tears falling down"
|
|
scores = analyze_lyrics(lyrics)
|
|
max_score = max(scores.values())
|
|
assert max_score <= 1.0
|
|
|
|
def test_all_moods_present(self):
|
|
scores = analyze_lyrics("test lyrics here")
|
|
assert len(scores) == len(MOOD_NAMES)
|
|
for mood in MOOD_NAMES:
|
|
assert mood in scores
|
|
|
|
def test_mixed_mood_lyrics(self):
|
|
lyrics = "I'm happy but also sad, crying tears of joy, dancing alone"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert scores["Happy"] > 0 or scores["Sad"] > 0
|
|
|
|
def test_case_insensitive(self):
|
|
scores_lower = analyze_lyrics("I cry alone in the dark")
|
|
scores_upper = analyze_lyrics("I CRY ALONE IN THE DARK")
|
|
assert scores_lower == scores_upper
|
|
|
|
def test_confidence_threshold_constant(self):
|
|
assert 0 < CONFIDENCE_THRESHOLD < 1
|
|
|
|
def test_mood_names_list(self):
|
|
expected = ["Sad", "Happy", "Energetic", "Focused", "Chill", "Romantic", "Angry", "Nostalgic", "Melancholy", "Dreamy"]
|
|
assert MOOD_NAMES == expected
|
|
|
|
def test_long_lyrics(self):
|
|
lyrics = " ".join(["word"] * 10000)
|
|
scores = analyze_lyrics(lyrics)
|
|
assert len(scores) == len(MOOD_NAMES)
|
|
|
|
def test_special_characters_in_lyrics(self):
|
|
lyrics = "I cry! alone... in the dark? tears!!"
|
|
scores = analyze_lyrics(lyrics)
|
|
assert "Sad" in scores
|
|
|
|
|
|
class TestAudioService:
|
|
"""Test audio utility functions."""
|
|
|
|
def test_supported_formats(self):
|
|
from app.services.audio import SUPPORTED_FORMATS
|
|
expected = {'.mp3', '.aac', '.flac', '.wav', '.ogg', '.m4a'}
|
|
assert SUPPORTED_FORMATS == expected
|
|
|
|
def test_extract_metadata_returns_dict(self):
|
|
from app.services.audio import extract_metadata
|
|
result = extract_metadata("/nonexistent/file.mp3")
|
|
assert isinstance(result, dict)
|
|
|
|
def test_extract_metadata_nonexistent_file(self):
|
|
from app.services.audio import extract_metadata
|
|
result = extract_metadata("/nonexistent/path/file.mp3")
|
|
assert result.get("title") is None
|
|
assert result.get("artist") is None
|
|
|
|
def test_transcode_nonexistent_file(self):
|
|
from app.services.audio import transcode_to_ogg
|
|
result = transcode_to_ogg("/nonexistent/file.mp3", "/tmp")
|
|
assert result is None
|
|
|
|
def test_get_stream_path_priority(self):
|
|
from app.services.audio import get_stream_path
|
|
from unittest.mock import MagicMock
|
|
|
|
song = MagicMock()
|
|
song.transcoded_path = "/tmp/existing.ogg"
|
|
song.file_path = "/tmp/original.mp3"
|
|
|
|
# If transcoded path exists, it should be preferred
|
|
os.makedirs("/tmp", exist_ok=True)
|
|
with open("/tmp/existing.ogg", "w") as f:
|
|
f.write("")
|
|
|
|
result = get_stream_path(song)
|
|
assert result == "/tmp/existing.ogg"
|
|
os.remove("/tmp/existing.ogg")
|
|
|
|
def test_get_stream_path_fallback(self):
|
|
from app.services.audio import get_stream_path
|
|
from unittest.mock import MagicMock
|
|
|
|
song = MagicMock()
|
|
song.transcoded_path = None
|
|
song.file_path = "/nonexistent/file.mp3"
|
|
|
|
result = get_stream_path(song)
|
|
assert result is None
|
|
|
|
def test_scan_empty_directory(self):
|
|
from app.services.audio import scan_directory
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
# Need a mock db
|
|
from unittest.mock import MagicMock
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.count.return_value = 0
|
|
|
|
result = scan_directory(tmpdir, mock_db)
|
|
assert result.scanned == 0
|
|
assert result.added == 0
|
|
assert result.errors == []
|
|
|
|
def test_scan_skips_non_audio_files(self):
|
|
from app.services.audio import scan_directory
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
# Create non-audio files
|
|
with open(os.path.join(tmpdir, "readme.txt"), "w") as f:
|
|
f.write("test")
|
|
with open(os.path.join(tmpdir, "image.jpg"), "w") as f:
|
|
f.write("test")
|
|
|
|
from unittest.mock import MagicMock
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.count.return_value = 0
|
|
|
|
result = scan_directory(tmpdir, mock_db)
|
|
assert result.scanned == 0
|
|
|
|
def test_delete_song_removes_files(self):
|
|
from app.services.audio import delete_song
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
file1 = os.path.join(tmpdir, "test1.ogg")
|
|
file2 = os.path.join(tmpdir, "test2.mp3")
|
|
open(file1, "w").close()
|
|
open(file2, "w").close()
|
|
|
|
from unittest.mock import MagicMock
|
|
song = MagicMock()
|
|
song.file_path = file1
|
|
song.transcoded_path = file2
|
|
song.album_art_path = None
|
|
|
|
delete_song(song)
|
|
assert not os.path.exists(file1)
|
|
assert not os.path.exists(file2)
|
|
|
|
shutil.rmtree(tmpdir)
|
|
|
|
|
|
class TestRadioBrowser:
|
|
"""Test radio browser service."""
|
|
|
|
def test_haversine_same_point(self):
|
|
from app.services.radio_browser import _haversine
|
|
distance = _haversine(40.0, -74.0, 40.0, -74.0)
|
|
assert distance == 0.0
|
|
|
|
def test_haversine_positive_distance(self):
|
|
from app.services.radio_browser import _haversine
|
|
distance = _haversine(40.7128, -74.0060, 51.5074, -0.1278)
|
|
assert distance > 0
|
|
assert distance < 20000 # NYC to London < 20000km
|
|
|
|
def test_haversine_symmetric(self):
|
|
from app.services.radio_browser import _haversine
|
|
d1 = _haversine(40.0, -74.0, 51.0, 0.0)
|
|
d2 = _haversine(51.0, 0.0, 40.0, -74.0)
|
|
assert abs(d1 - d2) < 0.01
|
|
|
|
|
|
class TestLyricsService:
|
|
"""Test lyrics service."""
|
|
|
|
def test_no_api_key_returns_none(self):
|
|
from app.services.lyrics import fetch_lyrics
|
|
result = fetch_lyrics("test song", "test artist")
|
|
assert result is None
|
|
|
|
def test_fetch_lyrics_returns_string_or_none(self):
|
|
from app.services.lyrics import fetch_lyrics
|
|
result = fetch_lyrics("song", "artist")
|
|
assert result is None or isinstance(result, str)
|
|
|
|
|
|
class TestSharePlay:
|
|
"""Test SharePlay service."""
|
|
|
|
def test_manager_creation(self):
|
|
from app.services.shareplay import SharePlayManager
|
|
mgr = SharePlayManager()
|
|
assert mgr.rooms == {}
|
|
assert mgr.connections == {}
|
|
|
|
def test_create_room(self):
|
|
from app.services.shareplay import SharePlayManager
|
|
from unittest.mock import MagicMock
|
|
|
|
mgr = SharePlayManager()
|
|
mock_db = MagicMock()
|
|
|
|
result = mgr.create_room(mock_db, creator="user1")
|
|
assert "id" in result
|
|
assert result["creator_user"] == "user1"
|
|
assert result["active_connections"] == 1
|
|
assert result["id"] in mgr.rooms
|
|
|
|
def test_update_state(self):
|
|
from app.services.shareplay import SharePlayManager
|
|
|
|
mgr = SharePlayManager()
|
|
mgr.rooms["test"] = {"is_playing": False, "position": 0}
|
|
|
|
result = mgr.update_state("test", is_playing=True)
|
|
assert result["is_playing"] is True
|
|
|
|
def test_get_nonexistent_state(self):
|
|
from app.services.shareplay import SharePlayManager
|
|
|
|
mgr = SharePlayManager()
|
|
result = mgr.get_state("nonexistent")
|
|
assert result is None
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"]) |