diff --git a/tests/components/arcam_fmj/conftest.py b/tests/components/arcam_fmj/conftest.py new file mode 100644 index 00000000000..10405dbeb11 --- /dev/null +++ b/tests/components/arcam_fmj/conftest.py @@ -0,0 +1,52 @@ +"""Tests for the arcam_fmj component.""" +from arcam.fmj.client import Client +from arcam.fmj.state import State +from asynctest import Mock +import pytest + +from homeassistant.components.arcam_fmj import DEVICE_SCHEMA +from homeassistant.components.arcam_fmj.const import DOMAIN +from homeassistant.components.arcam_fmj.media_player import ArcamFmj +from homeassistant.const import CONF_HOST, CONF_PORT + +MOCK_HOST = "127.0.0.1" +MOCK_PORT = 1234 +MOCK_TURN_ON = { + "service": "switch.turn_on", + "data": {"entity_id": "switch.test"}, +} +MOCK_NAME = "dummy" +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}]} + + +@pytest.fixture(name="client") +def client_fixture(): + """Get a mocked client.""" + client = Mock(Client) + client.host = MOCK_HOST + client.port = MOCK_PORT + return client + + +@pytest.fixture(name="state") +def state_fixture(client): + """Get a mocked state.""" + state = Mock(State) + state.client = client + state.zn = 1 + state.get_power.return_value = True + return state + + +@pytest.fixture(name="player") +def player_fixture(hass, state): + """Get standard player.""" + player = ArcamFmj(state, MOCK_NAME, None) + player.async_schedule_update_ha_state = Mock() + return player diff --git a/tests/components/arcam_fmj/test_config_flow.py b/tests/components/arcam_fmj/test_config_flow.py index 54fb34443a5..6df280fa92e 100644 --- a/tests/components/arcam_fmj/test_config_flow.py +++ b/tests/components/arcam_fmj/test_config_flow.py @@ -1,41 +1,37 @@ """Tests for the Arcam FMJ config flow module.""" + import pytest + from homeassistant import data_entry_flow -from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.components.arcam_fmj.config_flow import ArcamFmjFlowHandler +from homeassistant.components.arcam_fmj.const import DOMAIN -from tests.common import MockConfigEntry, MockDependency +from .conftest import MOCK_CONFIG, MOCK_NAME -with MockDependency("arcam"), MockDependency("arcam.fmj"), MockDependency( - "arcam.fmj.client" -): - from homeassistant.components.arcam_fmj import DEVICE_SCHEMA - from homeassistant.components.arcam_fmj.config_flow import ArcamFmjFlowHandler - from homeassistant.components.arcam_fmj.const import DOMAIN +from tests.common import MockConfigEntry - MOCK_HOST = "127.0.0.1" - MOCK_PORT = 1234 - MOCK_NAME = "Arcam FMJ" - MOCK_CONFIG = DEVICE_SCHEMA({CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}) - @pytest.fixture(name="config_entry") - def config_entry_fixture(): - """Create a mock HEOS config entry.""" - return MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, title=MOCK_NAME) +@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) - 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" - 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) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == MOCK_NAME - assert result["data"] == MOCK_CONFIG +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" + + +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) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Arcam FMJ" + assert result["data"] == MOCK_CONFIG diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py new file mode 100644 index 00000000000..2d2c14f8e18 --- /dev/null +++ b/tests/components/arcam_fmj/test_media_player.py @@ -0,0 +1,348 @@ +"""Tests for arcam fmj receivers.""" +from math import isclose + +from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceCodes +from asynctest.mock import ANY, MagicMock, Mock, PropertyMock, patch +import pytest + +from homeassistant.components.arcam_fmj.const import ( + DOMAIN, + SIGNAL_CLIENT_DATA, + SIGNAL_CLIENT_STARTED, + SIGNAL_CLIENT_STOPPED, +) +from homeassistant.components.arcam_fmj.media_player import ArcamFmj +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant + +from .conftest import MOCK_HOST, MOCK_NAME, MOCK_PORT + +MOCK_TURN_ON = { + "service": "switch.turn_on", + "data": {"entity_id": "switch.test"}, +} + + +async def test_properties(player, state): + """Test standard properties.""" + assert player.unique_id is None + assert player.device_info == { + "identifiers": {(DOMAIN, MOCK_HOST, MOCK_PORT)}, + "model": "FMJ", + "manufacturer": "Arcam", + } + assert not player.should_poll + + +async def test_powered_off(player, state): + """Test properties in powered off state.""" + state.get_source.return_value = None + state.get_power.return_value = None + assert player.source is None + assert player.state == STATE_OFF + + +async def test_powered_on(player, state): + """Test properties in powered on state.""" + state.get_source.return_value = SourceCodes.PVR + state.get_power.return_value = True + assert player.source == "PVR" + assert player.state == STATE_ON + + +async def test_supported_features_no_service(player, state): + """Test support when turn on service exist.""" + state.get_power.return_value = None + assert player.supported_features == 68876 + + state.get_power.return_value = False + assert player.supported_features == 69004 + + +async def test_supported_features_service(hass, state): + """Test support when turn on service exist.""" + player = ArcamFmj(state, "dummy", MOCK_TURN_ON) + state.get_power.return_value = None + assert player.supported_features == 69004 + + state.get_power.return_value = False + assert player.supported_features == 69004 + + +async def test_turn_on_without_service(player, state): + """Test turn on service.""" + state.get_power.return_value = None + await player.async_turn_on() + state.set_power.assert_not_called() + + state.get_power.return_value = False + await player.async_turn_on() + state.set_power.assert_called_with(True) + + +async def test_turn_on_with_service(hass, state): + """Test support when turn on service exist.""" + player = ArcamFmj(state, "dummy", MOCK_TURN_ON) + player.hass = Mock(HomeAssistant) + 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() + state.set_power.assert_called_with(False) + + +@pytest.mark.parametrize("mute", [True, False]) +async def test_mute_volume(player, state, mute): + """Test mute functionallity.""" + await player.async_mute_volume(mute) + state.set_mute.assert_called_with(mute) + player.async_schedule_update_ha_state.assert_called_with() + + +async def test_name(player): + """Test name.""" + assert player.name == MOCK_NAME + + +async def test_update(player, state): + """Test update.""" + await player.async_update() + state.update.assert_called_with() + + +@pytest.mark.parametrize( + "fmt, result", + [ + (None, True), + (IncomingAudioFormat.PCM, True), + (IncomingAudioFormat.ANALOGUE_DIRECT, True), + (IncomingAudioFormat.DOLBY_DIGITAL, False), + ], +) +async def test_2ch(player, state, fmt, result): + """Test selection of 2ch mode.""" + state.get_incoming_audio_format.return_value = (fmt, None) + assert player._get_2ch() == result # pylint: disable=W0212 + + +@pytest.mark.parametrize( + "source, value", + [("PVR", SourceCodes.PVR), ("BD", SourceCodes.BD), ("INVALID", None)], +) +async def test_select_source(player, state, source, value): + """Test selection of source.""" + await player.async_select_source(source) + if value: + state.set_source.assert_called_with(value) + else: + state.set_source.assert_not_called() + + +async def test_source_list(player, state): + """Test source list.""" + state.get_source_list.return_value = [SourceCodes.BD] + assert player.source_list == ["BD"] + + +@pytest.mark.parametrize( + "mode, mode_sel, mode_2ch, mode_mch", + [ + ("STEREO", True, DecodeMode2CH.STEREO, None), + ("STEREO", False, None, None), + ("STEREO", False, None, None), + ], +) +async def test_select_sound_mode(player, state, mode, mode_sel, mode_2ch, mode_mch): + """Test selection sound mode.""" + player._get_2ch = Mock(return_value=mode_sel) # pylint: disable=W0212 + + await player.async_select_sound_mode(mode) + if mode_2ch: + state.set_decode_mode_2ch.assert_called_with(mode_2ch) + else: + state.set_decode_mode_2ch.assert_not_called() + + if mode_mch: + state.set_decode_mode_mch.assert_called_with(mode_mch) + else: + state.set_decode_mode_mch.assert_not_called() + + +async def test_volume_up(player, state): + """Test mute functionallity.""" + await player.async_volume_up() + state.inc_volume.assert_called_with() + player.async_schedule_update_ha_state.assert_called_with() + + +async def test_volume_down(player, state): + """Test mute functionallity.""" + await player.async_volume_down() + state.dec_volume.assert_called_with() + player.async_schedule_update_ha_state.assert_called_with() + + +@pytest.mark.parametrize( + "mode, mode_sel, mode_2ch, mode_mch", + [ + ("STEREO", True, DecodeMode2CH.STEREO, None), + ("STEREO_DOWNMIX", False, None, DecodeModeMCH.STEREO_DOWNMIX), + (None, False, None, None), + ], +) +async def test_sound_mode(player, state, mode, mode_sel, mode_2ch, mode_mch): + """Test selection sound mode.""" + player._get_2ch = Mock(return_value=mode_sel) # pylint: disable=W0212 + state.get_decode_mode_2ch.return_value = mode_2ch + state.get_decode_mode_mch.return_value = mode_mch + + assert player.sound_mode == mode + + +async def test_sound_mode_list(player, state): + """Test sound mode list.""" + player._get_2ch = Mock(return_value=True) # pylint: disable=W0212 + assert sorted(player.sound_mode_list) == sorted([x.name for x in DecodeMode2CH]) + player._get_2ch = Mock(return_value=False) # pylint: disable=W0212 + assert sorted(player.sound_mode_list) == sorted([x.name for x in DecodeModeMCH]) + + +async def test_sound_mode_zone_x(player, state): + """Test second zone sound mode.""" + state.zn = 2 + assert player.sound_mode is None + assert player.sound_mode_list is None + + +async def test_is_volume_muted(player, state): + """Test muted.""" + state.get_mute.return_value = True + assert player.is_volume_muted is True # pylint: disable=singleton-comparison + state.get_mute.return_value = False + assert player.is_volume_muted is False # pylint: disable=singleton-comparison + state.get_mute.return_value = None + assert player.is_volume_muted is None + + +async def test_volume_level(player, state): + """Test volume.""" + state.get_volume.return_value = 0 + assert isclose(player.volume_level, 0.0) + state.get_volume.return_value = 50 + assert isclose(player.volume_level, 50.0 / 99) + state.get_volume.return_value = 99 + assert isclose(player.volume_level, 1.0) + state.get_volume.return_value = None + assert player.volume_level is None + + +@pytest.mark.parametrize("volume, call", [(0.0, 0), (0.5, 50), (1.0, 99)]) +async def test_set_volume_level(player, state, volume, call): + """Test setting volume.""" + await player.async_set_volume_level(volume) + state.set_volume.assert_called_with(call) + + +@pytest.mark.parametrize( + "source, media_content_type", + [ + (SourceCodes.DAB, MEDIA_TYPE_MUSIC), + (SourceCodes.FM, MEDIA_TYPE_MUSIC), + (SourceCodes.PVR, None), + (None, None), + ], +) +async def test_media_content_type(player, state, source, media_content_type): + """Test content type deduction.""" + state.get_source.return_value = source + assert player.media_content_type == media_content_type + + +@pytest.mark.parametrize( + "source, dab, rds, channel", + [ + (SourceCodes.DAB, "dab", "rds", "dab"), + (SourceCodes.DAB, None, None, None), + (SourceCodes.FM, "dab", "rds", "rds"), + (SourceCodes.FM, None, None, None), + (SourceCodes.PVR, "dab", "rds", None), + ], +) +async def test_media_channel(player, state, source, dab, rds, channel): + """Test media channel.""" + state.get_dab_station.return_value = dab + state.get_rds_information.return_value = rds + state.get_source.return_value = source + assert player.media_channel == channel + + +@pytest.mark.parametrize( + "source, dls, artist", + [ + (SourceCodes.DAB, "dls", "dls"), + (SourceCodes.FM, "dls", None), + (SourceCodes.DAB, None, None), + ], +) +async def test_media_artist(player, state, source, dls, artist): + """Test media artist.""" + state.get_dls_pdt.return_value = dls + state.get_source.return_value = source + assert player.media_artist == artist + + +@pytest.mark.parametrize( + "source, channel, title", + [ + (SourceCodes.DAB, "channel", "DAB - channel"), + (SourceCodes.DAB, None, "DAB"), + (None, None, None), + ], +) +async def test_media_title(player, state, source, channel, title): + """Test media title.""" + state.get_source.return_value = source + with patch.object( + ArcamFmj, "media_channel", new_callable=PropertyMock + ) as media_channel: + media_channel.return_value = channel + assert player.media_title == title + + +async def test_added_to_hass(player, state): + """Test addition to hass.""" + connectors = {} + + def _connect(signal, fun): + connectors[signal] = fun + + player.hass = MagicMock() + player.hass.helpers.dispatcher.async_dispatcher_connect.side_effects = _connect + + await player.async_added_to_hass() + state.start.assert_called_with() + player.hass.helpers.dispatcher.async_dispatcher_connect.assert_any_call( + SIGNAL_CLIENT_DATA, ANY + ) + player.hass.helpers.dispatcher.async_dispatcher_connect.assert_any_call( + SIGNAL_CLIENT_STARTED, ANY + ) + player.hass.helpers.dispatcher.async_dispatcher_connect.assert_any_call( + SIGNAL_CLIENT_STOPPED, ANY + )