mirror of
https://github.com/home-assistant/core.git
synced 2026-09-24 23:41:48 -05:00
Refactor Frontier Silicon tests (#180264)
This commit is contained in:
@@ -1 +1,13 @@
|
||||
"""Tests for the Frontier Silicon integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from afsapi import Equaliser, FSConnectionError, PlayCaps, PlayerMode, Preset
|
||||
from afsapi.nodes import PresetsListItem
|
||||
from afsapi import Equaliser, PlayCaps, PlayerMode, PlayState, Preset
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.frontier_silicon.const import CONF_WEBFSAPI_URL, DOMAIN
|
||||
@@ -13,140 +12,73 @@ from homeassistant.const import CONF_PIN
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
class FakeAFSAPIDevice:
|
||||
"""A fake Frontier Silicon Device."""
|
||||
|
||||
# registry of fake AFSAPI devices
|
||||
afsapi_device_map = {}
|
||||
|
||||
def __new__(cls, webfsapi_endpoint: str, pin: str | int, timeout: int = 2):
|
||||
"""Create or reuse a fake device for the endpoint."""
|
||||
|
||||
if webfsapi_endpoint not in cls.afsapi_device_map:
|
||||
cls.afsapi_device_map[webfsapi_endpoint] = super().__new__(cls)
|
||||
return cls.afsapi_device_map[webfsapi_endpoint]
|
||||
|
||||
def __init__(
|
||||
self, webfsapi_endpoint: str, pin: str | int, timeout: int = 2
|
||||
) -> None:
|
||||
"""Constructor."""
|
||||
if not hasattr(self, "init_done"):
|
||||
self.webfsapi_endpoint = webfsapi_endpoint
|
||||
self.reset()
|
||||
self.init_done = True
|
||||
|
||||
def reset(self):
|
||||
"""Reset the state of the device, ready for the next test."""
|
||||
self.fail_get_power = False
|
||||
self.has_power = False
|
||||
|
||||
async def get_radio_id(self) -> str:
|
||||
"""Mock get_radio_id AFSAPI function."""
|
||||
return "FakeID"
|
||||
|
||||
async def get_power(self) -> bool:
|
||||
"""Mock get_power AFSAPI function."""
|
||||
if self.fail_get_power:
|
||||
raise FSConnectionError
|
||||
return self.has_power
|
||||
|
||||
async def get_play_name(self) -> str:
|
||||
"""Mock get_play_name AFSAPI function."""
|
||||
return "Something Playing"
|
||||
|
||||
async def get_play_text(self) -> str:
|
||||
"""Mock get_play_text AFSAPI function."""
|
||||
return "Something Playing Extra Text"
|
||||
|
||||
async def get_play_artist(self) -> str:
|
||||
"""Mock get_play_artist AFSAPI function."""
|
||||
return "Artist Name"
|
||||
|
||||
async def get_play_album(self) -> str:
|
||||
"""Mock get_play_album AFSAPI function."""
|
||||
return "Album Name"
|
||||
|
||||
async def get_play_status(self) -> int:
|
||||
"""Mock get_play_status AFSAPI function."""
|
||||
return 0
|
||||
|
||||
async def get_mode(self) -> PlayerMode:
|
||||
"""Mock get_mode AFSAPI function."""
|
||||
available_modes = await self.get_modes()
|
||||
return available_modes[0]
|
||||
|
||||
async def get_mute(self) -> bool:
|
||||
"""Mock get_mute AFSAPI function."""
|
||||
return False
|
||||
|
||||
async def get_volume(self) -> int:
|
||||
"""Mock get_volume AFSAPI function."""
|
||||
return 3
|
||||
|
||||
async def get_play_graphic(self) -> str:
|
||||
"""Mock get_play_graphic AFSAPI function."""
|
||||
return "https://1.1.1.1/graphic_url"
|
||||
|
||||
async def get_modes(self) -> list[PlayerMode]:
|
||||
"""Mock get_modes AFSAPI function."""
|
||||
valid_modes = [(0, {"id": "mocked_mode0", "label": "MockedMode"})]
|
||||
|
||||
return [
|
||||
PlayerMode(
|
||||
id=v["id"],
|
||||
key=int(k),
|
||||
label=v.get("label"),
|
||||
selectable=v.get("selectable"),
|
||||
streamable=v.get("streamable"),
|
||||
modetype=v.get("modeType"),
|
||||
)
|
||||
for k, v in valid_modes
|
||||
]
|
||||
|
||||
async def get_play_caps(self) -> PlayCaps | None:
|
||||
"""Mock get_play_caps AFSAPI function."""
|
||||
return PlayCaps(0)
|
||||
|
||||
async def get_presets(self) -> list[Preset]:
|
||||
"""Mock get_presets AFSAPI function."""
|
||||
|
||||
def _to_preset(
|
||||
key: str,
|
||||
preset_fields: PresetsListItem,
|
||||
) -> Preset:
|
||||
"""Internal helper function to convert data to Preset."""
|
||||
return Preset(int(key), preset_fields.get("type"), preset_fields["name"])
|
||||
|
||||
presets_data = [(0, {"type": "mocked_eqpreset_type0", "name": "MockedPreset"})]
|
||||
|
||||
return [_to_preset(key, preset_fields) for key, preset_fields in presets_data]
|
||||
|
||||
async def get_eq_preset(self) -> Equaliser:
|
||||
"""Mock get_eq_preset AFSAPI function."""
|
||||
available_equalisers = await self.get_equalisers()
|
||||
return available_equalisers[0]
|
||||
|
||||
async def get_equalisers(self) -> list[Equaliser]:
|
||||
"""Mock get_equalisers AFSAPI function."""
|
||||
equalisers_data = [(0, {"label": "MockedEq"})]
|
||||
return [
|
||||
Equaliser(key=int(key), label=eqinfo["label"])
|
||||
for key, eqinfo in equalisers_data
|
||||
]
|
||||
|
||||
async def get_volume_steps(self) -> int:
|
||||
"""Mock get_volume_steps AFSAPI function."""
|
||||
return 2
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_afsapi_dev(config_entry: MockConfigEntry):
|
||||
"""Return a test FakeAFSAPIDevice, creating it for an endpoint if needed."""
|
||||
webfsapi_endpoint = config_entry.data[CONF_WEBFSAPI_URL]
|
||||
pin = config_entry.data[CONF_PIN]
|
||||
fake_dev = FakeAFSAPIDevice(webfsapi_endpoint, pin)
|
||||
yield fake_dev
|
||||
fake_dev.reset()
|
||||
def mock_afsapi() -> Generator[AsyncMock]:
|
||||
"""Mock a Frontier Silicon AFSAPI client."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.frontier_silicon.AFSAPI",
|
||||
autospec=True,
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI",
|
||||
new=mock_client,
|
||||
),
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.webfsapi_endpoint = "http://1.1.1.1:80/webfsapi"
|
||||
|
||||
# get_webfsapi_endpoint is a staticmethod on the class; expose it on the
|
||||
# instance mock too so tests can configure it via the yielded client.
|
||||
mock_client.get_webfsapi_endpoint.return_value = "http://1.1.1.1:80/webfsapi"
|
||||
client.get_webfsapi_endpoint = mock_client.get_webfsapi_endpoint
|
||||
client.get_friendly_name.return_value = "Name of the device"
|
||||
client.get_radio_id.return_value = "mock_radio_id"
|
||||
|
||||
client.get_power.return_value = False
|
||||
client.get_play_status.return_value = PlayState.IDLE
|
||||
client.get_play_name.return_value = "Something Playing"
|
||||
client.get_play_text.return_value = "Something Playing Extra Text"
|
||||
client.get_play_artist.return_value = "Artist Name"
|
||||
client.get_play_album.return_value = "Album Name"
|
||||
client.get_play_graphic.return_value = "https://1.1.1.1/graphic_url"
|
||||
client.get_mute.return_value = False
|
||||
client.get_volume.return_value = 3
|
||||
client.get_volume_steps.return_value = 2
|
||||
client.get_play_caps.return_value = PlayCaps(0)
|
||||
|
||||
modes = [
|
||||
PlayerMode(
|
||||
id="mocked_mode0",
|
||||
key=0,
|
||||
label="MockedMode",
|
||||
selectable=True,
|
||||
streamable=None,
|
||||
modetype=None,
|
||||
)
|
||||
]
|
||||
client.get_modes.return_value = modes
|
||||
client.get_mode.return_value = modes[0]
|
||||
|
||||
equalisers = [Equaliser(key=0, label="MockedEq")]
|
||||
client.get_equalisers.return_value = equalisers
|
||||
client.get_eq_preset.return_value = equalisers[0]
|
||||
|
||||
client.get_presets.return_value = [
|
||||
Preset(0, "mocked_eqpreset_type0", "MockedPreset")
|
||||
]
|
||||
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -154,42 +86,7 @@ def config_entry() -> MockConfigEntry:
|
||||
"""Create a mock Frontier Silicon config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Name of the device",
|
||||
unique_id="mock_radio_id",
|
||||
data={CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi", CONF_PIN: "1234"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_valid_device_url() -> Generator[None]:
|
||||
"""Return a valid webfsapi endpoint."""
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_webfsapi_endpoint",
|
||||
return_value="http://1.1.1.1:80/webfsapi",
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_valid_pin() -> Generator[None]:
|
||||
"""Make get_friendly_name return a value, indicating a valid pin."""
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_friendly_name",
|
||||
return_value="Name of the device",
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_radio_id() -> Generator[None]:
|
||||
"""Return a valid radio_id."""
|
||||
with patch("afsapi.AFSAPI.get_radio_id", return_value="mock_radio_id"):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Test the Frontier Silicon config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from afsapi import FSConnectionError, FSNotImplementedError, InvalidPinError
|
||||
import pytest
|
||||
@@ -18,7 +18,7 @@ from homeassistant.helpers.service_info.ssdp import SsdpServiceInfo
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
pytestmark = pytest.mark.usefixtures("mock_afsapi", "mock_setup_entry")
|
||||
|
||||
|
||||
MOCK_DISCOVERY = SsdpServiceInfo(
|
||||
@@ -46,6 +46,7 @@ INVALID_MOCK_DISCOVERY = SsdpServiceInfo(
|
||||
)
|
||||
async def test_form_default_pin(
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
radio_id_return_value: str | None,
|
||||
radio_id_side_effect: Exception | None,
|
||||
@@ -58,15 +59,12 @@ async def test_form_default_pin(
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_radio_id",
|
||||
return_value=radio_id_return_value,
|
||||
side_effect=radio_id_side_effect,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
mock_afsapi.get_radio_id.return_value = radio_id_return_value
|
||||
mock_afsapi.get_radio_id.side_effect = radio_id_side_effect
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
@@ -84,6 +82,7 @@ async def test_form_default_pin(
|
||||
)
|
||||
async def test_form_nondefault_pin(
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
radio_id_return_value: str | None,
|
||||
radio_id_side_effect: Exception | None,
|
||||
@@ -96,29 +95,24 @@ async def test_form_nondefault_pin(
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_friendly_name",
|
||||
side_effect=InvalidPinError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_afsapi.get_friendly_name.side_effect = InvalidPinError
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result2["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_radio_id",
|
||||
return_value=radio_id_return_value,
|
||||
side_effect=radio_id_side_effect,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
mock_afsapi.get_friendly_name.side_effect = None
|
||||
mock_afsapi.get_radio_id.return_value = radio_id_return_value
|
||||
mock_afsapi.get_radio_id.side_effect = radio_id_side_effect
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
@@ -140,6 +134,7 @@ async def test_form_nondefault_pin(
|
||||
)
|
||||
async def test_form_nondefault_pin_invalid(
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
friendly_name_error: Exception,
|
||||
result_error: str,
|
||||
mock_setup_entry: AsyncMock,
|
||||
@@ -152,34 +147,29 @@ async def test_form_nondefault_pin_invalid(
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_friendly_name",
|
||||
side_effect=InvalidPinError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_afsapi.get_friendly_name.side_effect = InvalidPinError
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result2["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_friendly_name",
|
||||
side_effect=friendly_name_error,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_afsapi.get_friendly_name.side_effect = friendly_name_error
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result3["errors"] == {"base": result_error}
|
||||
|
||||
mock_afsapi.get_friendly_name.side_effect = None
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
@@ -204,6 +194,7 @@ async def test_form_nondefault_pin_invalid(
|
||||
)
|
||||
async def test_invalid_device_url(
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
webfsapi_endpoint_error: Exception,
|
||||
result_error: str,
|
||||
mock_setup_entry: AsyncMock,
|
||||
@@ -216,20 +207,18 @@ async def test_invalid_device_url(
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_webfsapi_endpoint",
|
||||
side_effect=webfsapi_endpoint_error,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_afsapi.get_webfsapi_endpoint.side_effect = webfsapi_endpoint_error
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "user"
|
||||
assert result2["errors"] == {"base": result_error}
|
||||
|
||||
mock_afsapi.get_webfsapi_endpoint.side_effect = None
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
@@ -247,6 +236,7 @@ async def test_invalid_device_url(
|
||||
|
||||
async def test_user_already_configured_without_unique_id(
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
) -> None:
|
||||
"""Test manual setup aborts when an entry with the same endpoint already exists."""
|
||||
entry = MockConfigEntry(
|
||||
@@ -256,17 +246,14 @@ async def test_user_already_configured_without_unique_id(
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_radio_id",
|
||||
side_effect=FSNotImplementedError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
mock_afsapi.get_radio_id.side_effect = FSNotImplementedError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
|
||||
assert result2["type"] is FlowResultType.ABORT
|
||||
assert result2["reason"] == "already_configured"
|
||||
@@ -278,21 +265,19 @@ async def test_user_already_configured_without_unique_id(
|
||||
)
|
||||
async def test_ssdp(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: MockConfigEntry,
|
||||
mock_afsapi: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
radio_id_return_value: str | None,
|
||||
radio_id_side_effect: Exception | None,
|
||||
) -> None:
|
||||
"""Test a device being discovered."""
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_radio_id",
|
||||
return_value=radio_id_return_value,
|
||||
side_effect=radio_id_side_effect,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_DISCOVERY,
|
||||
)
|
||||
mock_afsapi.get_radio_id.return_value = radio_id_return_value
|
||||
mock_afsapi.get_radio_id.side_effect = radio_id_side_effect
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_DISCOVERY,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm"
|
||||
@@ -351,35 +336,32 @@ async def test_ssdp_already_configured(
|
||||
[(ValueError, "unknown"), (FSConnectionError, "cannot_connect")],
|
||||
)
|
||||
async def test_ssdp_fail(
|
||||
hass: HomeAssistant, webfsapi_endpoint_error: Exception, result_error: str
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
webfsapi_endpoint_error: Exception,
|
||||
result_error: str,
|
||||
) -> None:
|
||||
"""Test a device being discovered but failing to reply."""
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_webfsapi_endpoint",
|
||||
side_effect=webfsapi_endpoint_error,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_DISCOVERY,
|
||||
)
|
||||
mock_afsapi.get_webfsapi_endpoint.side_effect = webfsapi_endpoint_error
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_DISCOVERY,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == result_error
|
||||
|
||||
|
||||
async def test_ssdp_nondefault_pin(hass: HomeAssistant) -> None:
|
||||
async def test_ssdp_nondefault_pin(hass: HomeAssistant, mock_afsapi: AsyncMock) -> None:
|
||||
"""Test a device being discovered."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_friendly_name",
|
||||
side_effect=InvalidPinError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_DISCOVERY,
|
||||
)
|
||||
mock_afsapi.get_friendly_name.side_effect = InvalidPinError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=MOCK_DISCOVERY,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "invalid_auth"
|
||||
@@ -413,6 +395,7 @@ async def test_reauth_flow(hass: HomeAssistant, config_entry: MockConfigEntry) -
|
||||
)
|
||||
async def test_reauth_flow_friendly_name_error(
|
||||
hass: HomeAssistant,
|
||||
mock_afsapi: AsyncMock,
|
||||
exception: Exception,
|
||||
reason: str,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -425,20 +408,18 @@ async def test_reauth_flow_friendly_name_error(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "device_config"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.config_flow.AFSAPI.get_friendly_name",
|
||||
side_effect=exception,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_afsapi.get_friendly_name.side_effect = exception
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result2["errors"] == {"base": reason}
|
||||
|
||||
mock_afsapi.get_friendly_name.side_effect = None
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_PIN: "4242"},
|
||||
|
||||
@@ -1,24 +1,54 @@
|
||||
"""Test the Frontier Silicon media player entity."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from afsapi import FSConnectionError, FSNotImplementedError, PlayCaps
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.frontier_silicon.media_player import AFSAPIMediaPlayer
|
||||
from homeassistant.components.media_player import MediaPlayerEntityFeature
|
||||
from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_UNAVAILABLE
|
||||
from homeassistant.components.media_player import (
|
||||
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
||||
MediaPlayerEntityFeature,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
STATE_IDLE,
|
||||
STATE_OFF,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity_component import async_update_entity
|
||||
|
||||
from .conftest import FakeAFSAPIDevice
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ENTITY_ID = "media_player.name_of_the_device"
|
||||
|
||||
_FULL_PLAY_CAPS = (
|
||||
PlayCaps.PAUSE
|
||||
| PlayCaps.STOP
|
||||
| PlayCaps.SKIP_NEXT
|
||||
| PlayCaps.SKIP_PREVIOUS
|
||||
| PlayCaps.FAST_FORWARD
|
||||
| PlayCaps.REWIND
|
||||
| PlayCaps.SHUFFLE
|
||||
| PlayCaps.REPEAT
|
||||
| PlayCaps.SEEK
|
||||
| PlayCaps.APPLY_FEEDBACK
|
||||
| PlayCaps.SCROBBLING
|
||||
| PlayCaps.ADD_PRESET
|
||||
| PlayCaps.THUMBS_UP
|
||||
| PlayCaps.THUMBS_DOWN
|
||||
| PlayCaps.SKIP_FORWARD
|
||||
| PlayCaps.SKIP_BACKWARD
|
||||
| PlayCaps.REPEAT_ONE
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -33,16 +63,27 @@ _LOGGER = logging.getLogger(__name__)
|
||||
],
|
||||
)
|
||||
async def test_async_media_previous_track_maps_errors(
|
||||
error: Exception, translation_key: str, message: str | None
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
mock_afsapi: AsyncMock,
|
||||
error: Exception,
|
||||
translation_key: str,
|
||||
message: str | None,
|
||||
) -> None:
|
||||
"""Test previous track maps API failures to Home Assistant errors."""
|
||||
fs_device = AsyncMock()
|
||||
fs_device.rewind.side_effect = error
|
||||
mock_config_entry = MockConfigEntry()
|
||||
entity = AFSAPIMediaPlayer(mock_config_entry, fs_device)
|
||||
mock_afsapi.get_power.return_value = True
|
||||
mock_afsapi.get_play_caps.return_value = _FULL_PLAY_CAPS
|
||||
mock_afsapi.rewind.side_effect = error
|
||||
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
with pytest.raises(HomeAssistantError) as exc_info:
|
||||
await entity.async_media_previous_track()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert exc_info.value.translation_key == translation_key
|
||||
assert exc_info.value.translation_placeholders["command"] == "media_previous_track"
|
||||
@@ -52,33 +93,18 @@ async def test_async_media_previous_track_maps_errors(
|
||||
)
|
||||
|
||||
|
||||
async def test_async_media_caps() -> None:
|
||||
async def test_async_media_caps(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
mock_afsapi: AsyncMock,
|
||||
) -> None:
|
||||
"""Test AFSAPI play caps translation to MediaPlayerEntityFeatures."""
|
||||
fs_device = AsyncMock()
|
||||
fs_device.get_power.return_value = False
|
||||
fs_device.get_play_caps.return_value = (
|
||||
PlayCaps.PAUSE
|
||||
| PlayCaps.STOP
|
||||
| PlayCaps.SKIP_NEXT
|
||||
| PlayCaps.SKIP_PREVIOUS
|
||||
| PlayCaps.FAST_FORWARD
|
||||
| PlayCaps.REWIND
|
||||
| PlayCaps.SHUFFLE
|
||||
| PlayCaps.REPEAT
|
||||
| PlayCaps.SEEK
|
||||
| PlayCaps.APPLY_FEEDBACK
|
||||
| PlayCaps.SCROBBLING
|
||||
| PlayCaps.ADD_PRESET
|
||||
| PlayCaps.THUMBS_UP
|
||||
| PlayCaps.THUMBS_DOWN
|
||||
| PlayCaps.SKIP_FORWARD
|
||||
| PlayCaps.SKIP_BACKWARD
|
||||
| PlayCaps.REPEAT_ONE
|
||||
)
|
||||
mock_config_entry = MockConfigEntry()
|
||||
entity = AFSAPIMediaPlayer(mock_config_entry, fs_device)
|
||||
await entity.async_update()
|
||||
assert entity.supported_features == (
|
||||
mock_afsapi.get_play_caps.return_value = _FULL_PLAY_CAPS
|
||||
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == (
|
||||
AFSAPIMediaPlayer._BASE_SUPPORTED_FEATURES
|
||||
| MediaPlayerEntityFeature.PLAY
|
||||
| MediaPlayerEntityFeature.PAUSE
|
||||
@@ -97,32 +123,25 @@ async def test_media_player_on(
|
||||
config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
fake_afsapi_dev: FakeAFSAPIDevice,
|
||||
mock_afsapi: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test update of a device which is powered on."""
|
||||
# Connect device
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.AFSAPI",
|
||||
FakeAFSAPIDevice,
|
||||
):
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
# Verify device exists
|
||||
devices = dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)
|
||||
assert len(devices) == 1
|
||||
device_entry = devices[0]
|
||||
|
||||
# Verify device has the expected number of entities
|
||||
expected_num_entities = 1
|
||||
entities = er.async_entries_for_device(entity_registry, device_entry.id)
|
||||
assert len(entities) == expected_num_entities
|
||||
assert len(entities) == 1
|
||||
|
||||
# Power on the device and advance time to trigger a poll
|
||||
mock_afsapi.get_power.return_value = True
|
||||
freezer.tick(timedelta(seconds=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Power on the fake device
|
||||
fake_afsapi_dev.has_power = True
|
||||
# get hass to do an update
|
||||
await async_update_entity(hass, entities[0].entity_id)
|
||||
assert hass.states.get(entities[0].entity_id).state == STATE_IDLE
|
||||
|
||||
|
||||
@@ -131,43 +150,33 @@ async def test_async_update_disconnect(
|
||||
config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
fake_afsapi_dev: FakeAFSAPIDevice,
|
||||
mock_afsapi: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that an update with a disconnect can change device availability."""
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
# Connect device
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.AFSAPI",
|
||||
FakeAFSAPIDevice,
|
||||
):
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device exists
|
||||
devices = dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)
|
||||
assert len(devices) == 1
|
||||
device_entry = devices[0]
|
||||
|
||||
# Verify device has the expected number of entities
|
||||
expected_num_entities = 1
|
||||
entities = er.async_entries_for_device(entity_registry, device_entry.id)
|
||||
assert len(entities) == expected_num_entities
|
||||
assert len(entities) == 1
|
||||
entity_id = entities[0].entity_id
|
||||
|
||||
# Get hass to do an update
|
||||
await async_update_entity(hass, entities[0].entity_id)
|
||||
# Fake device starts in off state
|
||||
assert hass.states.get(entities[0].entity_id).state == STATE_OFF
|
||||
# Device starts in off state
|
||||
assert hass.states.get(entity_id).state == STATE_OFF
|
||||
|
||||
# Make the fake device raise a connection error next time get_power is called
|
||||
fake_afsapi_dev.fail_get_power = True
|
||||
# get hass to do an update
|
||||
await async_update_entity(hass, entities[0].entity_id)
|
||||
# Check device availability, should now be offline
|
||||
assert hass.states.get(entities[0].entity_id).state == STATE_UNAVAILABLE
|
||||
# Make the device raise a connection error on the next poll
|
||||
mock_afsapi.get_power.side_effect = FSConnectionError
|
||||
freezer.tick(timedelta(seconds=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
# Reset device error state
|
||||
fake_afsapi_dev.fail_get_power = False
|
||||
await async_update_entity(hass, entities[0].entity_id)
|
||||
# Fake device should be back in off state
|
||||
assert hass.states.get(entities[0].entity_id).state == STATE_OFF
|
||||
mock_afsapi.get_power.side_effect = None
|
||||
freezer.tick(timedelta(seconds=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(entity_id).state == STATE_OFF
|
||||
|
||||
Reference in New Issue
Block a user