spotify export: clickable authorize link (popup-block safe) + endpoint pre-check tests
Double-checking the on-demand auth flow: the needs_auth handler called window.open() AFTER an await, which breaks the user-gesture chain so browsers popup-block it — the user would see "approve in the new tab" with no tab. Replaced with a clickable authorize link (a direct click is never blocked). Adds two endpoint tests via the Flask test client: Spotify export returns needs_auth + the /auth/spotify/export url (and short-circuits before the DB) when the token lacks write scope, and does NOT short-circuit when write scope is present. 10 service-export tests green, 64 script-integrity green, ruff clean.
This commit is contained in:
parent
f3672c7ab4
commit
efefdd64ff
2 changed files with 28 additions and 4 deletions
|
|
@ -128,3 +128,27 @@ def test_spotify_backfill_search_disables_cross_service_fallback(monkeypatch):
|
|||
assert fn is not None
|
||||
fn('Kendrick Lamar', 'Not Like Us') # drives the search
|
||||
assert seen['allow_fallback'] is False
|
||||
|
||||
|
||||
def test_spotify_export_endpoint_demands_auth_when_no_write_scope(monkeypatch):
|
||||
"""The export endpoint must return needs_auth (not start a doomed job) when the Spotify
|
||||
token lacks write scope — and it must short-circuit BEFORE touching the DB."""
|
||||
import types
|
||||
monkeypatch.setattr(ws, 'spotify_client',
|
||||
types.SimpleNamespace(has_write_scope=lambda: False))
|
||||
resp = ws.app.test_client().post('/api/playlists/5/export/service/spotify')
|
||||
data = resp.get_json()
|
||||
assert data['needs_auth'] is True
|
||||
assert data['auth_url'] == '/auth/spotify/export'
|
||||
assert data['success'] is False
|
||||
|
||||
|
||||
def test_spotify_export_endpoint_proceeds_when_write_scope_present(monkeypatch):
|
||||
"""With write scope, the spotify path must NOT short-circuit on needs_auth (it goes on to
|
||||
start a job — here it just must not be a needs_auth response)."""
|
||||
import types
|
||||
monkeypatch.setattr(ws, 'spotify_client',
|
||||
types.SimpleNamespace(has_write_scope=lambda: True))
|
||||
resp = ws.app.test_client().post('/api/playlists/999999/export/service/spotify')
|
||||
data = resp.get_json()
|
||||
assert not data.get('needs_auth')
|
||||
|
|
|
|||
|
|
@ -730,11 +730,11 @@ async function _startPlaylistExport(playlistId, mode, name, backfill) {
|
|||
body: isService ? JSON.stringify({ backfill: !!backfill }) : JSON.stringify({ mode }),
|
||||
});
|
||||
const data = await resp.json();
|
||||
// Spotify export needs a one-time write-permission grant. Open the consent in a new tab
|
||||
// and tell the user to try again once they've authorized.
|
||||
// Spotify export needs a one-time write-permission grant. Surface a clickable link (a
|
||||
// direct user click avoids popup-blocking; window.open after this await would be blocked)
|
||||
// and tell the user to retry once they've authorized.
|
||||
if (data.needs_auth && data.auth_url) {
|
||||
window.open(data.auth_url, '_blank');
|
||||
_setExportStatus(playlistId, `<span style="color:#f59e0b;">Spotify needs permission to create playlists — approve it in the new tab, then click Export again.</span>`, 15000);
|
||||
_setExportStatus(playlistId, `<span style="color:#f59e0b;">Spotify needs permission to create playlists — <a href="${data.auth_url}" target="_blank" rel="noopener" style="color:#38bdf8;text-decoration:underline;">authorize</a>, then click Export again.</span>`, 20000);
|
||||
return;
|
||||
}
|
||||
if (!data.success || !data.job_id) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue