Discord-reported: clicking the Tidal "Authenticate" button on a Docker setup landed users on a remote-access instructions page that told them their callback URL would look like http://127.0.0.1:8888/tidal/callback?code=... — Spotify's port, hardcoded into the Tidal instructions. Users who followed those instructions literally saved 8888 into their tidal.redirect_uri setting; that mismatched their Tidal Developer App's registered :8889 redirect URI and Tidal returned error 1002 (invalid redirect URI) on every auth attempt. Pull the port from the actual TidalClient.redirect_uri the OAuth URL was just built with (urlparse), with the SOULSYNC_TIDAL_CALLBACK_PORT env var as fallback when the URI can't be parsed. Both the Step 2 example and the Step 3 highlighted URL now reflect whatever Tidal port the user is actually configured to use. Adds 3 regression tests covering the reported scenario, custom callback ports via SOULSYNC_TIDAL_CALLBACK_PORT, and a defensive fallback when redirect_uri is unparseable. Tests hit the real /auth/tidal route through Flask's test client and assert the rendered HTML, so future hardcoded ports get caught immediately.
147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
"""Regression tests for Tidal auth instruction page port rendering.
|
|
|
|
Discord-reported bug: the auth-instructions page shown after clicking
|
|
the Tidal "Authenticate" button rendered example callback URLs with
|
|
port ``8888`` (Spotify's port) instead of ``8889`` (Tidal's port).
|
|
Users who followed the instructions literally saved Spotify's port
|
|
into their ``tidal.redirect_uri`` setting; that mismatched their
|
|
Tidal Developer App's registered ``:8889`` redirect URI and Tidal
|
|
returned error 1002 (invalid redirect URI) on every auth attempt.
|
|
|
|
These tests make sure the rendered instructions show whatever port
|
|
the OAuth URL itself was built with, so the displayed example always
|
|
matches what the user must register in their Tidal app.
|
|
"""
|
|
|
|
from typing import Callable
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
# Run the route through Flask's test client so we get the real HTML
|
|
# the user would see. We patch out:
|
|
# - TidalClient (the real client tries to connect to Tidal),
|
|
# - the activity-feed call (writes to runtime state),
|
|
# - request.host detection (so the Docker code path is exercised
|
|
# and the instructions page is the one with the example URL).
|
|
@pytest.fixture
|
|
def auth_route_client(monkeypatch: pytest.MonkeyPatch):
|
|
"""Return a Flask test client wired up enough to render the
|
|
Tidal auth-instructions page."""
|
|
# Force the "remote/docker" branch by faking a remote-host request.
|
|
# Easier than mocking is_docker; the route only needs ONE of the
|
|
# two flags to render the instructions page.
|
|
monkeypatch.setattr(
|
|
"os.path.exists",
|
|
lambda p: p == "/.dockerenv" or False,
|
|
)
|
|
|
|
fake_client = MagicMock()
|
|
fake_client.client_id = "fake-id"
|
|
fake_client.code_verifier = "v" * 40
|
|
fake_client.code_challenge = "c" * 40
|
|
fake_client.auth_url = "https://login.tidal.com/authorize"
|
|
|
|
def _set_redirect_uri(value):
|
|
fake_client.redirect_uri = value
|
|
fake_client._generate_pkce_challenge = MagicMock()
|
|
|
|
with patch("core.tidal_client.TidalClient", return_value=fake_client):
|
|
with patch("web_server.add_activity_item"):
|
|
from web_server import app as flask_app
|
|
flask_app.config['TESTING'] = True
|
|
yield flask_app.test_client(), fake_client
|
|
|
|
|
|
def _extract_html(response) -> str:
|
|
return response.get_data(as_text=True)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_instructions_show_tidal_port_not_spotify_port_when_config_uses_8889(
|
|
auth_route_client, monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""The reported scenario: tidal.redirect_uri config carries port
|
|
8889, the rendered instructions must show 8889 (not Spotify's
|
|
8888) in both the Step 2 example and the Step 3 highlighted URL."""
|
|
client, fake_client = auth_route_client
|
|
|
|
fake_client.redirect_uri = "http://127.0.0.1:8889/tidal/callback"
|
|
|
|
from config.settings import config_manager
|
|
monkeypatch.setattr(
|
|
config_manager, "get",
|
|
lambda key, default=None: (
|
|
"http://127.0.0.1:8889/tidal/callback"
|
|
if key == "tidal.redirect_uri" else default
|
|
),
|
|
)
|
|
|
|
response = client.get("/auth/tidal", base_url="http://192.168.1.50:8008")
|
|
html = _extract_html(response)
|
|
|
|
# Both example URLs in the instructions must use Tidal's port.
|
|
assert ":8889/tidal/callback" in html, (
|
|
"Step 2/3 example URLs must reflect the configured Tidal port"
|
|
)
|
|
assert ":8888/tidal/callback" not in html, (
|
|
"Spotify's port must not appear in Tidal auth instructions"
|
|
)
|
|
|
|
|
|
def test_instructions_respect_custom_callback_port_from_env(
|
|
auth_route_client, monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""SOULSYNC_TIDAL_CALLBACK_PORT env var changes which port the
|
|
Tidal callback server binds to; the instructions must reflect
|
|
that custom port too, not assume the 8889 default."""
|
|
client, fake_client = auth_route_client
|
|
|
|
fake_client.redirect_uri = "http://127.0.0.1:9999/tidal/callback"
|
|
|
|
from config.settings import config_manager
|
|
monkeypatch.setattr(
|
|
config_manager, "get",
|
|
lambda key, default=None: (
|
|
"http://127.0.0.1:9999/tidal/callback"
|
|
if key == "tidal.redirect_uri" else default
|
|
),
|
|
)
|
|
monkeypatch.setenv("SOULSYNC_TIDAL_CALLBACK_PORT", "9999")
|
|
|
|
response = client.get("/auth/tidal", base_url="http://192.168.1.50:8008")
|
|
html = _extract_html(response)
|
|
|
|
assert ":9999/tidal/callback" in html
|
|
|
|
|
|
def test_instructions_fall_back_to_default_port_when_redirect_uri_is_unparseable(
|
|
auth_route_client, monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Defensive: if redirect_uri somehow has no port (corrupted
|
|
config, schemeless string, etc.), the instructions fall back to
|
|
the default Tidal port from the env var instead of crashing or
|
|
showing the Spotify port."""
|
|
client, fake_client = auth_route_client
|
|
|
|
fake_client.redirect_uri = "not-a-valid-url"
|
|
|
|
from config.settings import config_manager
|
|
monkeypatch.setattr(
|
|
config_manager, "get",
|
|
lambda key, default=None: (
|
|
"not-a-valid-url" if key == "tidal.redirect_uri" else default
|
|
),
|
|
)
|
|
|
|
response = client.get("/auth/tidal", base_url="http://192.168.1.50:8008")
|
|
html = _extract_html(response)
|
|
|
|
# Falls back to Tidal default 8889, never to Spotify's 8888.
|
|
assert ":8889/tidal/callback" in html
|
|
assert ":8888/tidal/callback" not in html
|