"""Tests for deep coverage of services and middleware.""" from unittest.mock import MagicMock, patch import tempfile import os class TestAPIKeyMiddleware: def test_health_no_auth(self, client): r = client.get("/health") assert r.status_code == 200 def test_static_no_auth(self, client): r = client.get("/static/test.txt") assert r.status_code in (200, 404) # 404 ok if file missing class TestMoodEngineDeep: def test_seed_mood_categories(self, db_module): from app.services.mood_engine import seed_mood_categories from app.models.mood import MoodCategory db = db_module.SessionLocal() seed_mood_categories(db) cats = db.query(MoodCategory).all() assert len(cats) == 10 names = [c.name for c in cats] assert "Happy" in names assert "Sad" in names db.close() def test_seed_idempotent(self, db_module): from app.services.mood_engine import seed_mood_categories from app.models.mood import MoodCategory db = db_module.SessionLocal() seed_mood_categories(db) seed_mood_categories(db) cats = db.query(MoodCategory).all() assert len(cats) == 10 db.close() def test_analyze_song_mood_no_lyrics(self, db_module): from app.services.mood_engine import analyze_song_mood from app.models.song import Song db = db_module.SessionLocal() s = Song(id="s1", title="T", artist="A", file_path="/tmp/t.mp3") db.add(s) db.commit() result = analyze_song_mood("s1", db) assert result["song_id"] == "s1" assert result["top_mood"] is None assert result["confidence"] == 0 db.close() def test_get_mood_playlist_with_songs(self, db_module): from app.services.mood_engine import get_mood_playlist from app.models.song import Song from app.models.mood import MoodSong db = db_module.SessionLocal() s = Song(id="s1", title="T", artist="A", file_path="/tmp/t.mp3") ms = MoodSong(mood_id="happy", song_id="s1", confidence_score=0.9) db.add(s) db.add(ms) db.commit() songs = get_mood_playlist("happy", db, limit=50) assert len(songs) == 1 db.close() class TestLyricsServiceMocked: def test_fetch_lyrics_no_key(self): from app.services.lyrics import fetch_lyrics, GENIUS_API_KEY assert GENIUS_API_KEY == "" assert fetch_lyrics("song", "artist") is None def test_fetch_lyrics_request_error(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests with patch.object(requests, "get", side_effect=requests.RequestException("fail")): result = lyrics_mod.fetch_lyrics("song", "artist") assert result is None lyrics_mod.GENIUS_API_KEY = original_key def test_fetch_lyrics_no_hits(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"response": {"hits": []}} with patch.object(requests, "get", return_value=mock_resp): result = lyrics_mod.fetch_lyrics("song", "artist") assert result is None lyrics_mod.GENIUS_API_KEY = original_key def test_fetch_lyrics_non_200(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests mock_resp = MagicMock() mock_resp.status_code = 401 with patch.object(requests, "get", return_value=mock_resp): result = lyrics_mod.fetch_lyrics("song", "artist") assert result is None lyrics_mod.GENIUS_API_KEY = original_key def test_fetch_lyrics_no_song_id(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"response": {"hits": [{"result": {}}]}} with patch.object(requests, "get", return_value=mock_resp): result = lyrics_mod.fetch_lyrics("song", "artist") assert result is None lyrics_mod.GENIUS_API_KEY = original_key def test_fetch_lyrics_empty_lyrics(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests search_resp = MagicMock() search_resp.status_code = 200 search_resp.json.return_value = {"response": {"hits": [{"result": {"id": 123}}]}} song_resp = MagicMock() song_resp.status_code = 200 song_resp.json.return_value = {"response": {"lyrics": ""}} with patch.object(requests, "get", side_effect=[search_resp, song_resp]): result = lyrics_mod.fetch_lyrics("song", "artist") assert result is None lyrics_mod.GENIUS_API_KEY = original_key def test_fetch_lyrics_not_written_yet(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests search_resp = MagicMock() search_resp.status_code = 200 search_resp.json.return_value = {"response": {"hits": [{"result": {"id": 123}}]}} song_resp = MagicMock() song_resp.status_code = 200 song_resp.json.return_value = {"response": {"lyrics": "[Lyrics are not written yet]"}} with patch.object(requests, "get", side_effect=[search_resp, song_resp]): result = lyrics_mod.fetch_lyrics("song", "artist") assert result is None lyrics_mod.GENIUS_API_KEY = original_key def test_fetch_lyrics_success(self): import app.services.lyrics as lyrics_mod original_key = lyrics_mod.GENIUS_API_KEY lyrics_mod.GENIUS_API_KEY = "fake_key" import requests search_resp = MagicMock() search_resp.status_code = 200 search_resp.json.return_value = {"response": {"hits": [{"result": {"id": 123}}]}} song_resp = MagicMock() song_resp.status_code = 200 song_resp.json.return_value = {"response": {"lyrics": "test lyrics here"}} with patch.object(requests, "get", side_effect=[search_resp, song_resp]): result = lyrics_mod.fetch_lyrics("song", "artist") assert result == "test lyrics here" lyrics_mod.GENIUS_API_KEY = original_key class TestMusicbrainzMocked: def test_search_artist_404(self): import app.services.musicbrainz as mb import requests mock_resp = MagicMock() mock_resp.status_code = 404 with patch.object(requests, "get", return_value=mock_resp): result = mb.search_artist("unknown") assert result is None def test_search_artist_no_results(self): import app.services.musicbrainz as mb import requests mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"artists": []} with patch.object(requests, "get", return_value=mock_resp): result = mb.search_artist("unknown") assert result is None def test_search_artist_success(self): import app.services.musicbrainz as mb import requests mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"artists": [{"id": "a1", "name": "Test Artist"}]} with patch.object(requests, "get", return_value=mock_resp): result = mb.search_artist("Test") assert result["id"] == "a1" assert result["name"] == "Test Artist" def test_get_artist_releases_404(self): import app.services.musicbrainz as mb import requests mock_resp = MagicMock() mock_resp.status_code = 404 with patch.object(requests, "get", return_value=mock_resp): result = mb.get_artist_releases("a1") assert result == [] def test_get_artist_releases_success(self): import app.services.musicbrainz as mb import requests mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = { "releases": [ { "id": "r1", "title": "Album 1", "date": "2025-01-01", "media": [{"tracks": [{"title": "Song 1", "length": 245000}]}] } ] } with patch.object(requests, "get", return_value=mock_resp): result = mb.get_artist_releases("a1") assert len(result) == 1 assert result[0]["title"] == "Album 1" assert len(result[0]["tracks"]) == 1 def test_get_artist_releases_error(self): import app.services.musicbrainz as mb import requests with patch.object(requests, "get", side_effect=requests.RequestException("fail")): result = mb.get_artist_releases("a1") assert result == [] class TestAudioServiceDeep: def test_scan_existing_skipped(self, db_module): from app.services.audio import scan_directory from app.models.song import Song tmpdir = tempfile.mkdtemp() # Create a fake audio file fp = os.path.join(tmpdir, "test.mp3") with open(fp, "wb") as f: f.write(b"fake mp3") db = db_module.SessionLocal() # Pre-add to DB s = Song(id="s1", title="T", artist="A", file_path=fp) db.add(s) db.commit() result = scan_directory(tmpdir, db) assert result.scanned == 1 assert result.added == 0 db.close() os.unlink(fp) os.rmdir(tmpdir) def test_transcode_timeout(self): from app.services.audio import transcode_to_ogg import subprocess with patch("app.services.audio.subprocess.run", side_effect=subprocess.TimeoutExpired("cmd", 1)): result = transcode_to_ogg("/tmp/test.mp3", "/tmp") assert result is None def test_transcode_file_not_found(self): from app.services.audio import transcode_to_ogg with patch("app.services.audio.subprocess.run", side_effect=FileNotFoundError()): result = transcode_to_ogg("/tmp/test.mp3", "/tmp") assert result is None class TestSharePlayManagerDeep: def test_add_to_cue_success(self, db_module): from app.services.shareplay import SharePlayManager from app.models.song import Song db = db_module.SessionLocal() s = Song(id="s1", title="T", artist="A", file_path="/tmp/t.mp3") db.add(s) db.commit() m = SharePlayManager() result = m.add_to_cue(db, "room1", "s1", "alice") assert result is True db.close() def test_next_cue_success(self, db_module): from app.services.shareplay import SharePlayManager from app.models.song import Song from app.models.shareplay import SharePlayCue as CueModel db = db_module.SessionLocal() s = Song(id="s1", title="T", artist="A", file_path="/tmp/t.mp3") cue = CueModel(id="c1", room_id="room1", song_id="s1", position=0, added_by="alice") db.add(s) db.add(cue) db.commit() m = SharePlayManager() result = m.next_cue(db, "room1") assert result == "s1" db.close() def test_get_cue_with_songs(self, db_module): from app.services.shareplay import SharePlayManager from app.models.song import Song from app.models.shareplay import SharePlayCue as CueModel db = db_module.SessionLocal() s = Song(id="s1", title="Test Song", artist="Artist A", file_path="/tmp/t.mp3") cue = CueModel(id="c1", room_id="room1", song_id="s1", position=0, added_by="alice") db.add(s) db.add(cue) db.commit() m = SharePlayManager() result = m.get_cue(db, "room1") assert len(result) == 1 assert result[0]["song"]["title"] == "Test Song" assert result[0]["added_by"] == "alice" db.close() class TestMoodKeywords: def test_all_moods_have_15_keywords(self): from app.services.mood_engine import MOOD_KEYWORDS for mood, keywords in MOOD_KEYWORDS.items(): assert len(keywords) == 15, f"{mood} has {len(keywords)} keywords" def test_all_weights_positive(self): from app.services.mood_engine import MOOD_KEYWORDS for mood, keywords in MOOD_KEYWORDS.items(): for word, weight in keywords: assert weight > 0, f"{mood}/{word} has non-positive weight" def test_keywords_are_words(self): from app.services.mood_engine import MOOD_KEYWORDS for mood, keywords in MOOD_KEYWORDS.items(): for word, weight in keywords: assert " " not in word, f"{mood}/{word} contains space" assert word == word.lower(), f"{mood}/{word} not lowercase" class TestDefaultChannels: def test_default_channels_count(self): from app.routers.lofi import DEFAULT_CHANNELS assert len(DEFAULT_CHANNELS) == 3 def test_default_channels_have_fields(self): from app.routers.lofi import DEFAULT_CHANNELS for ch in DEFAULT_CHANNELS: assert "id" in ch assert "name" in ch assert "stream_url" in ch assert ch.get("is_active") is True class TestPlaceholderEvents: def test_placeholder_events_count(self): from app.routers.events import PLACEHOLDER_EVENTS assert len(PLACEHOLDER_EVENTS) >= 3 def test_placeholder_events_have_fields(self): from app.routers.events import PLACEHOLDER_EVENTS for e in PLACEHOLDER_EVENTS: assert "id" in e assert "name" in e assert "date" in e class TestRadioBrowserFormatEdge: def test_format_station_none_geo(self): from app.services.radio_browser import _format_station s = { "stationuuid": "u1", "name": "Test", "codec": "mp3", "url_resolved": "http://s.com", "geo_lat": None, "geo_long": None, "tag": "", "countryname": "", "language": "", "bitrate": None, "votes": None, } result = _format_station(s) assert result["location_lat"] is None assert result["bitrate"] is None assert result["votes"] == 0 def test_format_station_empty_tags(self): from app.services.radio_browser import _format_station s = { "stationuuid": "u1", "name": "T", "codec": "mp3", "url_resolved": "http://s.com", "geo_lat": None, "geo_long": None, "tag": None, "countryname": "", "language": "", "bitrate": None, "votes": None, } result = _format_station(s) assert result["tags"] == [] class TestHaversineEdgeCases: def test_poles(self): from app.services.radio_browser import _haversine d = _haversine(90, 0, -90, 0) assert 19000 < d < 21000 # ~20000km pole to pole def test_equator(self): from app.services.radio_browser import _haversine d = _haversine(0, 0, 0, 180) assert 19000 < d < 21000 # ~20000km equator half-circle class TestDurationFormat: def test_zero(self): from app.services.musicbrainz import _format_duration assert _format_duration(0) == "0:00" def test_under_minute(self): from app.services.musicbrainz import _format_duration assert _format_duration(45) == "0:45" def test_exact_minute(self): from app.services.musicbrainz import _format_duration assert _format_duration(60) == "1:00" def test_over_hour(self): from app.services.musicbrainz import _format_duration assert _format_duration(3725) == "62:05" class TestScanResultSchema: def test_scan_result_defaults(self): from app.schemas.song import ScanResult sr = ScanResult(scanned=0, added=0, skipped=0, errors=[]) assert sr.errors == [] def test_scan_result_with_errors(self): from app.schemas.song import ScanResult sr = ScanResult(scanned=5, added=3, skipped=1, errors=["err1", "err2"]) assert len(sr.errors) == 2 class TestUserSettingsDefaults: def test_all_defaults(self): from app.schemas.settings import UserSettingsResponse us = UserSettingsResponse() assert us.audio_quality == "high" assert us.theme == "dark" assert us.scan_directories == [] assert us.user_name == "User" assert us.user_avatar is None assert us.auto_transcode is True assert us.default_mood is None