mirror of
https://github.com/bnair123/MusicAnalyser.git
synced 2026-02-25 11:46:07 +00:00
Major changes: - Add skip tracking: poll currently-playing every 15s, detect skips (<30s listened) - Add listening-log and sessions API endpoints - Fix ReccoBeats client to extract spotify_id from href response - Compress heatmap from 24 hours to 6 x 4-hour blocks - Add OpenAI support in narrative service (use max_completion_tokens for new models) - Add ListeningLog component with timeline and list views - Update all frontend components to use real data (album art, play counts) - Add docker-compose external network (dockernet) support - Add comprehensive documentation (API, DATA_MODEL, ARCHITECTURE, FRONTEND) - Add unit tests for ingest and API endpoints
114 lines
3.7 KiB
Python
114 lines
3.7 KiB
Python
import pytest
|
|
from unittest.mock import MagicMock, AsyncMock, patch
|
|
from datetime import datetime, timedelta
|
|
|
|
from app.ingest import PlaybackTracker, finalize_track
|
|
|
|
|
|
class TestPlaybackTracker:
|
|
def test_initial_state(self):
|
|
tracker = PlaybackTracker()
|
|
assert tracker.current_track_id is None
|
|
assert tracker.track_start_time is None
|
|
assert tracker.accumulated_listen_ms == 0
|
|
assert tracker.last_progress_ms == 0
|
|
assert tracker.is_paused is False
|
|
|
|
|
|
class TestFinalizeTrack:
|
|
def test_finalize_creates_play_history_when_not_exists(self):
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
tracker = PlaybackTracker()
|
|
tracker.current_track_id = "track123"
|
|
tracker.track_start_time = datetime(2024, 1, 1, 10, 0, 0)
|
|
tracker.accumulated_listen_ms = 60000
|
|
|
|
finalize_track(mock_db, tracker)
|
|
|
|
mock_db.add.assert_called_once()
|
|
mock_db.commit.assert_called_once()
|
|
|
|
assert tracker.current_track_id is None
|
|
assert tracker.accumulated_listen_ms == 0
|
|
|
|
def test_finalize_marks_skip_when_under_30s(self):
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
tracker = PlaybackTracker()
|
|
tracker.current_track_id = "track123"
|
|
tracker.track_start_time = datetime(2024, 1, 1, 10, 0, 0)
|
|
tracker.accumulated_listen_ms = 15000
|
|
|
|
finalize_track(mock_db, tracker)
|
|
|
|
call_args = mock_db.add.call_args[0][0]
|
|
assert call_args.skipped is True
|
|
|
|
def test_finalize_updates_existing_play(self):
|
|
mock_existing = MagicMock()
|
|
mock_existing.listened_ms = None
|
|
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.filter.return_value.first.return_value = (
|
|
mock_existing
|
|
)
|
|
|
|
tracker = PlaybackTracker()
|
|
tracker.current_track_id = "track123"
|
|
tracker.track_start_time = datetime(2024, 1, 1, 10, 0, 0)
|
|
tracker.accumulated_listen_ms = 120000
|
|
|
|
finalize_track(mock_db, tracker)
|
|
|
|
assert mock_existing.listened_ms == 120000
|
|
assert mock_existing.skipped is False
|
|
mock_db.commit.assert_called_once()
|
|
|
|
|
|
class TestReccoBeatsClient:
|
|
@pytest.mark.asyncio
|
|
async def test_extracts_spotify_id_from_href(self):
|
|
from app.services.reccobeats_client import ReccoBeatsClient
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"content": [
|
|
{
|
|
"id": "uuid-here",
|
|
"href": "https://open.spotify.com/track/abc123xyz",
|
|
"energy": 0.8,
|
|
"valence": 0.6,
|
|
}
|
|
]
|
|
}
|
|
|
|
mock_client.return_value.__aenter__.return_value.get = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
|
|
client = ReccoBeatsClient()
|
|
result = await client.get_audio_features(["abc123xyz"])
|
|
|
|
assert len(result) == 1
|
|
assert result[0]["spotify_id"] == "abc123xyz"
|
|
assert result[0]["energy"] == 0.8
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_returns_empty_on_error(self):
|
|
from app.services.reccobeats_client import ReccoBeatsClient
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__.return_value.get = AsyncMock(
|
|
side_effect=Exception("Network error")
|
|
)
|
|
|
|
client = ReccoBeatsClient()
|
|
result = await client.get_audio_features(["test123"])
|
|
|
|
assert result == []
|