Arcam config flow (#34384)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Joakim Plate 2020-06-06 22:43:28 +02:00 committed by GitHub
parent 524b48be7d
commit 31973de2d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 379 additions and 204 deletions

View file

@ -5,27 +5,15 @@ import logging
from arcam.fmj import ConnectionFailed
from arcam.fmj.client import Client
import async_timeout
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_ZONE,
EVENT_HOMEASSISTANT_STOP,
SERVICE_TURN_ON,
)
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from .const import (
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
DOMAIN_DATA_CONFIG,
DOMAIN_DATA_ENTRIES,
DOMAIN_DATA_TASKS,
SIGNAL_CLIENT_DATA,
@ -35,44 +23,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
def _optional_zone(value):
if value:
return ZONE_SCHEMA(value)
return ZONE_SCHEMA({})
def _zone_name_validator(config):
for zone, zone_config in config[CONF_ZONE].items():
if CONF_NAME not in zone_config:
zone_config[
CONF_NAME
] = f"{DEFAULT_NAME} ({config[CONF_HOST]}:{config[CONF_PORT]}) - {zone}"
return config
ZONE_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(SERVICE_TURN_ON): cv.SERVICE_SCHEMA,
}
)
DEVICE_SCHEMA = vol.Schema(
vol.All(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.positive_int,
vol.Optional(CONF_ZONE, default={1: _optional_zone(None)}): {
vol.In([1, 2]): _optional_zone
},
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.positive_int,
},
_zone_name_validator,
)
)
CONFIG_SCHEMA = cv.deprecated(DOMAIN, invalidation_version="0.115")
async def _await_cancel(task):
@ -83,27 +34,10 @@ async def _await_cancel(task):
pass
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA])}, extra=vol.ALLOW_EXTRA
)
async def async_setup(hass: HomeAssistantType, config: ConfigType):
"""Set up the component."""
hass.data[DOMAIN_DATA_ENTRIES] = {}
hass.data[DOMAIN_DATA_TASKS] = {}
hass.data[DOMAIN_DATA_CONFIG] = {}
for device in config[DOMAIN]:
hass.data[DOMAIN_DATA_CONFIG][(device[CONF_HOST], device[CONF_PORT])] = device
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_HOST: device[CONF_HOST], CONF_PORT: device[CONF_PORT]},
)
)
async def _stop(_):
asyncio.gather(
@ -116,21 +50,12 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
async def async_setup_entry(hass: HomeAssistantType, entry: config_entries.ConfigEntry):
"""Set up an access point from a config entry."""
"""Set up config entry."""
entries = hass.data[DOMAIN_DATA_ENTRIES]
tasks = hass.data[DOMAIN_DATA_TASKS]
client = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
config = hass.data[DOMAIN_DATA_CONFIG].get(
(entry.data[CONF_HOST], entry.data[CONF_PORT]),
DEVICE_SCHEMA(
{CONF_HOST: entry.data[CONF_HOST], CONF_PORT: entry.data[CONF_PORT]}
),
)
tasks = hass.data.setdefault(DOMAIN_DATA_TASKS, {})
hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id] = {
"client": client,
"config": config,
}
entries[entry.entry_id] = client
task = asyncio.create_task(_run_client(hass, client, DEFAULT_SCAN_INTERVAL))
tasks[entry.entry_id] = task

View file

@ -1,27 +1,102 @@
"""Config flow to configure the Arcam FMJ component."""
from operator import itemgetter
import logging
from urllib.parse import urlparse
from arcam.fmj.client import Client, ConnectionFailed
from arcam.fmj.utils import get_uniqueid_from_host, get_uniqueid_from_udn
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.ssdp import ATTR_SSDP_LOCATION, ATTR_UPNP_UDN
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN, DOMAIN_DATA_ENTRIES
_GETKEY = itemgetter(CONF_HOST, CONF_PORT)
_LOGGER = logging.getLogger(__name__)
def get_entry_client(hass, entry):
"""Retrieve client associated with a config entry."""
return hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id]
@config_entries.HANDLERS.register(DOMAIN)
class ArcamFmjFlowHandler(config_entries.ConfigFlow):
"""Handle a SimpliSafe config flow."""
"""Handle config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml."""
entries = self.hass.config_entries.async_entries(DOMAIN)
import_key = _GETKEY(import_config)
for entry in entries:
if _GETKEY(entry.data) == import_key:
return self.async_abort(reason="already_setup")
async def _async_set_unique_id_and_update(self, host, port, uuid):
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_configured({CONF_HOST: host, CONF_PORT: port})
return self.async_create_entry(title="Arcam FMJ", data=import_config)
async def _async_check_and_create(self, host, port):
client = Client(host, port)
try:
await client.start()
except ConnectionFailed:
return self.async_abort(reason="unable_to_connect")
finally:
await client.stop()
return self.async_create_entry(
title=f"{DEFAULT_NAME} ({host})", data={CONF_HOST: host, CONF_PORT: port},
)
async def async_step_user(self, user_info=None):
"""Handle a discovered device."""
errors = {}
if user_info is not None:
uuid = await get_uniqueid_from_host(
async_get_clientsession(self.hass), user_info[CONF_HOST]
)
if uuid:
await self._async_set_unique_id_and_update(
user_info[CONF_HOST], user_info[CONF_PORT], uuid
)
return await self._async_check_and_create(
user_info[CONF_HOST], user_info[CONF_PORT]
)
fields = {
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
return self.async_show_form(
step_id="user", data_schema=vol.Schema(fields), errors=errors
)
async def async_step_confirm(self, user_input=None):
"""Handle user-confirmation of discovered node."""
context = self.context # pylint: disable=no-member
placeholders = {
"host": context[CONF_HOST],
}
context["title_placeholders"] = placeholders
if user_input is not None:
return await self._async_check_and_create(
context[CONF_HOST], context[CONF_PORT]
)
return self.async_show_form(
step_id="confirm", description_placeholders=placeholders
)
async def async_step_ssdp(self, discovery_info):
"""Handle a discovered device."""
host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname
port = DEFAULT_PORT
uuid = get_uniqueid_from_udn(discovery_info[ATTR_UPNP_UDN])
await self._async_set_unique_id_and_update(host, port, uuid)
context = self.context # pylint: disable=no-member
context[CONF_HOST] = host
context[CONF_PORT] = DEFAULT_PORT
return await self.async_step_confirm()

View file

@ -13,4 +13,3 @@ DEFAULT_SCAN_INTERVAL = 5
DOMAIN_DATA_ENTRIES = f"{DOMAIN}.entries"
DOMAIN_DATA_TASKS = f"{DOMAIN}.tasks"
DOMAIN_DATA_CONFIG = f"{DOMAIN}.config"

View file

@ -1,8 +1,14 @@
{
"domain": "arcam_fmj",
"name": "Arcam FMJ Receivers",
"config_flow": false,
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
"requirements": ["arcam-fmj==0.4.6"],
"requirements": ["arcam-fmj==0.5.1"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
"manufacturer": "ARCAM"
}
],
"codeowners": ["@elupus"]
}

View file

@ -1,6 +1,5 @@
"""Arcam media player."""
import logging
from typing import Optional
from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceCodes
from arcam.fmj.state import State
@ -17,21 +16,13 @@ from homeassistant.components.media_player.const import (
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_NAME,
CONF_ZONE,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.core import callback
from homeassistant.helpers.service import async_call_from_config
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.helpers.typing import HomeAssistantType
from .config_flow import get_entry_client
from .const import (
DOMAIN,
DOMAIN_DATA_ENTRIES,
EVENT_TURN_ON,
SIGNAL_CLIENT_DATA,
SIGNAL_CLIENT_STARTED,
@ -47,19 +38,17 @@ async def async_setup_entry(
async_add_entities,
):
"""Set up the configuration entry."""
data = hass.data[DOMAIN_DATA_ENTRIES][config_entry.entry_id]
client = data["client"]
config = data["config"]
client = get_entry_client(hass, config_entry)
async_add_entities(
[
ArcamFmj(
config_entry.title,
State(client, zone),
config_entry.unique_id or config_entry.entry_id,
zone_config[CONF_NAME],
zone_config.get(SERVICE_TURN_ON),
)
for zone, zone_config in config[CONF_ZONE].items()
for zone in [1, 2]
],
True,
)
@ -71,13 +60,13 @@ class ArcamFmj(MediaPlayerEntity):
"""Representation of a media device."""
def __init__(
self, state: State, uuid: str, name: str, turn_on: Optional[ConfigType]
self, device_name, state: State, uuid: str,
):
"""Initialize device."""
self._state = state
self._device_name = device_name
self._name = f"{device_name} - Zone: {state.zn}"
self._uuid = uuid
self._name = name
self._turn_on = turn_on
self._support = (
SUPPORT_SELECT_SOURCE
| SUPPORT_VOLUME_SET
@ -102,6 +91,11 @@ class ArcamFmj(MediaPlayerEntity):
)
)
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
return self._state.zn == 1
@property
def unique_id(self):
"""Return unique identifier if known."""
@ -111,8 +105,12 @@ class ArcamFmj(MediaPlayerEntity):
def device_info(self):
"""Return a device description for device registry."""
return {
"identifiers": {(DOMAIN, self._state.client.host, self._state.client.port)},
"model": "FMJ",
"name": self._device_name,
"identifiers": {
(DOMAIN, self._uuid),
(DOMAIN, self._state.client.host, self._state.client.port),
},
"model": "Arcam FMJ AVR",
"manufacturer": "Arcam",
}
@ -229,15 +227,6 @@ class ArcamFmj(MediaPlayerEntity):
if self._state.get_power() is not None:
_LOGGER.debug("Turning on device using connection")
await self._state.set_power(True)
elif self._turn_on:
_LOGGER.debug("Turning on device using service call")
await async_call_from_config(
self.hass,
self._turn_on,
variables=None,
blocking=True,
validate_config=False,
)
else:
_LOGGER.debug("Firing event to turn on device")
self.hass.bus.async_fire(EVENT_TURN_ON, {ATTR_ENTITY_ID: self.entity_id})

View file

@ -1,7 +1,28 @@
{
"config": {
"abort": {
"already_configured": "Device was already setup.",
"already_in_progress": "Config flow for device is already in progress.",
"unable_to_connect": "Unable to connect to device."
},
"error": {},
"flow_title": "Arcam FMJ on {host}",
"step": {
"confirm": {
"description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?"
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]"
},
"description": "Please enter the host name or IP address of device."
}
}
},
"device_automation": {
"trigger_type": {
"turn_on": "{entity_name} was requested to turn on"
}
}
}
}

View file

@ -1,4 +1,26 @@
{
"title": "Arcam FMJ",
"config": {
"abort": {
"already_configured": "Device was already setup.",
"already_in_progress": "Config flow for device is already in progress.",
"unable_to_connect": "Unable to connect to device."
},
"error": {},
"flow_title": "Arcam FMJ on {host}",
"step": {
"confirm": {
"description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?"
},
"user": {
"data": {
"host": "Host",
"port": "Port"
},
"description": "Please enter the host name or IP address of device."
}
}
},
"device_automation": {
"trigger_type": {
"turn_on": "{entity_name} was requested to turn on"

View file

@ -15,6 +15,7 @@ FLOWS = [
"almond",
"ambiclimate",
"ambient_station",
"arcam_fmj",
"atag",
"august",
"avri",

View file

@ -6,6 +6,12 @@ To update, run python3 -m script.hassfest
# fmt: off
SSDP = {
"arcam_fmj": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
"manufacturer": "ARCAM"
}
],
"deconz": [
{
"manufacturer": "Royal Philips Electronics"

View file

@ -272,7 +272,7 @@ aprslib==0.6.46
aqualogic==1.0
# homeassistant.components.arcam_fmj
arcam-fmj==0.4.6
arcam-fmj==0.5.1
# homeassistant.components.arris_tg2492lg
arris-tg2492lg==1.0.0

View file

@ -137,7 +137,7 @@ apprise==0.8.5
aprslib==0.6.46
# homeassistant.components.arcam_fmj
arcam-fmj==0.4.6
arcam-fmj==0.5.1
# homeassistant.components.dlna_dmr
# homeassistant.components.upnp

View file

@ -3,30 +3,24 @@ from arcam.fmj.client import Client
from arcam.fmj.state import State
import pytest
from homeassistant.components.arcam_fmj import DEVICE_SCHEMA
from homeassistant.components.arcam_fmj.const import DOMAIN
from homeassistant.components.arcam_fmj.const import DEFAULT_NAME
from homeassistant.components.arcam_fmj.media_player import ArcamFmj
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.setup import async_setup_component
from tests.async_mock import Mock, patch
from tests.common import MockConfigEntry
MOCK_HOST = "127.0.0.1"
MOCK_PORT = 1234
MOCK_PORT = 50000
MOCK_TURN_ON = {
"service": "switch.turn_on",
"data": {"entity_id": "switch.test"},
}
MOCK_NAME = "dummy"
MOCK_UUID = "1234"
MOCK_ENTITY_ID = "media_player.arcam_fmj_127_0_0_1_1234_1"
MOCK_CONFIG = DEVICE_SCHEMA({CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT})
@pytest.fixture(name="config")
def config_fixture():
"""Create hass config fixture."""
return {DOMAIN: [{CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}]}
MOCK_ENTITY_ID = "media_player.arcam_fmj_127_0_0_1_zone_1"
MOCK_UUID = "456789abcdef"
MOCK_UDN = f"uuid:01234567-89ab-cdef-0123-{MOCK_UUID}"
MOCK_NAME = f"{DEFAULT_NAME} ({MOCK_HOST})"
MOCK_CONFIG_ENTRY = {CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}
@pytest.fixture(name="client")
@ -75,7 +69,7 @@ def state_fixture(state_1):
@pytest.fixture(name="player")
def player_fixture(hass, state):
"""Get standard player."""
player = ArcamFmj(state, MOCK_UUID, MOCK_NAME, None)
player = ArcamFmj(MOCK_NAME, state, MOCK_UUID)
player.entity_id = MOCK_ENTITY_ID
player.hass = hass
player.async_write_ha_state = Mock()
@ -83,8 +77,12 @@ def player_fixture(hass, state):
@pytest.fixture(name="player_setup")
async def player_setup_fixture(hass, config, state_1, state_2, client):
async def player_setup_fixture(hass, state_1, state_2, client):
"""Get standard player."""
config_entry = MockConfigEntry(
domain="arcam_fmj", data=MOCK_CONFIG_ENTRY, title=MOCK_NAME
)
config_entry.add_to_hass(hass)
def state_mock(cli, zone):
if zone == 1:
@ -95,6 +93,6 @@ async def player_setup_fixture(hass, config, state_1, state_2, client):
with patch("homeassistant.components.arcam_fmj.Client", return_value=client), patch(
"homeassistant.components.arcam_fmj.media_player.State", side_effect=state_mock
), patch("homeassistant.components.arcam_fmj._run_client", return_value=None):
assert await async_setup_component(hass, "arcam_fmj", config)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
yield MOCK_ENTITY_ID

View file

@ -1,37 +1,182 @@
"""Tests for the Arcam FMJ config flow module."""
from arcam.fmj.client import ConnectionFailed
import pytest
from homeassistant import data_entry_flow
from homeassistant.components.arcam_fmj.config_flow import ArcamFmjFlowHandler
from homeassistant.components.arcam_fmj.const import DOMAIN
from homeassistant.components import ssdp
from homeassistant.components.arcam_fmj.config_flow import get_entry_client
from homeassistant.components.arcam_fmj.const import DOMAIN, DOMAIN_DATA_ENTRIES
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SOURCE
from .conftest import MOCK_CONFIG, MOCK_NAME
from .conftest import (
MOCK_CONFIG_ENTRY,
MOCK_HOST,
MOCK_NAME,
MOCK_PORT,
MOCK_UDN,
MOCK_UUID,
)
from tests.async_mock import AsyncMock, patch
from tests.common import MockConfigEntry
MOCK_UPNP_DEVICE = f"""
<root xmlns="urn:schemas-upnp-org:device-1-0">
<device>
<UDN>{MOCK_UDN}</UDN>
</device>
</root>
"""
@pytest.fixture(name="config_entry")
def config_entry_fixture():
"""Create a mock Arcam config entry."""
return MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, title=MOCK_NAME)
MOCK_UPNP_LOCATION = f"http://{MOCK_HOST}:8080/dd.xml"
MOCK_DISCOVER = {
ssdp.ATTR_UPNP_MANUFACTURER: "ARCAM",
ssdp.ATTR_UPNP_MODEL_NAME: " ",
ssdp.ATTR_UPNP_MODEL_NUMBER: "AVR450, AVR750",
ssdp.ATTR_UPNP_FRIENDLY_NAME: f"Arcam media client {MOCK_UUID}",
ssdp.ATTR_UPNP_SERIAL: "12343",
ssdp.ATTR_SSDP_LOCATION: f"http://{MOCK_HOST}:8080/dd.xml",
ssdp.ATTR_UPNP_UDN: MOCK_UDN,
ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:MediaRenderer:1",
}
async def test_single_import_only(hass, config_entry):
"""Test form is shown when host not provided."""
config_entry.add_to_hass(hass)
flow = ArcamFmjFlowHandler()
flow.hass = hass
result = await flow.async_step_import(MOCK_CONFIG)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_setup"
@pytest.fixture(name="dummy_client", autouse=True)
def dummy_client_fixture(hass):
"""Mock out the real client."""
with patch("homeassistant.components.arcam_fmj.config_flow.Client") as client:
client.return_value.start.side_effect = AsyncMock(return_value=None)
client.return_value.stop.side_effect = AsyncMock(return_value=None)
yield client.return_value
async def test_import(hass):
"""Test form is shown when host not provided."""
flow = ArcamFmjFlowHandler()
flow.hass = hass
result = await flow.async_step_import(MOCK_CONFIG)
async def test_ssdp(hass, dummy_client):
"""Test a ssdp import flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=MOCK_DISCOVER,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "confirm"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Arcam FMJ"
assert result["data"] == MOCK_CONFIG
assert result["title"] == f"Arcam FMJ ({MOCK_HOST})"
assert result["data"] == MOCK_CONFIG_ENTRY
async def test_ssdp_abort(hass):
"""Test a ssdp import flow."""
entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_CONFIG_ENTRY, title=MOCK_NAME, unique_id=MOCK_UUID
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=MOCK_DISCOVER,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_ssdp_unable_to_connect(hass, dummy_client):
"""Test a ssdp import flow."""
dummy_client.start.side_effect = AsyncMock(side_effect=ConnectionFailed)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=MOCK_DISCOVER,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "confirm"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "unable_to_connect"
async def test_ssdp_update(hass):
"""Test a ssdp import flow."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "old_host", CONF_PORT: MOCK_PORT},
title=MOCK_NAME,
unique_id=MOCK_UUID,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=MOCK_DISCOVER,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert entry.data[CONF_HOST] == MOCK_HOST
async def test_user(hass, aioclient_mock):
"""Test a manual user configuration flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=None,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
user_input = {
CONF_HOST: MOCK_HOST,
CONF_PORT: MOCK_PORT,
}
aioclient_mock.get(MOCK_UPNP_LOCATION, text=MOCK_UPNP_DEVICE)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == f"Arcam FMJ ({MOCK_HOST})"
assert result["data"] == MOCK_CONFIG_ENTRY
assert result["result"].unique_id == MOCK_UUID
async def test_invalid_ssdp(hass, aioclient_mock):
"""Test a a config flow where ssdp fails."""
user_input = {
CONF_HOST: MOCK_HOST,
CONF_PORT: MOCK_PORT,
}
aioclient_mock.get(MOCK_UPNP_LOCATION, text="")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == f"Arcam FMJ ({MOCK_HOST})"
assert result["data"] == MOCK_CONFIG_ENTRY
assert result["result"].unique_id is None
async def test_user_wrong(hass, aioclient_mock):
"""Test a manual user configuration flow with no ssdp response."""
user_input = {
CONF_HOST: MOCK_HOST,
CONF_PORT: MOCK_PORT,
}
aioclient_mock.get(MOCK_UPNP_LOCATION, status=404)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == f"Arcam FMJ ({MOCK_HOST})"
assert result["result"].unique_id is None
async def test_get_entry_client(hass):
"""Test helper for configuration."""
entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_CONFIG_ENTRY, title=MOCK_NAME, unique_id=MOCK_UUID
)
hass.data[DOMAIN_DATA_ENTRIES] = {entry.entry_id: "dummy"}
assert get_entry_client(hass, entry) == "dummy"

View file

@ -4,10 +4,14 @@ from math import isclose
from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceCodes
import pytest
from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC
from homeassistant.core import HomeAssistant
from homeassistant.components.media_player.const import (
ATTR_INPUT_SOURCE,
MEDIA_TYPE_MUSIC,
SERVICE_SELECT_SOURCE,
)
from homeassistant.const import ATTR_ENTITY_ID
from .conftest import MOCK_ENTITY_ID, MOCK_HOST, MOCK_NAME, MOCK_PORT, MOCK_UUID
from .conftest import MOCK_HOST, MOCK_NAME, MOCK_PORT, MOCK_UUID
from tests.async_mock import ANY, MagicMock, Mock, PropertyMock, patch
@ -27,8 +31,9 @@ async def test_properties(player, state):
"""Test standard properties."""
assert player.unique_id == f"{MOCK_UUID}-1"
assert player.device_info == {
"identifiers": {("arcam_fmj", MOCK_HOST, MOCK_PORT)},
"model": "FMJ",
"name": f"Arcam FMJ ({MOCK_HOST})",
"identifiers": {("arcam_fmj", MOCK_UUID), ("arcam_fmj", MOCK_HOST, MOCK_PORT)},
"model": "Arcam FMJ AVR",
"manufacturer": "Arcam",
}
assert not player.should_poll
@ -55,12 +60,12 @@ async def test_powered_on(player, state):
async def test_supported_features(player, state):
"""Test support when turn on service exist."""
"""Test supported features."""
data = await update(player)
assert data.attributes["supported_features"] == 69004
async def test_turn_on_without_service(player, state):
async def test_turn_on(player, state):
"""Test turn on service."""
state.get_power.return_value = None
await player.async_turn_on()
@ -71,29 +76,6 @@ async def test_turn_on_without_service(player, state):
state.set_power.assert_called_with(True)
async def test_turn_on_with_service(hass, state):
"""Test support when turn on service exist."""
from homeassistant.components.arcam_fmj.media_player import ArcamFmj
player = ArcamFmj(state, MOCK_UUID, "dummy", MOCK_TURN_ON)
player.hass = Mock(HomeAssistant)
player.entity_id = MOCK_ENTITY_ID
with patch(
"homeassistant.components.arcam_fmj.media_player.async_call_from_config"
) as async_call_from_config:
state.get_power.return_value = None
await player.async_turn_on()
state.set_power.assert_not_called()
async_call_from_config.assert_called_with(
player.hass,
MOCK_TURN_ON,
variables=None,
blocking=True,
validate_config=False,
)
async def test_turn_off(player, state):
"""Test command to turn off."""
await player.async_turn_off()
@ -110,7 +92,7 @@ async def test_mute_volume(player, state, mute):
async def test_name(player):
"""Test name."""
assert player.name == MOCK_NAME
assert player.name == f"{MOCK_NAME} - Zone: 1"
async def test_update(player, state):
@ -138,9 +120,15 @@ async def test_2ch(player, state, fmt, result):
"source, value",
[("PVR", SourceCodes.PVR), ("BD", SourceCodes.BD), ("INVALID", None)],
)
async def test_select_source(player, state, source, value):
async def test_select_source(hass, player_setup, state, source, value):
"""Test selection of source."""
await player.async_select_source(source)
await hass.services.async_call(
"media_player",
SERVICE_SELECT_SOURCE,
service_data={ATTR_ENTITY_ID: player_setup, ATTR_INPUT_SOURCE: source},
blocking=True,
)
if value:
state.set_source.assert_called_with(value)
else: