/
/
/
1"""Tests for binding a sendspin web player to the session that announced it."""
2
3from __future__ import annotations
4
5from types import SimpleNamespace
6from typing import TYPE_CHECKING
7
8import pytest
9from aiohttp import web
10from aiohttp.test_utils import make_mocked_request
11from music_assistant_models.auth import User, UserRole
12
13from music_assistant.controllers.webserver.controller import WebserverController
14from music_assistant.controllers.webserver.websocket_client import WebsocketClientHandler
15
16if TYPE_CHECKING:
17 from music_assistant.mass import MusicAssistant
18
19# Party guests all authenticate as this one shared account, each with its own token.
20_GUEST_USER = User(user_id="party_guest", username="party_guest", role=UserRole.GUEST)
21
22
23@pytest.fixture
24def webserver(mass_minimal: MusicAssistant) -> WebserverController:
25 """Return a WebserverController with stubbed serialization dependencies."""
26 mass_minimal.metadata = SimpleNamespace( # type: ignore[assignment]
27 compute_image_id=lambda provider, path: f"{provider}--{path}"
28 )
29 mass_minimal.translations = SimpleNamespace( # type: ignore[assignment]
30 get_translation=lambda _key, **_kwargs: None
31 )
32 webserver = WebserverController(mass_minimal)
33 mass_minimal.webserver = webserver
34 return webserver
35
36
37def _create_ws_client(
38 webserver: WebserverController, token: str, user: User = _GUEST_USER
39) -> WebsocketClientHandler:
40 """Create an authenticated websocket client handler holding the given token."""
41 request = make_mocked_request("GET", "/ws", app=web.Application())
42 client = WebsocketClientHandler(webserver, request)
43 client._authenticated_user = user
44 client._current_token = token
45 webserver.register_websocket_client(client)
46 return client
47
48
49def test_web_player_is_bound_to_the_session_that_announced_it(
50 webserver: WebserverController,
51) -> None:
52 """A guest's web player lands only on its own session, not on every guest sharing the account."""
53 guest_a = _create_ws_client(webserver, "token-a")
54 guest_b = _create_ws_client(webserver, "token-b")
55
56 webserver.set_sendspin_player_for_token("token-a", "player-a")
57 webserver.set_sendspin_player_for_token("token-b", "player-b")
58
59 assert guest_a._sendspin_player_id == "player-a"
60 assert guest_b._sendspin_player_id == "player-b"
61
62
63def test_web_player_reaches_every_session_sharing_a_token(
64 webserver: WebserverController,
65) -> None:
66 """The tabs of one browser share a token, so each of them learns the web player."""
67 tab_one = _create_ws_client(webserver, "token-a")
68 tab_two = _create_ws_client(webserver, "token-a")
69
70 webserver.set_sendspin_player_for_token("token-a", "player-a")
71
72 assert tab_one._sendspin_player_id == "player-a"
73 assert tab_two._sendspin_player_id == "player-a"
74
75
76def test_unauthenticated_session_is_left_alone(webserver: WebserverController) -> None:
77 """A client that never authenticated holds no token and is never stamped."""
78 anonymous = _create_ws_client(webserver, "token-a")
79 anonymous._authenticated_user = None
80 anonymous._current_token = None
81
82 webserver.set_sendspin_player_for_token("token-a", "player-a")
83
84 assert anonymous._sendspin_player_id is None
85