/
/
/
1"""Unit tests for the QQ Music setup flow helpers."""
2
3# mypy: ignore-errors
4
5from __future__ import annotations
6
7from types import SimpleNamespace
8
9import pytest
10from qqmusic_api.models.login import QRCodeLoginEvents
11
12from music_assistant.models.setup_flow import AbortFlow, StepExpiredError
13from music_assistant.providers.qqmusic.setup_flow import _poll_qr_login, _qr_data_uri
14
15
16def test_qr_data_uri_honors_mimetype() -> None:
17 """The data-URI should carry the QR's own mimetype (QQ png vs WeChat jpeg) and bytes."""
18 png_qr = SimpleNamespace(data=b"abc", mimetype="image/png")
19 jpeg_qr = SimpleNamespace(data=b"abc", mimetype="image/jpeg")
20 assert _qr_data_uri(png_qr) == "data:image/png;base64,YWJj"
21 assert _qr_data_uri(jpeg_qr) == "data:image/jpeg;base64,YWJj"
22
23
24def _client_returning(
25 event: QRCodeLoginEvents, credential: object | None = None
26) -> SimpleNamespace:
27 """Build a fake QQ client whose check_qrcode yields the given single result."""
28
29 async def check_qrcode(_qr: object) -> SimpleNamespace:
30 return SimpleNamespace(event=event, credential=credential)
31
32 return SimpleNamespace(login=SimpleNamespace(check_qrcode=check_qrcode))
33
34
35@pytest.mark.asyncio
36async def test_poll_qr_login_returns_credential_on_done() -> None:
37 """A DONE event carrying a credential should be returned to the caller."""
38 credential = SimpleNamespace(musicid=123, musickey="mk")
39 client = _client_returning(QRCodeLoginEvents.DONE, credential)
40 result = await _poll_qr_login(client, SimpleNamespace())
41 assert result is credential
42
43
44@pytest.mark.asyncio
45async def test_poll_qr_login_expired_raises_step_expired() -> None:
46 """A TIMEOUT event should surface as StepExpiredError so the caller refreshes the QR."""
47 client = _client_returning(QRCodeLoginEvents.TIMEOUT)
48 with pytest.raises(StepExpiredError):
49 await _poll_qr_login(client, SimpleNamespace())
50
51
52@pytest.mark.asyncio
53async def test_poll_qr_login_refused_aborts_flow() -> None:
54 """A REFUSE event should abort the flow with the login_rejected reason."""
55 client = _client_returning(QRCodeLoginEvents.REFUSE)
56 with pytest.raises(AbortFlow) as excinfo:
57 await _poll_qr_login(client, SimpleNamespace())
58 assert excinfo.value.reason == "login_rejected"
59