soulsync/tests/test_resolve_secret.py
BoulderBadgeDad 09b97c5f63 #870: Deezer ARL 'resets itself' — test the SAVED token, not the redaction mask
The Deezer ARL field round-trips a redaction sentinel for a saved-but-untouched
secret (shown as dots). The save path already guards against the sentinel
overwriting the real token (ConfigManager.set), so the ARL was never actually
lost — but the connection TEST read the field value and sent the sentinel as the
token, so Deezer returned USER_ID=0 ('Invalid ARL token') after navigating away
and back. That false failure made it look like the ARL kept resetting.

Fix:
- ConfigManager.resolve_secret(key, posted): empty/sentinel posted value -> the
  stored value; a real string -> a genuine new secret. Reusable for any secret
  connection-test (single source of truth).
- /api/deezer-download/test now resolves the effective ARL via resolve_secret, so
  an untouched field tests the stored token.
- testDeezerDownloadConnection() strips the sentinel before sending (untouched ->
  empty -> backend uses the saved token).

Seam/regression tests for resolve_secret (sentinel/empty/none -> stored, real ->
passthrough, nothing stored -> empty). JS integrity 64 green.
2026-06-13 15:37:29 -07:00

43 lines
1.6 KiB
Python

"""ConfigManager.resolve_secret — test the EFFECTIVE secret, not the mask (#870).
The settings UI renders a saved-but-untouched secret as the redaction sentinel
(shown as dots). The Deezer ARL connection test was sending that sentinel as the
token, so Deezer rejected it ("Invalid ARL token — USER_ID=0") and it looked like
the ARL kept resetting — even though the saved value was fine. resolve_secret maps
an empty/sentinel posted value back to the stored token.
"""
from __future__ import annotations
from config.settings import ConfigManager
S = ConfigManager.REDACTED_SENTINEL
def _cm(config_data):
cm = object.__new__(ConfigManager) # bypass __init__/DB — get() reads config_data
cm.config_data = config_data
return cm
def test_sentinel_resolves_to_stored():
cm = _cm({'deezer_download': {'arl': 'real_arl_123'}})
assert cm.resolve_secret('deezer_download.arl', S) == 'real_arl_123'
def test_empty_or_none_resolves_to_stored():
cm = _cm({'deezer_download': {'arl': 'real_arl_123'}})
assert cm.resolve_secret('deezer_download.arl', '') == 'real_arl_123'
assert cm.resolve_secret('deezer_download.arl', ' ') == 'real_arl_123'
assert cm.resolve_secret('deezer_download.arl', None) == 'real_arl_123'
def test_real_value_passes_through_trimmed():
cm = _cm({'deezer_download': {'arl': 'old'}})
assert cm.resolve_secret('deezer_download.arl', 'new_token') == 'new_token'
assert cm.resolve_secret('deezer_download.arl', ' spaced ') == 'spaced'
def test_sentinel_with_nothing_stored_returns_empty():
cm = _cm({})
assert cm.resolve_secret('deezer_download.arl', S) == ''