Files
Tunetti/tests/test_music_db.py
NikkeDoy 93f5239ef7
Some checks failed
SonarQube Code Quality Scan / SonarQube Scan (push) Failing after 2m37s
| Add tests
2026-06-03 18:53:24 +03:00

247 lines
9.2 KiB
Python

"""Tests for the music database module (music_db.py)."""
import json
import os
import tempfile
import pytest
# We'll create a fresh MusicDB for each test using a temporary file.
@pytest.fixture
def music_db():
"""Yield a MusicDB backed by a temporary SQLite file."""
from music_db import MusicDB
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
tmp.close()
db = MusicDB(tmp.name)
yield db
db.close()
os.unlink(tmp.name)
# ── Helper ────────────────────────────────────────────────────────────────────
_SONG = dict(
video_id="test_video_001",
title="Test Song",
artists=[{"name": "Test Artist", "id": "artist_001"}],
album={"name": "Test Album", "id": "album_001"},
duration=240,
thumbnail="https://example.com/thumb.jpg",
)
def _record_play(db, **overrides):
kwargs = dict(_SONG)
kwargs.update(overrides)
db.record_play(**kwargs)
# ── Tests ─────────────────────────────────────────────────────────────────────
class TestRecordPlay:
def test_records_play_and_history(self, music_db):
"""record_play creates a song entry and a history row."""
_record_play(music_db)
stats = music_db.get_stats()
assert stats["unique_songs"] == 1
assert stats["total_plays"] == 1
history = music_db.get_history(limit=10)
assert len(history) == 1
assert history[0]["video_id"] == _SONG["video_id"]
def test_increments_play_count(self, music_db):
"""Playing the same song multiple times increments play_count."""
_record_play(music_db)
_record_play(music_db)
_record_play(music_db)
stats = music_db.get_stats()
assert stats["unique_songs"] == 1
assert stats["total_plays"] == 3
def test_updates_last_played(self, music_db):
"""Re-playing a song updates last_played timestamp."""
_record_play(music_db)
import time
time.sleep(0.01)
_record_play(music_db)
history = music_db.get_history(limit=10)
# The latest play should be the one returned
assert history[0]["video_id"] == _SONG["video_id"]
def test_without_album(self, music_db):
"""record_play works without album data."""
_record_play(music_db, album=None)
stats = music_db.get_stats()
assert stats["unique_songs"] == 1
def test_without_thumbnail(self, music_db):
"""record_play works without thumbnail."""
_record_play(music_db, thumbnail=None)
stats = music_db.get_stats()
assert stats["unique_songs"] == 1
class TestFavourites:
def test_toggle_favourite(self, music_db):
"""toggle_favourite flips the favourite flag."""
_record_play(music_db)
# Start unfavoured
assert music_db.is_favourite(_SONG["video_id"]) is False
# Toggle on
assert music_db.toggle_favourite(_SONG["video_id"]) is True
assert music_db.is_favourite(_SONG["video_id"]) is True
# Toggle off
assert music_db.toggle_favourite(_SONG["video_id"]) is False
assert music_db.is_favourite(_SONG["video_id"]) is False
def test_toggle_favourite_nonexistent_song(self, music_db):
"""toggle_favourite on a non-existent song returns False."""
assert music_db.toggle_favourite("nonexistent") is False
def test_set_favourite(self, music_db):
"""set_favourite explicitly sets the favourite state."""
_record_play(music_db)
vid = _SONG["video_id"]
assert music_db.set_favourite(vid, True) is True
assert music_db.is_favourite(vid) is True
assert music_db.set_favourite(vid, False) is True
assert music_db.is_favourite(vid) is False
def test_set_favourite_nonexistent(self, music_db):
"""set_favourite on a non-existent song returns False."""
assert music_db.set_favourite("nonexistent", True) is False
def test_get_favourites_returns_favourited_songs(self, music_db):
"""get_favourites returns only favourited songs."""
# Record two songs, favourite one
_record_play(music_db, video_id="song_a", title="A")
_record_play(music_db, video_id="song_b", title="B")
music_db.toggle_favourite("song_a")
favs = music_db.get_favourites()
assert len(favs) == 1
assert favs[0]["video_id"] == "song_a"
class TestHistory:
def test_history_ordered_by_recent(self, music_db):
"""get_history returns songs ordered by most recent play."""
_record_play(music_db, video_id="song_a", title="A")
import time
time.sleep(0.01)
_record_play(music_db, video_id="song_b", title="B")
history = music_db.get_history(limit=10)
# Most recent (song_b) should be first
assert history[0]["video_id"] == "song_b"
assert history[1]["video_id"] == "song_a"
def test_history_deduplicates_by_video_id(self, music_db):
"""get_history shows each song at most once (most recent play)."""
_record_play(music_db, video_id="song_a", title="A")
_record_play(music_db, video_id="song_b", title="B")
_record_play(music_db, video_id="song_a", title="A")
history = music_db.get_history(limit=10)
# song_a appears once, but song_b is in there too
assert len(history) == 2
# song_a is most recent since it was played last
assert history[0]["video_id"] == "song_a"
def test_history_respects_limit(self, music_db):
"""get_history respects the limit parameter."""
for i in range(5):
_record_play(music_db, video_id=f"song_{i}", title=f"S{i}")
history = music_db.get_history(limit=3)
assert len(history) == 3
def test_empty_history(self, music_db):
"""get_history on empty DB returns empty list."""
assert music_db.get_history() == []
class TestStats:
def test_get_stats_empty(self, music_db):
"""get_stats on empty DB returns zero counts."""
stats = music_db.get_stats()
assert stats["unique_songs"] == 0
assert stats["total_plays"] is None or stats["total_plays"] == 0
assert stats["favourites"] == 0
assert stats["top_played"] == []
assert stats["recent"] == []
assert stats["plays_by_day"] == []
def test_get_stats_with_data(self, music_db):
"""get_stats returns accurate aggregate data."""
_record_play(music_db, video_id="song_a", title="A", duration=120)
_record_play(music_db, video_id="song_b", title="B", duration=180)
_record_play(music_db, video_id="song_a", title="A", duration=120)
music_db.toggle_favourite("song_b")
stats = music_db.get_stats()
assert stats["unique_songs"] == 2
assert stats["total_plays"] == 3
assert stats["favourites"] == 1
assert stats["top_played"][0]["video_id"] == "song_a"
assert stats["top_played"][0]["play_count"] == 2
assert len(stats["recent"]) == 3
assert len(stats["plays_by_day"]) >= 1 # today at least
def test_top_played_limit_returns_up_to_ten(self, music_db):
"""top_played in stats returns at most 10 entries."""
for i in range(15):
_record_play(music_db, video_id=f"song_{i}", title=f"S{i}")
for _ in range(i + 1):
_record_play(music_db, video_id=f"song_{i}", title=f"S{i}")
stats = music_db.get_stats()
assert len(stats["top_played"]) == 10
class TestEdgeCases:
def test_unicode_song_title(self, music_db):
"""record_play handles Unicode characters in titles and artists."""
_record_play(music_db, title="🎵 Джаз", artists=[{"name": "José", "id": "j"}])
history = music_db.get_history(limit=10)
assert history[0]["title"] == "🎵 Джаз"
def test_empty_artists_list(self, music_db):
"""record_play works with empty artists list."""
_record_play(music_db, artists=[])
stats = music_db.get_stats()
assert stats["unique_songs"] == 1
def test_very_long_title(self, music_db):
"""record_play handles very long titles."""
long_title = "A" * 1000
_record_play(music_db, title=long_title)
history = music_db.get_history(limit=10)
assert history[0]["title"] == long_title
def test_concurrent_access(self, music_db):
"""Multiple rapid calls don't cause errors."""
import threading
errors = []
def worker(vid):
try:
_record_play(music_db, video_id=vid, title=f"S{vid}")
music_db.toggle_favourite(vid)
music_db.is_favourite(vid)
music_db.get_stats()
except Exception as e:
errors.append(e)
threads = [threading.Thread(target=worker, args=(f"vid_{i}",)) for i in range(20)]
for t in threads:
t.start()
for t in threads:
t.join(timeout=5)
assert not errors, f"Concurrent errors: {errors}"
stats = music_db.get_stats()
assert stats["unique_songs"] == 20