2020-03-11 16:37:02 +00:00
|
|
|
"""Tests for Plex setup."""
|
|
|
|
import copy
|
|
|
|
from datetime import timedelta
|
2020-03-29 04:02:29 +00:00
|
|
|
import ssl
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
from asynctest import ClockedTestCase, patch
|
2020-03-11 16:37:02 +00:00
|
|
|
import plexapi
|
2020-04-09 22:49:09 +00:00
|
|
|
import pytest
|
2020-03-11 16:37:02 +00:00
|
|
|
import requests
|
|
|
|
|
|
|
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
|
|
|
import homeassistant.components.plex.const as const
|
|
|
|
from homeassistant.config_entries import (
|
|
|
|
ENTRY_STATE_LOADED,
|
|
|
|
ENTRY_STATE_NOT_LOADED,
|
|
|
|
ENTRY_STATE_SETUP_ERROR,
|
|
|
|
ENTRY_STATE_SETUP_RETRY,
|
|
|
|
)
|
|
|
|
from homeassistant.const import (
|
|
|
|
CONF_HOST,
|
|
|
|
CONF_PORT,
|
|
|
|
CONF_SSL,
|
|
|
|
CONF_TOKEN,
|
2020-03-29 04:02:29 +00:00
|
|
|
CONF_URL,
|
2020-03-11 16:37:02 +00:00
|
|
|
CONF_VERIFY_SSL,
|
|
|
|
)
|
2020-04-09 22:49:09 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
2020-03-11 16:37:02 +00:00
|
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
|
|
|
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
|
2020-03-29 04:02:29 +00:00
|
|
|
from .mock_classes import MockPlexAccount, MockPlexServer
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
from tests.common import (
|
|
|
|
MockConfigEntry,
|
|
|
|
async_fire_time_changed,
|
|
|
|
async_test_home_assistant,
|
|
|
|
)
|
2020-03-11 16:37:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_setup_with_config(hass):
|
|
|
|
"""Test setup component with config."""
|
|
|
|
config = {
|
|
|
|
const.DOMAIN: {
|
|
|
|
CONF_HOST: MOCK_SERVERS[0][CONF_HOST],
|
|
|
|
CONF_PORT: MOCK_SERVERS[0][CONF_PORT],
|
|
|
|
CONF_TOKEN: MOCK_TOKEN,
|
|
|
|
CONF_SSL: True,
|
|
|
|
CONF_VERIFY_SSL: True,
|
|
|
|
MP_DOMAIN: {
|
|
|
|
const.CONF_IGNORE_NEW_SHARED_USERS: False,
|
|
|
|
const.CONF_USE_EPISODE_ART: False,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_plex_server = MockPlexServer()
|
|
|
|
|
|
|
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
|
|
|
"homeassistant.components.plex.PlexWebsocket.listen"
|
|
|
|
) as mock_listen:
|
|
|
|
assert await async_setup_component(hass, const.DOMAIN, config) is True
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert mock_listen.called
|
|
|
|
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
|
|
|
|
entry = hass.config_entries.async_entries(const.DOMAIN)[0]
|
|
|
|
assert entry.state == ENTRY_STATE_LOADED
|
|
|
|
|
|
|
|
server_id = mock_plex_server.machineIdentifier
|
|
|
|
loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id]
|
|
|
|
|
|
|
|
assert loaded_server.plex_server == mock_plex_server
|
|
|
|
|
|
|
|
|
2020-04-28 17:31:22 +00:00
|
|
|
@pytest.mark.skip
|
2020-04-09 22:49:09 +00:00
|
|
|
class TestClockedPlex(ClockedTestCase):
|
|
|
|
"""Create clock-controlled asynctest class."""
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
@pytest.fixture(autouse=True)
|
2020-04-28 17:31:22 +00:00
|
|
|
def inject_fixture(self, caplog, hass_storage):
|
2020-04-09 22:49:09 +00:00
|
|
|
"""Inject pytest fixtures as instance attributes."""
|
|
|
|
self.caplog = caplog
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
async def setUp(self):
|
|
|
|
"""Initialize this test class."""
|
|
|
|
self.hass = await async_test_home_assistant(self.loop)
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
async def tearDown(self):
|
|
|
|
"""Clean up the HomeAssistant instance."""
|
|
|
|
await self.hass.async_stop()
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
async def test_setup_with_config_entry(self):
|
|
|
|
"""Test setup component with config."""
|
|
|
|
hass = self.hass
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
mock_plex_server = MockPlexServer()
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN,
|
|
|
|
data=DEFAULT_DATA,
|
|
|
|
options=DEFAULT_OPTIONS,
|
|
|
|
unique_id=DEFAULT_DATA["server_id"],
|
|
|
|
)
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
|
|
|
"homeassistant.components.plex.PlexWebsocket.listen"
|
|
|
|
) as mock_listen:
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
assert mock_listen.called
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
|
|
|
|
assert entry.state == ENTRY_STATE_LOADED
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
server_id = mock_plex_server.machineIdentifier
|
|
|
|
loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id]
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
assert loaded_server.plex_server == mock_plex_server
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
async_dispatcher_send(
|
|
|
|
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
2020-03-11 16:37:02 +00:00
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
sensor = hass.states.get("sensor.plex_plex_server_1")
|
|
|
|
assert sensor.state == str(len(mock_plex_server.accounts))
|
|
|
|
|
|
|
|
# Ensure existing entities refresh
|
|
|
|
await self.advance(const.DEBOUNCE_TIMEOUT)
|
|
|
|
async_dispatcher_send(
|
|
|
|
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
|
2020-04-05 06:21:20 +00:00
|
|
|
)
|
2020-04-09 22:49:09 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
for test_exception in (
|
|
|
|
plexapi.exceptions.BadRequest,
|
|
|
|
requests.exceptions.RequestException,
|
|
|
|
):
|
|
|
|
with patch.object(
|
|
|
|
mock_plex_server, "clients", side_effect=test_exception
|
|
|
|
) as patched_clients_bad_request:
|
|
|
|
await self.advance(const.DEBOUNCE_TIMEOUT)
|
|
|
|
async_dispatcher_send(
|
|
|
|
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert patched_clients_bad_request.called
|
|
|
|
assert (
|
|
|
|
f"Could not connect to Plex server: {mock_plex_server.friendlyName}"
|
|
|
|
in self.caplog.text
|
|
|
|
)
|
|
|
|
self.caplog.clear()
|
2020-03-11 16:37:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_set_config_entry_unique_id(hass):
|
|
|
|
"""Test updating missing unique_id from config entry."""
|
|
|
|
|
|
|
|
mock_plex_server = MockPlexServer()
|
|
|
|
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN, data=DEFAULT_DATA, options=DEFAULT_OPTIONS, unique_id=None,
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
|
|
|
"homeassistant.components.plex.PlexWebsocket.listen"
|
|
|
|
) as mock_listen:
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert mock_listen.called
|
|
|
|
|
|
|
|
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
|
|
|
|
assert entry.state == ENTRY_STATE_LOADED
|
|
|
|
|
|
|
|
assert (
|
|
|
|
hass.config_entries.async_entries(const.DOMAIN)[0].unique_id
|
|
|
|
== mock_plex_server.machineIdentifier
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_setup_config_entry_with_error(hass):
|
|
|
|
"""Test setup component from config entry with errors."""
|
|
|
|
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN,
|
|
|
|
data=DEFAULT_DATA,
|
|
|
|
options=DEFAULT_OPTIONS,
|
|
|
|
unique_id=DEFAULT_DATA["server_id"],
|
|
|
|
)
|
|
|
|
|
|
|
|
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 == ENTRY_STATE_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 == ENTRY_STATE_SETUP_ERROR
|
|
|
|
|
|
|
|
|
|
|
|
async def test_setup_with_insecure_config_entry(hass):
|
|
|
|
"""Test setup component with config."""
|
|
|
|
|
|
|
|
mock_plex_server = MockPlexServer()
|
|
|
|
|
|
|
|
INSECURE_DATA = copy.deepcopy(DEFAULT_DATA)
|
|
|
|
INSECURE_DATA[const.PLEX_SERVER_CONFIG][CONF_VERIFY_SSL] = False
|
|
|
|
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN,
|
|
|
|
data=INSECURE_DATA,
|
|
|
|
options=DEFAULT_OPTIONS,
|
|
|
|
unique_id=DEFAULT_DATA["server_id"],
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
|
|
|
"homeassistant.components.plex.PlexWebsocket.listen"
|
|
|
|
) as mock_listen:
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert mock_listen.called
|
|
|
|
|
|
|
|
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
|
|
|
|
assert entry.state == ENTRY_STATE_LOADED
|
|
|
|
|
|
|
|
|
|
|
|
async def test_unload_config_entry(hass):
|
|
|
|
"""Test unloading a config entry."""
|
|
|
|
mock_plex_server = MockPlexServer()
|
|
|
|
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN,
|
|
|
|
data=DEFAULT_DATA,
|
|
|
|
options=DEFAULT_OPTIONS,
|
|
|
|
unique_id=DEFAULT_DATA["server_id"],
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
config_entries = hass.config_entries.async_entries(const.DOMAIN)
|
|
|
|
assert len(config_entries) == 1
|
|
|
|
assert entry is config_entries[0]
|
|
|
|
|
|
|
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
|
|
|
"homeassistant.components.plex.PlexWebsocket.listen"
|
|
|
|
) as mock_listen:
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_listen.called
|
|
|
|
|
|
|
|
assert entry.state == ENTRY_STATE_LOADED
|
|
|
|
|
|
|
|
server_id = mock_plex_server.machineIdentifier
|
|
|
|
loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id]
|
|
|
|
|
|
|
|
assert loaded_server.plex_server == mock_plex_server
|
|
|
|
|
|
|
|
with patch("homeassistant.components.plex.PlexWebsocket.close") as mock_close:
|
|
|
|
await hass.config_entries.async_unload(entry.entry_id)
|
|
|
|
assert mock_close.called
|
|
|
|
|
|
|
|
assert entry.state == ENTRY_STATE_NOT_LOADED
|
|
|
|
|
|
|
|
|
|
|
|
async def test_setup_with_photo_session(hass):
|
|
|
|
"""Test setup component with config."""
|
|
|
|
|
|
|
|
mock_plex_server = MockPlexServer(session_type="photo")
|
|
|
|
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN,
|
|
|
|
data=DEFAULT_DATA,
|
|
|
|
options=DEFAULT_OPTIONS,
|
|
|
|
unique_id=DEFAULT_DATA["server_id"],
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
|
|
|
"homeassistant.components.plex.PlexWebsocket.listen"
|
|
|
|
):
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
|
|
|
|
assert entry.state == ENTRY_STATE_LOADED
|
|
|
|
|
|
|
|
server_id = mock_plex_server.machineIdentifier
|
|
|
|
|
2020-04-09 22:49:09 +00:00
|
|
|
async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
|
|
|
|
await hass.async_block_till_done()
|
2020-03-11 16:37:02 +00:00
|
|
|
|
|
|
|
media_player = hass.states.get("media_player.plex_product_title")
|
|
|
|
assert media_player.state == "idle"
|
|
|
|
|
|
|
|
sensor = hass.states.get("sensor.plex_plex_server_1")
|
|
|
|
assert sensor.state == str(len(mock_plex_server.accounts))
|
2020-03-29 04:02:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_setup_when_certificate_changed(hass):
|
|
|
|
"""Test setup component when the Plex certificate has changed."""
|
|
|
|
|
|
|
|
old_domain = "1-2-3-4.1234567890abcdef1234567890abcdef.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
|
|
|
|
|
|
|
|
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_entry = MockConfigEntry(
|
|
|
|
domain=const.DOMAIN,
|
|
|
|
data=OLD_HOSTNAME_DATA,
|
|
|
|
options=DEFAULT_OPTIONS,
|
|
|
|
unique_id=DEFAULT_DATA["server_id"],
|
|
|
|
)
|
|
|
|
|
|
|
|
new_entry = MockConfigEntry(domain=const.DOMAIN, data=DEFAULT_DATA)
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
|
|
|
|
), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
|
|
|
|
old_entry.add_to_hass(hass)
|
|
|
|
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 == ENTRY_STATE_LOADED
|
|
|
|
|
|
|
|
assert (
|
|
|
|
old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL]
|
|
|
|
== new_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL]
|
|
|
|
)
|