"""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"])