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.
119 lines
4.5 KiB
119 lines
4.5 KiB
"""Pin the ``/api/import/album/match`` endpoint's source-routing
|
|
behavior — github issue #524 regression guard.
|
|
|
|
The bug: clicking an album in the import page POSTed only ``album_id``,
|
|
dropping the ``source`` field that the backend needs to route the
|
|
lookup to the correct metadata client. The backend silently fell back
|
|
to its primary-source-priority chain, which fails for cross-source
|
|
album_ids (Deezer numeric id vs Spotify primary, etc.) → broken
|
|
fallback dict written to the library DB.
|
|
|
|
The frontend fix populates source on every match POST. These tests
|
|
pin the BACKEND defense: when source is dropped (curl, third-party,
|
|
regression in another caller), a clear warning lands in the logs so
|
|
the regression is grep-able instead of silent.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def import_match_client(monkeypatch):
|
|
"""Flask test client, with the album-match payload builder mocked
|
|
so we don't have to spin up real metadata clients."""
|
|
with patch("web_server.add_activity_item"):
|
|
with patch("web_server.SpotifyClient"):
|
|
with patch("core.tidal_client.TidalClient"):
|
|
from web_server import app as flask_app
|
|
flask_app.config['TESTING'] = True
|
|
yield flask_app.test_client()
|
|
|
|
|
|
def test_missing_source_logs_warning(import_match_client, caplog):
|
|
"""When the match POST omits source, backend logs a clear warning
|
|
so the regression is visible in app.log even though the request
|
|
still proceeds (best-effort lookup via primary-source priority).
|
|
"""
|
|
fake_payload = {'success': True, 'album': {}, 'matches': [], 'unmatched_files': []}
|
|
with caplog.at_level(logging.WARNING, logger='soulsync'):
|
|
with patch(
|
|
'web_server.build_album_import_match_payload',
|
|
return_value=fake_payload,
|
|
):
|
|
resp = import_match_client.post(
|
|
'/api/import/album/match',
|
|
json={'album_id': '1234567890'}, # no source
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
# The defensive log must mention the missing source AND the album_id
|
|
# so ops can grep app.log for the offending caller.
|
|
assert any(
|
|
"Missing 'source'" in r.message and '1234567890' in r.message
|
|
for r in caplog.records
|
|
), (
|
|
"Expected a warning naming the missing source + album_id. "
|
|
"Got records: " + repr([r.message for r in caplog.records])
|
|
)
|
|
|
|
|
|
def test_source_provided_does_not_warn(import_match_client, caplog):
|
|
"""When source IS provided (the common path), no warning fires.
|
|
Catches regression where the warning becomes noisy from firing on
|
|
every legit request."""
|
|
fake_payload = {'success': True, 'album': {}, 'matches': [], 'unmatched_files': []}
|
|
with caplog.at_level(logging.WARNING, logger='soulsync'):
|
|
with patch(
|
|
'web_server.build_album_import_match_payload',
|
|
return_value=fake_payload,
|
|
):
|
|
resp = import_match_client.post(
|
|
'/api/import/album/match',
|
|
json={
|
|
'album_id': '1234567890',
|
|
'source': 'deezer',
|
|
'album_name': 'Test Album',
|
|
'album_artist': 'Test Artist',
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
missing_source_warnings = [
|
|
r for r in caplog.records if "Missing 'source'" in r.message
|
|
]
|
|
assert not missing_source_warnings, (
|
|
"When source is supplied, no missing-source warning should fire. "
|
|
f"Got: {[r.message for r in missing_source_warnings]}"
|
|
)
|
|
|
|
|
|
def test_source_passed_through_to_payload_builder(import_match_client):
|
|
"""Verify the endpoint actually forwards source to the underlying
|
|
payload builder. Without this, we'd be logging the warning correctly
|
|
but still doing the wrong lookup."""
|
|
fake_payload = {'success': True, 'album': {}, 'matches': [], 'unmatched_files': []}
|
|
with patch(
|
|
'web_server.build_album_import_match_payload',
|
|
return_value=fake_payload,
|
|
) as mock_builder:
|
|
import_match_client.post(
|
|
'/api/import/album/match',
|
|
json={
|
|
'album_id': 'abc123',
|
|
'source': 'spotify',
|
|
'album_name': 'X',
|
|
'album_artist': 'Y',
|
|
},
|
|
)
|
|
|
|
mock_builder.assert_called_once()
|
|
call_kwargs = mock_builder.call_args.kwargs
|
|
assert call_kwargs['source'] == 'spotify'
|
|
assert call_kwargs['album_name'] == 'X'
|
|
assert call_kwargs['album_artist'] == 'Y'
|