music-app/backend/tests/test_max_coverage.py
Jarian Cottingham 24c03426f8 test: add comprehensive test suite with 94% coverage
- Add pytest config with 90% coverage threshold
- 15 test files covering all routers, services, schemas, models
- 300 tests: unit tests, integration tests, edge cases, mocked external APIs
- Update CI workflow to run pytest with coverage enforcement
- Mock external services (Genius, MusicBrainz, RadioBrowser)
- In-memory SQLite DB per test via conftest fixtures
2026-07-06 06:07:31 +00:00

295 lines
9.4 KiB
Python

"""Tests to push coverage toward 90%."""
import tempfile
import os
class TestSongUpload:
def test_upload_unsupported(self, client):
r = client.post(
"/api/songs/upload",
files={"file": ("test.txt", b"not audio", "text/plain")},
)
assert r.status_code == 400
assert "Unsupported format" in r.json()["detail"]
class TestReleasesWithMock:
pass
def test_add_release_artist_not_found(self, client, db_module):
from app.models.playlist import Playlist
db = db_module.SessionLocal()
p = Playlist(id="p1", name="Test")
db.add(p)
db.commit()
db.close()
r = client.post("/api/releases/add", params={
"artist": "UnknownXYZ",
"album_id": "a1",
"playlist_id": "p1"
})
assert r.status_code == 200
assert r.json().get("error") == "Artist not found"
class TestReleaseRouterWithMock:
def test_check_artist_releases_mocked(self, client, db_module):
import app.routers.releases as rel_mod
original = rel_mod.search_artist
rel_mod.search_artist = lambda x: {"id": "a1", "name": x, "releases": []}
try:
r = client.get("/api/releases/TestArtist")
assert r.status_code == 200
finally:
rel_mod.search_artist = original
class TestImportBulk:
def test_bulk_import_unsupported_formats(self, client):
r = client.post(
"/api/import/bulk",
files=[("files", ("a.txt", b"x", "text/plain")),
("files", ("b.jpg", b"x", "image/jpeg"))],
)
assert r.status_code == 200
data = r.json()
assert data["scanned"] == 0
assert len(data["errors"]) == 2
class TestMoodAnalyzeAll:
def test_analyze_all_empty(self, client):
r = client.post("/api/mood/analyze")
assert r.status_code == 200
assert r.json()["analyzed"] == 0
class TestRadioCurrent:
def test_current_playing_defaults(self, client):
r = client.get("/api/radio/current")
assert r.status_code == 200
data = r.json()
assert data["station"] is None
assert data["song_name"] is None
assert data["artist_name"] is None
assert data["is_playing"] is False
class TestSettingsAutoTranscode:
def test_auto_transcode_false(self, client):
client.put("/api/settings", json={"auto_transcode": False})
r = client.get("/api/settings")
assert r.json()["auto_transcode"] is False
class TestSongPagination:
def test_songs_total_pages(self, client, db_module):
from app.models.song import Song
db = db_module.SessionLocal()
for i in range(5):
s = Song(id=f"s{i}", title=f"T{i}", artist="A", file_path=f"/tmp/{i}.mp3")
db.add(s)
db.commit()
db.close()
r = client.get("/api/songs?per_page=2")
assert r.status_code == 200
data = r.json()
assert data["total"] == 5
assert data["total_pages"] == 3
assert len(data["items"]) == 2
class TestPlaylistWithSongs:
def test_playlist_songs_ordered(self, client, db_module):
from app.models.song import Song
from app.models.playlist import Playlist, PlaylistSong
db = db_module.SessionLocal()
p = Playlist(id="p1", name="Test")
s1 = Song(id="s1", title="First", artist="A", file_path="/tmp/1.mp3")
s2 = Song(id="s2", title="Second", artist="A", file_path="/tmp/2.mp3")
ps1 = PlaylistSong(playlist_id="p1", song_id="s2", position=0)
ps2 = PlaylistSong(playlist_id="p1", song_id="s1", position=1)
db.add(p)
db.add(s1)
db.add(s2)
db.add(ps1)
db.add(ps2)
db.commit()
db.close()
r = client.get("/api/playlists/p1")
assert r.status_code == 200
data = r.json()
assert len(data["songs"]) == 2
assert data["songs"][0]["title"] == "Second" # position 0
assert data["songs"][1]["title"] == "First" # position 1
assert data["song_count"] == 2
class TestSearchMultipleFields:
def test_search_by_artist(self, client, db_module):
from app.models.song import Song
db = db_module.SessionLocal()
s = Song(id="s1", title="Song", artist="UniqueArtist", file_path="/tmp/t.mp3")
db.add(s)
db.commit()
db.close()
r = client.get("/api/search?q=UniqueArtist")
assert r.status_code == 200
assert len(r.json()["songs"]) == 1
def test_search_by_genre(self, client, db_module):
from app.models.song import Song
db = db_module.SessionLocal()
s = Song(id="s1", title="Song", artist="A", genre="UniqueGenre", file_path="/tmp/t.mp3")
db.add(s)
db.commit()
db.close()
r = client.get("/api/search?q=UniqueGenre")
assert r.status_code == 200
assert len(r.json()["songs"]) == 1
class TestReleaseCheckAllArtists:
def test_check_releases_no_artists(self, client):
r = client.get("/api/releases")
assert r.status_code == 200
assert isinstance(r.json(), list)
class TestLofiSeed:
def test_seed_channels_called(self, client):
# Default channels are seeded on startup
r = client.get("/api/lofi/channels")
assert r.status_code == 200
assert len(r.json()) >= 3
class TestSharePlayControlTypes:
def test_control_volume(self, client, db_module):
from app.routers.shareplay import manager
db = db_module.SessionLocal()
room = manager.create_room(db, creator="user")
db.close()
r = client.post("/api/shareplay/control", json={
"room_id": room["id"], "type": "unknown", "payload": None
})
assert r.status_code == 200
class TestHealthEndpoint:
def test_health_returns_ok(self, client):
r = client.get("/health")
assert r.status_code == 200
assert r.json() == {"status": "ok"}
class TestNotFoundRoutes:
def test_404_unknown_route(self, client):
r = client.get("/api/nonexistent/route")
assert r.status_code == 404
def test_405_wrong_method(self, client):
r = client.post("/health")
assert r.status_code == 405
class TestCORSHeaders:
pass
class TestAccountStatsWithGenres:
def test_stats_empty(self, client):
r = client.get("/api/account/stats")
assert r.status_code == 200
data = r.json()
assert data["total_listening_time"] == 0
assert data["top_artists"] == []
assert data["top_genres"] == []
assert data["top_moods"] == []
class TestMoodSetExistingSetting:
def test_set_mood_updates_existing(self, client, db_module):
from app.models.settings import UserSetting
db = db_module.SessionLocal()
s = UserSetting(key="default_mood", value="OldMood")
db.add(s)
db.commit()
db.close()
r = client.post("/api/mood/set", json={"mood": "NewMood"})
assert r.status_code == 200
db = db_module.SessionLocal()
s = db.query(UserSetting).filter(UserSetting.key == "default_mood").first()
assert s.value == "NewMood"
db.close()
class TestPlaylistUpdateFields:
def test_update_description(self, client, db_module):
from app.models.playlist import Playlist
db = db_module.SessionLocal()
p = Playlist(id="p1", name="Test")
db.add(p)
db.commit()
db.close()
r = client.put("/api/playlists/p1", json={
"name": "Test", "description": "New desc"
})
assert r.status_code == 200
assert r.json()["description"] == "New desc"
class TestRadioStationsParams:
def test_stations_with_genre(self, client):
r = client.get("/api/radio/stations?genre=jazz&limit=10")
assert r.status_code == 200
def test_nearby_radius(self, client):
r = client.get("/api/radio/nearby?lat=0&lon=0&radius=500")
assert r.status_code == 200
class TestScanSongsWithDir:
def test_scan_specific_dir(self, client):
tmpdir = tempfile.mkdtemp()
r = client.post("/api/songs/scan", params={"directory": tmpdir})
assert r.status_code == 200
assert r.json()["scanned"] == 0
os.rmdir(tmpdir)
class TestDeleteSongCascade:
def test_delete_song_removes_from_db(self, client, db_module):
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()
db.close()
r = client.delete("/api/songs/s1")
assert r.status_code == 200
r = client.get("/api/songs/s1")
assert r.status_code == 404
class TestServerConfig:
def test_server_config_defaults(self):
from app.schemas.settings import ServerConfigResponse
sc = ServerConfigResponse(id="0", path="/music", name="Music")
assert sc.last_scanned is None
assert sc.song_count == 0
class TestReleaseTrackSchema:
def test_release_track(self):
from app.schemas.releases import ReleaseTrack
t = ReleaseTrack(title="T", duration=0, duration_formatted="0:00")
assert t.duration == 0
class TestSharePlayCommandSchema:
def test_command_no_payload(self):
from app.schemas.shareplay import SharePlayCommand
cmd = SharePlayCommand(type="play")
assert cmd.payload is None