mirror of https://github.com/Nezreka/SoulSync.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
6.4 KiB
182 lines
6.4 KiB
"""Boundary tests for `core.personalized.api` handler functions.
|
|
|
|
These are pure-function dispatchers — they take a manager + ids,
|
|
return a JSON-serializable dict. No Flask required, no real DB.
|
|
The Flask wiring in `web_server.py` adds `jsonify` + URL routing
|
|
on top.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
from types import SimpleNamespace
|
|
from typing import Any, List
|
|
|
|
import pytest
|
|
|
|
from core.personalized import api as _api
|
|
from core.personalized.manager import PersonalizedPlaylistManager
|
|
from core.personalized.specs import PlaylistKindRegistry, PlaylistKindSpec
|
|
from core.personalized.types import PlaylistConfig, Track
|
|
from database.personalized_schema import ensure_personalized_schema
|
|
|
|
|
|
class _FakeDB:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def _get_connection(self):
|
|
c = sqlite3.connect(self.path)
|
|
c.row_factory = sqlite3.Row
|
|
return c
|
|
|
|
|
|
@pytest.fixture
|
|
def db(tmp_path):
|
|
p = str(tmp_path / 't.db')
|
|
conn = sqlite3.connect(p)
|
|
ensure_personalized_schema(conn)
|
|
conn.commit()
|
|
conn.close()
|
|
return _FakeDB(p)
|
|
|
|
|
|
@pytest.fixture
|
|
def registry():
|
|
return PlaylistKindRegistry()
|
|
|
|
|
|
@pytest.fixture
|
|
def manager(db, registry):
|
|
return PersonalizedPlaylistManager(db, deps=None, registry=registry)
|
|
|
|
|
|
def _register(reg, kind='hidden_gems', requires_variant=False, generator=None):
|
|
spec = PlaylistKindSpec(
|
|
kind=kind, name_template=kind.replace('_', ' ').title(),
|
|
description=f'Description for {kind}',
|
|
default_config=PlaylistConfig(limit=20),
|
|
generator=generator or (lambda *a, **k: []),
|
|
requires_variant=requires_variant,
|
|
tags=['test'],
|
|
)
|
|
reg.register(spec)
|
|
return spec
|
|
|
|
|
|
# ─── list_kinds ──────────────────────────────────────────────────────
|
|
|
|
|
|
class TestListKinds:
|
|
def test_lists_every_registered_kind(self, registry):
|
|
_register(registry, kind='hidden_gems')
|
|
_register(registry, kind='time_machine', requires_variant=True)
|
|
out = _api.list_kinds(registry)
|
|
assert out['success'] is True
|
|
kinds = {k['kind'] for k in out['kinds']}
|
|
assert kinds == {'hidden_gems', 'time_machine'}
|
|
|
|
def test_kind_metadata_shape(self, registry):
|
|
_register(registry, kind='hidden_gems')
|
|
out = _api.list_kinds(registry)
|
|
kind = out['kinds'][0]
|
|
assert kind['kind'] == 'hidden_gems'
|
|
assert kind['requires_variant'] is False
|
|
assert kind['tags'] == ['test']
|
|
assert kind['default_config']['limit'] == 20
|
|
|
|
def test_empty_registry(self):
|
|
out = _api.list_kinds(PlaylistKindRegistry())
|
|
assert out == {'success': True, 'kinds': []}
|
|
|
|
|
|
# ─── list_playlists ─────────────────────────────────────────────────
|
|
|
|
|
|
class TestListPlaylists:
|
|
def test_returns_empty_when_no_playlists(self, manager, registry):
|
|
_register(registry)
|
|
out = _api.list_playlists(manager, profile_id=1)
|
|
assert out == {'success': True, 'playlists': []}
|
|
|
|
def test_serializes_playlist_record(self, manager, registry):
|
|
_register(registry)
|
|
manager.ensure_playlist('hidden_gems', '', 1)
|
|
out = _api.list_playlists(manager, profile_id=1)
|
|
assert out['success'] is True
|
|
assert len(out['playlists']) == 1
|
|
pl = out['playlists'][0]
|
|
assert pl['kind'] == 'hidden_gems'
|
|
assert pl['variant'] == ''
|
|
assert pl['name'] == 'Hidden Gems'
|
|
assert pl['track_count'] == 0
|
|
assert pl['config']['limit'] == 20
|
|
|
|
|
|
# ─── get_playlist_with_tracks ───────────────────────────────────────
|
|
|
|
|
|
class TestGetPlaylistWithTracks:
|
|
def test_auto_creates_on_first_get(self, manager, registry):
|
|
_register(registry)
|
|
out = _api.get_playlist_with_tracks(manager, 'hidden_gems', '', 1)
|
|
assert out['success'] is True
|
|
assert out['playlist']['kind'] == 'hidden_gems'
|
|
assert out['tracks'] == []
|
|
|
|
def test_returns_persisted_tracks(self, manager, registry):
|
|
gen_calls = []
|
|
|
|
def gen(deps, variant, config):
|
|
gen_calls.append(1)
|
|
return [Track(track_name='X', artist_name='Y', spotify_track_id='sp-1')]
|
|
|
|
_register(registry, generator=gen)
|
|
manager.refresh_playlist('hidden_gems', '', 1)
|
|
out = _api.get_playlist_with_tracks(manager, 'hidden_gems', '', 1)
|
|
assert len(out['tracks']) == 1
|
|
assert out['tracks'][0]['track_name'] == 'X'
|
|
assert out['tracks'][0]['spotify_track_id'] == 'sp-1'
|
|
|
|
def test_unknown_kind_raises_value_error(self, manager):
|
|
with pytest.raises(ValueError):
|
|
_api.get_playlist_with_tracks(manager, 'nope', '', 1)
|
|
|
|
|
|
# ─── refresh_playlist ───────────────────────────────────────────────
|
|
|
|
|
|
class TestRefreshPlaylist:
|
|
def test_refresh_runs_generator_and_returns_tracks(self, manager, registry):
|
|
_register(registry, generator=lambda *a, **k: [
|
|
Track(track_name='T1', artist_name='A', spotify_track_id='1'),
|
|
Track(track_name='T2', artist_name='B', spotify_track_id='2'),
|
|
])
|
|
out = _api.refresh_playlist(manager, 'hidden_gems', '', 1)
|
|
assert out['success'] is True
|
|
assert len(out['tracks']) == 2
|
|
assert out['playlist']['track_count'] == 2
|
|
assert out['playlist']['last_generated_at'] is not None
|
|
|
|
def test_config_overrides_passed_through(self, manager, registry):
|
|
captured = {}
|
|
|
|
def gen(deps, variant, config):
|
|
captured['limit'] = config.limit
|
|
return []
|
|
|
|
_register(registry, generator=gen)
|
|
_api.refresh_playlist(manager, 'hidden_gems', '', 1, config_overrides={'limit': 99})
|
|
assert captured['limit'] == 99
|
|
|
|
|
|
# ─── update_config ──────────────────────────────────────────────────
|
|
|
|
|
|
class TestUpdateConfig:
|
|
def test_patches_config(self, manager, registry):
|
|
_register(registry)
|
|
out = _api.update_config(manager, 'hidden_gems', '', 1, {'limit': 75})
|
|
assert out['success'] is True
|
|
assert out['playlist']['config']['limit'] == 75
|