"""Test the Devialet init.""" from unittest.mock import PropertyMock, patch from devialet import DevialetApi from devialet.const import UrlSuffix from yarl import URL from homeassistant.components.devialet.const import DOMAIN from homeassistant.components.devialet.media_player import SUPPORT_DEVIALET from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_ALBUM_NAME, ATTR_MEDIA_ARTIST, ATTR_MEDIA_DURATION, ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT, ATTR_MEDIA_TITLE, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, ATTR_SOUND_MODE_LIST, DOMAIN as MP_DOMAIN, SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, MediaPlayerState, ) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from . import HOST, NAME, setup_integration from tests.test_util.aiohttp import AiohttpClientMocker SERVICE_TO_URL = { SERVICE_MEDIA_SEEK: [UrlSuffix.SEEK], SERVICE_MEDIA_PLAY: [UrlSuffix.PLAY], SERVICE_MEDIA_PAUSE: [UrlSuffix.PAUSE], SERVICE_MEDIA_STOP: [UrlSuffix.PAUSE], SERVICE_MEDIA_PREVIOUS_TRACK: [UrlSuffix.PREVIOUS_TRACK], SERVICE_MEDIA_NEXT_TRACK: [UrlSuffix.NEXT_TRACK], SERVICE_TURN_OFF: [UrlSuffix.TURN_OFF], SERVICE_VOLUME_UP: [UrlSuffix.VOLUME_UP], SERVICE_VOLUME_DOWN: [UrlSuffix.VOLUME_DOWN], SERVICE_VOLUME_SET: [UrlSuffix.VOLUME_SET], SERVICE_VOLUME_MUTE: [UrlSuffix.MUTE, UrlSuffix.UNMUTE], SERVICE_SELECT_SOUND_MODE: [UrlSuffix.EQUALIZER, UrlSuffix.NIGHT_MODE], SERVICE_SELECT_SOURCE: [ str(UrlSuffix.SELECT_SOURCE).replace( "%SOURCE_ID%", "82834351-8255-4e2e-9ce2-b7d4da0aa3b0" ), str(UrlSuffix.SELECT_SOURCE).replace( "%SOURCE_ID%", "07b1bf6d-9216-4a7b-8d53-5590cee21d90" ), ], } SERVICE_TO_DATA = { SERVICE_MEDIA_SEEK: [{"seek_position": 321}], SERVICE_MEDIA_PLAY: [{}], SERVICE_MEDIA_PAUSE: [{}], SERVICE_MEDIA_STOP: [{}], SERVICE_MEDIA_PREVIOUS_TRACK: [{}], SERVICE_MEDIA_NEXT_TRACK: [{}], SERVICE_TURN_OFF: [{}], SERVICE_VOLUME_UP: [{}], SERVICE_VOLUME_DOWN: [{}], SERVICE_VOLUME_SET: [{ATTR_MEDIA_VOLUME_LEVEL: 0.5}], SERVICE_VOLUME_MUTE: [ {ATTR_MEDIA_VOLUME_MUTED: True}, {ATTR_MEDIA_VOLUME_MUTED: False}, ], SERVICE_SELECT_SOUND_MODE: [ {ATTR_SOUND_MODE: "Night mode"}, {ATTR_SOUND_MODE: "Flat"}, ], SERVICE_SELECT_SOURCE: [ {ATTR_INPUT_SOURCE: "Optical left"}, {ATTR_INPUT_SOURCE: "Online"}, ], } async def test_media_player_playing( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the Devialet configuration entry loading and unloading.""" await async_setup_component(hass, "homeassistant", {}) entry = await setup_integration(hass, aioclient_mock) assert entry.entry_id in hass.data[DOMAIN] assert entry.state is ConfigEntryState.LOADED await hass.services.async_call( "homeassistant", SERVICE_UPDATE_ENTITY, {ATTR_ENTITY_ID: [f"{MP_DOMAIN}.{NAME.lower()}"]}, blocking=True, ) state = hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}") assert state.state == MediaPlayerState.PLAYING assert state.name == NAME assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.2 assert state.attributes[ATTR_MEDIA_VOLUME_MUTED] is False assert state.attributes[ATTR_INPUT_SOURCE_LIST] is not None assert state.attributes[ATTR_SOUND_MODE_LIST] is not None assert state.attributes[ATTR_MEDIA_ARTIST] == "The Beatles" assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "1 (Remastered)" assert state.attributes[ATTR_MEDIA_TITLE] == "Hey Jude - Remastered 2015" assert state.attributes[ATTR_ENTITY_PICTURE] is not None assert state.attributes[ATTR_MEDIA_DURATION] == 425653 assert state.attributes[ATTR_MEDIA_POSITION] == 123102 assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] is not None assert state.attributes[ATTR_SUPPORTED_FEATURES] is not None assert state.attributes[ATTR_INPUT_SOURCE] is not None assert state.attributes[ATTR_SOUND_MODE] is not None with patch( "homeassistant.components.devialet.DevialetApi.playing_state", new_callable=PropertyMock, ) as mock: mock.return_value = MediaPlayerState.PAUSED await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").state == MediaPlayerState.PAUSED ) with patch( "homeassistant.components.devialet.DevialetApi.playing_state", new_callable=PropertyMock, ) as mock: mock.return_value = MediaPlayerState.ON await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").state == MediaPlayerState.ON ) with patch.object(DevialetApi, "equalizer", new_callable=PropertyMock) as mock: mock.return_value = None with patch.object(DevialetApi, "night_mode", new_callable=PropertyMock) as mock: mock.return_value = True await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").attributes[ ATTR_SOUND_MODE ] == "Night mode" ) with patch.object(DevialetApi, "equalizer", new_callable=PropertyMock) as mock: mock.return_value = "unexpected_value" with patch.object(DevialetApi, "night_mode", new_callable=PropertyMock) as mock: mock.return_value = False await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( ATTR_SOUND_MODE not in hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").attributes ) with patch.object(DevialetApi, "equalizer", new_callable=PropertyMock) as mock: mock.return_value = None with patch.object(DevialetApi, "night_mode", new_callable=PropertyMock) as mock: mock.return_value = None await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( ATTR_SOUND_MODE not in hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").attributes ) with patch.object( DevialetApi, "available_options", new_callable=PropertyMock ) as mock: mock.return_value = None await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").attributes[ ATTR_SUPPORTED_FEATURES ] == SUPPORT_DEVIALET ) with patch.object(DevialetApi, "source", new_callable=PropertyMock) as mock: mock.return_value = "someSource" await hass.config_entries.async_reload(entry.entry_id) await hass.async_block_till_done() assert ( ATTR_INPUT_SOURCE not in hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}").attributes ) await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() assert entry.entry_id not in hass.data[DOMAIN] assert entry.state is ConfigEntryState.NOT_LOADED async def test_media_player_offline( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the Devialet configuration entry loading and unloading.""" entry = await setup_integration(hass, aioclient_mock, state=STATE_UNAVAILABLE) assert entry.entry_id in hass.data[DOMAIN] assert entry.state is ConfigEntryState.LOADED state = hass.states.get(f"{MP_DOMAIN}.{NAME.lower()}") assert state.state == STATE_UNAVAILABLE assert state.name == NAME await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() assert entry.entry_id not in hass.data[DOMAIN] assert entry.state is ConfigEntryState.NOT_LOADED async def test_media_player_without_serial( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the Devialet configuration entry loading and unloading.""" entry = await setup_integration(hass, aioclient_mock, serial=None) assert entry.entry_id in hass.data[DOMAIN] assert entry.state is ConfigEntryState.LOADED assert entry.unique_id is None await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() assert entry.entry_id not in hass.data[DOMAIN] assert entry.state is ConfigEntryState.NOT_LOADED async def test_media_player_services( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the Devialet services.""" entry = await setup_integration( hass, aioclient_mock, state=MediaPlayerState.PLAYING ) assert entry.entry_id in hass.data[DOMAIN] assert entry.state is ConfigEntryState.LOADED target = {ATTR_ENTITY_ID: hass.states.get(f"{MP_DOMAIN}.{NAME}").entity_id} for i, (service, urls) in enumerate(SERVICE_TO_URL.items()): for url in urls: aioclient_mock.post(f"http://{HOST}{url}") for data_set in list(SERVICE_TO_DATA.values())[i]: service_data = target.copy() service_data.update(data_set) await hass.services.async_call( MP_DOMAIN, service, service_data=service_data, blocking=True, ) await hass.async_block_till_done() for url in urls: call_available = False for item in aioclient_mock.mock_calls: if item[0] == "POST" and item[1] == URL(f"http://{HOST}{url}"): call_available = True break assert call_available await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() assert entry.entry_id not in hass.data[DOMAIN] assert entry.state is ConfigEntryState.NOT_LOADED