mirror of
https://github.com/bnair123/MusicAnalyser.git
synced 2026-02-25 11:46:07 +00:00
Add skip tracking, compressed heatmap, listening log, docs, tests, and OpenAI support
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
This commit is contained in:
113
backend/tests/test_ingest.py
Normal file
113
backend/tests/test_ingest.py
Normal file
@@ -0,0 +1,113 @@
|
||||
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 == []
|
||||
Reference in New Issue
Block a user