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.
137 lines
4.6 KiB
137 lines
4.6 KiB
"""Tests for `worker_utils.set_album_api_track_count` — the shared helper
|
|
enrichment workers call to cache authoritative track counts."""
|
|
|
|
import sqlite3
|
|
|
|
from core.worker_utils import set_album_api_track_count
|
|
|
|
|
|
class _RecordingCursor:
|
|
"""Minimal cursor stand-in that captures execute() calls."""
|
|
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def execute(self, query, params=None):
|
|
self.calls.append((query, params))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Happy-path writes
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_writes_positive_int_count():
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-1", 12)
|
|
assert len(cursor.calls) == 1
|
|
query, params = cursor.calls[0]
|
|
assert "UPDATE albums SET api_track_count = ?" in query
|
|
assert "WHERE id = ?" in query
|
|
assert params == (12, "album-1")
|
|
|
|
|
|
def test_coerces_numeric_string_to_int():
|
|
"""Deezer / raw API dicts often have track counts as strings."""
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-2", "14")
|
|
assert cursor.calls[0][1] == (14, "album-2")
|
|
|
|
|
|
def test_writes_one_for_single_track_album():
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-single", 1)
|
|
assert cursor.calls[0][1] == (1, "album-single")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Skip-write cases (don't overwrite good values with bad ones)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_skips_write_when_count_is_zero():
|
|
"""A source that doesn't report track counts must not clobber a value
|
|
written by another source."""
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-x", 0)
|
|
assert cursor.calls == []
|
|
|
|
|
|
def test_skips_write_when_count_is_none():
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-x", None)
|
|
assert cursor.calls == []
|
|
|
|
|
|
def test_skips_write_when_count_is_negative():
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-x", -1)
|
|
assert cursor.calls == []
|
|
|
|
|
|
def test_skips_write_on_non_numeric_string():
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-x", "not a number")
|
|
assert cursor.calls == []
|
|
|
|
|
|
def test_skips_write_on_non_numeric_object():
|
|
cursor = _RecordingCursor()
|
|
set_album_api_track_count(cursor, "album-x", object())
|
|
assert cursor.calls == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Does not commit — caller owns the transaction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_helper_does_not_commit():
|
|
"""Workers batch multiple UPDATEs into one transaction. The helper
|
|
must not call commit() or it would break that batching."""
|
|
|
|
class _StrictCursor(_RecordingCursor):
|
|
commits = 0
|
|
|
|
def commit(self): # pragma: no cover — asserts it's never called
|
|
_StrictCursor.commits += 1
|
|
|
|
cursor = _StrictCursor()
|
|
set_album_api_track_count(cursor, "album-y", 5)
|
|
assert _StrictCursor.commits == 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Error isolation — a cursor.execute failure must not poison the worker's
|
|
# other UPDATEs in the same transaction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_swallows_cursor_execute_errors():
|
|
"""If the column doesn't exist yet (e.g., migration hasn't run) or
|
|
the DB is otherwise unhappy, the helper must not propagate the error.
|
|
Otherwise the worker's other UPDATEs (spotify_album_id, thumb_url,
|
|
etc.) batched in the same transaction would roll back."""
|
|
|
|
class _BrokenCursor:
|
|
def execute(self, query, params=None):
|
|
raise RuntimeError("no such column: api_track_count")
|
|
|
|
cursor = _BrokenCursor()
|
|
# Should not raise.
|
|
set_album_api_track_count(cursor, "album-z", 10)
|
|
|
|
|
|
def test_repairs_missing_api_track_count_column():
|
|
conn = sqlite3.connect(":memory:")
|
|
cursor = conn.cursor()
|
|
cursor.execute("CREATE TABLE albums (id TEXT PRIMARY KEY, title TEXT)")
|
|
cursor.execute("INSERT INTO albums (id, title) VALUES ('album-z', 'Album')")
|
|
|
|
set_album_api_track_count(cursor, "album-z", 10)
|
|
|
|
cursor.execute("PRAGMA table_info(albums)")
|
|
cols = {row[1] for row in cursor.fetchall()}
|
|
cursor.execute("SELECT api_track_count FROM albums WHERE id = 'album-z'")
|
|
row = cursor.fetchone()
|
|
conn.close()
|
|
|
|
assert 'api_track_count' in cols
|
|
assert row[0] == 10
|