"""Tests for Plex setup.""" import copy from datetime import timedelta from http import HTTPStatus import ssl from unittest.mock import patch import plexapi import requests import homeassistant.components.plex.const as const from homeassistant.components.plex.models import ( LIVE_TV_SECTION, TRANSIENT_SECTION, UNKNOWN_SECTION, ) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, STATE_IDLE, STATE_PLAYING, ) import homeassistant.util.dt as dt_util from .const import DEFAULT_DATA, DEFAULT_OPTIONS, PLEX_DIRECT_URL from .helpers import trigger_plex_update, wait_for_debouncer from tests.common import MockConfigEntry, async_fire_time_changed async def test_set_config_entry_unique_id(hass, entry, mock_plex_server): """Test updating missing unique_id from config entry.""" assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED assert ( hass.config_entries.async_entries(const.DOMAIN)[0].unique_id == mock_plex_server.machine_identifier ) async def test_setup_config_entry_with_error(hass, entry): """Test setup component from config entry with errors.""" with patch( "homeassistant.components.plex.PlexServer.connect", side_effect=requests.exceptions.ConnectionError, ): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) is False await hass.async_block_till_done() assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.SETUP_RETRY with patch( "homeassistant.components.plex.PlexServer.connect", side_effect=plexapi.exceptions.BadRequest, ): next_update = dt_util.utcnow() + timedelta(seconds=30) async_fire_time_changed(hass, next_update) await hass.async_block_till_done() assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.SETUP_RETRY async def test_setup_with_insecure_config_entry(hass, entry, setup_plex_server): """Test setup component with config.""" INSECURE_DATA = copy.deepcopy(DEFAULT_DATA) INSECURE_DATA[const.PLEX_SERVER_CONFIG][CONF_VERIFY_SSL] = False entry.data = INSECURE_DATA await setup_plex_server(config_entry=entry) assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED async def test_unload_config_entry(hass, entry, mock_plex_server): """Test unloading a config entry.""" config_entries = hass.config_entries.async_entries(const.DOMAIN) assert len(config_entries) == 1 assert entry is config_entries[0] assert entry.state is ConfigEntryState.LOADED server_id = mock_plex_server.machine_identifier loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] assert loaded_server == mock_plex_server websocket = hass.data[const.DOMAIN][const.WEBSOCKETS][server_id] await hass.config_entries.async_unload(entry.entry_id) assert websocket.close.called assert entry.state is ConfigEntryState.NOT_LOADED async def test_setup_with_photo_session(hass, entry, setup_plex_server): """Test setup component with config.""" await setup_plex_server(session_type="photo") assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED await hass.async_block_till_done() media_player = hass.states.get( "media_player.plex_plex_for_android_tv_shield_android_tv" ) assert media_player.state == STATE_IDLE await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") assert sensor.state == "0" async def test_setup_with_live_tv_session(hass, entry, setup_plex_server): """Test setup component with a Live TV session.""" await setup_plex_server(session_type="live_tv") assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED await hass.async_block_till_done() media_player = hass.states.get( "media_player.plex_plex_for_android_tv_shield_android_tv" ) assert media_player.state == STATE_PLAYING assert media_player.attributes["media_library_title"] == LIVE_TV_SECTION await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") assert sensor.state == "1" async def test_setup_with_transient_session(hass, entry, setup_plex_server): """Test setup component with a transient session.""" await setup_plex_server(session_type="transient") assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED await hass.async_block_till_done() media_player = hass.states.get( "media_player.plex_plex_for_android_tv_shield_android_tv" ) assert media_player.state == STATE_PLAYING assert media_player.attributes["media_library_title"] == TRANSIENT_SECTION await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") assert sensor.state == "1" async def test_setup_with_unknown_session(hass, entry, setup_plex_server): """Test setup component with an unknown session.""" await setup_plex_server(session_type="unknown") assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED await hass.async_block_till_done() media_player = hass.states.get( "media_player.plex_plex_for_android_tv_shield_android_tv" ) assert media_player.state == STATE_PLAYING assert media_player.attributes["media_library_title"] == UNKNOWN_SECTION await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") assert sensor.state == "1" async def test_setup_when_certificate_changed( hass, requests_mock, empty_library, empty_payload, plex_server_accounts, plex_server_default, plextv_account, plextv_resources, plextv_shared_users, mock_websocket, ): """Test setup component when the Plex certificate has changed.""" class WrongCertHostnameException(requests.exceptions.SSLError): """Mock the exception showing a mismatched hostname.""" def __init__(self): self.__context__ = ssl.SSLCertVerificationError( f"hostname '{old_domain}' doesn't match" ) old_domain = "1-2-3-4.1111111111ffffff1111111111ffffff.plex.direct" old_url = f"https://{old_domain}:32400" OLD_HOSTNAME_DATA = copy.deepcopy(DEFAULT_DATA) OLD_HOSTNAME_DATA[const.PLEX_SERVER_CONFIG][CONF_URL] = old_url old_entry = MockConfigEntry( domain=const.DOMAIN, data=OLD_HOSTNAME_DATA, options=DEFAULT_OPTIONS, unique_id=DEFAULT_DATA["server_id"], ) requests_mock.get("https://plex.tv/api/users/", text=plextv_shared_users) requests_mock.get("https://plex.tv/api/invites/requested", text=empty_payload) requests_mock.get(old_url, exc=WrongCertHostnameException) # Test with account failure requests_mock.get( "https://plex.tv/users/account", status_code=HTTPStatus.UNAUTHORIZED ) old_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(old_entry.entry_id) is False await hass.async_block_till_done() assert old_entry.state is ConfigEntryState.SETUP_ERROR await hass.config_entries.async_unload(old_entry.entry_id) # Test with no servers found requests_mock.get("https://plex.tv/users/account", text=plextv_account) requests_mock.get("https://plex.tv/api/resources", text=empty_payload) assert await hass.config_entries.async_setup(old_entry.entry_id) is False await hass.async_block_till_done() assert old_entry.state is ConfigEntryState.SETUP_ERROR await hass.config_entries.async_unload(old_entry.entry_id) # Test with success new_url = PLEX_DIRECT_URL requests_mock.get("https://plex.tv/api/resources", text=plextv_resources) for resource_url in [new_url, "http://1.2.3.4:32400"]: requests_mock.get(resource_url, text=plex_server_default) requests_mock.get(f"{new_url}/accounts", text=plex_server_accounts) requests_mock.get(f"{new_url}/library", text=empty_library) requests_mock.get(f"{new_url}/library/sections", text=empty_payload) assert await hass.config_entries.async_setup(old_entry.entry_id) await hass.async_block_till_done() assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert old_entry.state is ConfigEntryState.LOADED assert old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] == new_url async def test_tokenless_server(entry, setup_plex_server): """Test setup with a server with token auth disabled.""" TOKENLESS_DATA = copy.deepcopy(DEFAULT_DATA) TOKENLESS_DATA[const.PLEX_SERVER_CONFIG].pop(CONF_TOKEN, None) entry.data = TOKENLESS_DATA await setup_plex_server(config_entry=entry) assert entry.state is ConfigEntryState.LOADED async def test_bad_token_with_tokenless_server( hass, entry, mock_websocket, setup_plex_server, requests_mock ): """Test setup with a bad token and a server with token auth disabled.""" requests_mock.get( "https://plex.tv/users/account", status_code=HTTPStatus.UNAUTHORIZED ) await setup_plex_server() assert entry.state is ConfigEntryState.LOADED # Ensure updates that rely on account return nothing trigger_plex_update(mock_websocket) await hass.async_block_till_done() async def test_scan_clients_schedule(hass, setup_plex_server): """Test scan_clients scheduled update.""" with patch( "homeassistant.components.plex.server.PlexServer._async_update_platforms" ) as mock_scan_clients: await setup_plex_server() mock_scan_clients.reset_mock() async_fire_time_changed( hass, dt_util.utcnow() + const.CLIENT_SCAN_INTERVAL, ) await hass.async_block_till_done() assert mock_scan_clients.called