From 9639968a9673e660be39db5a29bca2038629a61d Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 22 Jan 2022 14:31:34 -0600 Subject: [PATCH] Refactor roku tests (#64498) --- tests/components/roku/__init__.py | 181 ------ tests/components/roku/conftest.py | 86 +++ .../roku/fixtures/active-app-netflix.xml | 4 - .../roku/fixtures/active-app-pluto.xml | 4 - .../roku/fixtures/active-app-roku.xml | 4 - .../roku/fixtures/active-app-screensaver.xml | 5 - .../roku/fixtures/active-app-tvinput-dtv.xml | 4 - tests/components/roku/fixtures/apps-tv.xml | 14 - tests/components/roku/fixtures/apps.xml | 11 - .../roku/fixtures/media-player-close.xml | 5 - .../roku/fixtures/media-player-live.xml | 12 - .../roku/fixtures/media-player-pause.xml | 12 - .../roku/fixtures/media-player-play.xml | 12 - tests/components/roku/fixtures/roku3-app.json | 93 +++ .../fixtures/roku3-device-info-power-off.xml | 35 - .../roku/fixtures/roku3-device-info.xml | 35 - .../components/roku/fixtures/roku3-idle.json | 90 +++ .../roku/fixtures/roku3-media-paused.json | 116 ++++ .../roku/fixtures/roku3-media-playing.json | 116 ++++ .../roku/fixtures/roku3-screensaver.json | 96 +++ tests/components/roku/fixtures/roku3.json | 90 +++ .../roku/fixtures/rokutv-7820x.json | 184 ++++++ .../fixtures/rokutv-device-info-power-off.xml | 72 --- .../roku/fixtures/rokutv-device-info.xml | 73 --- .../fixtures/rokutv-tv-active-channel.xml | 24 - .../roku/fixtures/rokutv-tv-channels.xml | 15 - tests/components/roku/test_binary_sensor.py | 30 +- tests/components/roku/test_config_flow.py | 162 ++--- tests/components/roku/test_init.py | 60 +- tests/components/roku/test_media_player.py | 600 ++++++++---------- tests/components/roku/test_remote.py | 67 +- tests/components/roku/test_sensor.py | 29 +- 32 files changed, 1305 insertions(+), 1036 deletions(-) create mode 100644 tests/components/roku/conftest.py delete mode 100644 tests/components/roku/fixtures/active-app-netflix.xml delete mode 100644 tests/components/roku/fixtures/active-app-pluto.xml delete mode 100644 tests/components/roku/fixtures/active-app-roku.xml delete mode 100644 tests/components/roku/fixtures/active-app-screensaver.xml delete mode 100644 tests/components/roku/fixtures/active-app-tvinput-dtv.xml delete mode 100644 tests/components/roku/fixtures/apps-tv.xml delete mode 100644 tests/components/roku/fixtures/apps.xml delete mode 100644 tests/components/roku/fixtures/media-player-close.xml delete mode 100644 tests/components/roku/fixtures/media-player-live.xml delete mode 100644 tests/components/roku/fixtures/media-player-pause.xml delete mode 100644 tests/components/roku/fixtures/media-player-play.xml create mode 100644 tests/components/roku/fixtures/roku3-app.json delete mode 100644 tests/components/roku/fixtures/roku3-device-info-power-off.xml delete mode 100644 tests/components/roku/fixtures/roku3-device-info.xml create mode 100644 tests/components/roku/fixtures/roku3-idle.json create mode 100644 tests/components/roku/fixtures/roku3-media-paused.json create mode 100644 tests/components/roku/fixtures/roku3-media-playing.json create mode 100644 tests/components/roku/fixtures/roku3-screensaver.json create mode 100644 tests/components/roku/fixtures/roku3.json create mode 100644 tests/components/roku/fixtures/rokutv-7820x.json delete mode 100644 tests/components/roku/fixtures/rokutv-device-info-power-off.xml delete mode 100644 tests/components/roku/fixtures/rokutv-device-info.xml delete mode 100644 tests/components/roku/fixtures/rokutv-tv-active-channel.xml delete mode 100644 tests/components/roku/fixtures/rokutv-tv-channels.xml diff --git a/tests/components/roku/__init__.py b/tests/components/roku/__init__.py index 5ae81eb7b72..c0e044a3589 100644 --- a/tests/components/roku/__init__.py +++ b/tests/components/roku/__init__.py @@ -1,16 +1,6 @@ """Tests for the Roku component.""" -from http import HTTPStatus -import re -from socket import gaierror as SocketGIAError - from homeassistant.components import ssdp, zeroconf -from homeassistant.components.roku.const import DOMAIN from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_SERIAL -from homeassistant.const import CONF_HOST -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry, load_fixture -from tests.test_util.aiohttp import AiohttpClientMocker NAME = "Roku 3" NAME_ROKUTV = '58" Onn Roku TV' @@ -42,174 +32,3 @@ MOCK_HOMEKIT_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( }, type="mock_type", ) - - -def mock_connection( - aioclient_mock: AiohttpClientMocker, - device: str = "roku3", - app: str = "roku", - host: str = HOST, - power: bool = True, - media_state: str = "close", - error: bool = False, - server_error: bool = False, -) -> None: - """Mock the Roku connection.""" - roku_url = f"http://{host}:8060" - - if error: - mock_connection_error( - aioclient_mock=aioclient_mock, device=device, app=app, host=host - ) - return - - if server_error: - mock_connection_server_error( - aioclient_mock=aioclient_mock, device=device, app=app, host=host - ) - return - - info_fixture = f"roku/{device}-device-info.xml" - if not power: - info_fixture = f"roku/{device}-device-info-power-off.xml" - - aioclient_mock.get( - f"{roku_url}/query/device-info", - text=load_fixture(info_fixture), - headers={"Content-Type": "text/xml"}, - ) - - apps_fixture = "roku/apps.xml" - if device == "rokutv": - apps_fixture = "roku/apps-tv.xml" - - aioclient_mock.get( - f"{roku_url}/query/apps", - text=load_fixture(apps_fixture), - headers={"Content-Type": "text/xml"}, - ) - - aioclient_mock.get( - f"{roku_url}/query/active-app", - text=load_fixture(f"roku/active-app-{app}.xml"), - headers={"Content-Type": "text/xml"}, - ) - - aioclient_mock.get( - f"{roku_url}/query/tv-active-channel", - text=load_fixture("roku/rokutv-tv-active-channel.xml"), - headers={"Content-Type": "text/xml"}, - ) - - aioclient_mock.get( - f"{roku_url}/query/tv-channels", - text=load_fixture("roku/rokutv-tv-channels.xml"), - headers={"Content-Type": "text/xml"}, - ) - - aioclient_mock.get( - f"{roku_url}/query/media-player", - text=load_fixture(f"roku/media-player-{media_state}.xml"), - headers={"Content-Type": "text/xml"}, - ) - - aioclient_mock.post( - re.compile(f"{roku_url}/keypress/.*"), - text="OK", - ) - - aioclient_mock.post( - re.compile(f"{roku_url}/launch/.*"), - text="OK", - ) - - aioclient_mock.post(f"{roku_url}/search", text="OK") - - -def mock_connection_error( - aioclient_mock: AiohttpClientMocker, - device: str = "roku3", - app: str = "roku", - host: str = HOST, -) -> None: - """Mock the Roku connection error.""" - roku_url = f"http://{host}:8060" - - aioclient_mock.get(f"{roku_url}/query/device-info", exc=SocketGIAError) - aioclient_mock.get(f"{roku_url}/query/apps", exc=SocketGIAError) - aioclient_mock.get(f"{roku_url}/query/active-app", exc=SocketGIAError) - aioclient_mock.get(f"{roku_url}/query/tv-active-channel", exc=SocketGIAError) - aioclient_mock.get(f"{roku_url}/query/tv-channels", exc=SocketGIAError) - - aioclient_mock.post(re.compile(f"{roku_url}/keypress/.*"), exc=SocketGIAError) - aioclient_mock.post(re.compile(f"{roku_url}/launch/.*"), exc=SocketGIAError) - aioclient_mock.post(f"{roku_url}/search", exc=SocketGIAError) - - -def mock_connection_server_error( - aioclient_mock: AiohttpClientMocker, - device: str = "roku3", - app: str = "roku", - host: str = HOST, -) -> None: - """Mock the Roku server error.""" - roku_url = f"http://{host}:8060" - - aioclient_mock.get( - f"{roku_url}/query/device-info", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get( - f"{roku_url}/query/apps", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get( - f"{roku_url}/query/active-app", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get( - f"{roku_url}/query/tv-active-channel", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get( - f"{roku_url}/query/tv-channels", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - - aioclient_mock.post( - re.compile(f"{roku_url}/keypress/.*"), status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.post( - re.compile(f"{roku_url}/launch/.*"), status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.post(f"{roku_url}/search", status=HTTPStatus.INTERNAL_SERVER_ERROR) - - -async def setup_integration( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, - device: str = "roku3", - app: str = "roku", - host: str = HOST, - unique_id: str = UPNP_SERIAL, - error: bool = False, - power: bool = True, - media_state: str = "close", - server_error: bool = False, - skip_entry_setup: bool = False, -) -> MockConfigEntry: - """Set up the Roku integration in Home Assistant.""" - entry = MockConfigEntry(domain=DOMAIN, unique_id=unique_id, data={CONF_HOST: host}) - - entry.add_to_hass(hass) - - if not skip_entry_setup: - mock_connection( - aioclient_mock, - device, - app=app, - host=host, - error=error, - power=power, - media_state=media_state, - server_error=server_error, - ) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - return entry diff --git a/tests/components/roku/conftest.py b/tests/components/roku/conftest.py new file mode 100644 index 00000000000..16261e07a89 --- /dev/null +++ b/tests/components/roku/conftest.py @@ -0,0 +1,86 @@ +"""Fixtures for Roku integration tests.""" +from collections.abc import Generator +import json +from unittest.mock import MagicMock, patch + +import pytest +from rokuecp import Device as RokuDevice + +from homeassistant.components.roku.const import DOMAIN +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture + + +def app_icon_url(*args, **kwargs): + """Get the URL to the application icon.""" + app_id = args[0] + return f"http://192.168.1.160:8060/query/icon/{app_id}" + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="Roku", + domain=DOMAIN, + data={CONF_HOST: "192.168.1.160"}, + unique_id="1GU48T017973", + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[None, None, None]: + """Mock setting up a config entry.""" + with patch("homeassistant.components.roku.async_setup_entry", return_value=True): + yield + + +@pytest.fixture +def mock_roku_config_flow( + request: pytest.FixtureRequest, +) -> Generator[None, MagicMock, None]: + """Return a mocked Roku client.""" + fixture: str = "roku/roku3.json" + if hasattr(request, "param") and request.param: + fixture = request.param + + device = RokuDevice(json.loads(load_fixture(fixture))) + with patch( + "homeassistant.components.roku.config_flow.Roku", autospec=True + ) as roku_mock: + client = roku_mock.return_value + client.app_icon_url.side_effect = app_icon_url + client.update.return_value = device + yield client + + +@pytest.fixture +def mock_roku(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: + """Return a mocked Roku client.""" + fixture: str = "roku/roku3.json" + if hasattr(request, "param") and request.param: + fixture = request.param + + device = RokuDevice(json.loads(load_fixture(fixture))) + with patch( + "homeassistant.components.roku.coordinator.Roku", autospec=True + ) as roku_mock: + client = roku_mock.return_value + client.app_icon_url.side_effect = app_icon_url + client.update.return_value = device + yield client + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_roku: MagicMock +) -> MockConfigEntry: + """Set up the Roku integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/roku/fixtures/active-app-netflix.xml b/tests/components/roku/fixtures/active-app-netflix.xml deleted file mode 100644 index 4cf7a3fc506..00000000000 --- a/tests/components/roku/fixtures/active-app-netflix.xml +++ /dev/null @@ -1,4 +0,0 @@ - - -Netflix - diff --git a/tests/components/roku/fixtures/active-app-pluto.xml b/tests/components/roku/fixtures/active-app-pluto.xml deleted file mode 100644 index cb3b85dc51c..00000000000 --- a/tests/components/roku/fixtures/active-app-pluto.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Pluto TV - It's Free TV - diff --git a/tests/components/roku/fixtures/active-app-roku.xml b/tests/components/roku/fixtures/active-app-roku.xml deleted file mode 100644 index 19808409518..00000000000 --- a/tests/components/roku/fixtures/active-app-roku.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Roku - diff --git a/tests/components/roku/fixtures/active-app-screensaver.xml b/tests/components/roku/fixtures/active-app-screensaver.xml deleted file mode 100644 index fcbb85c426d..00000000000 --- a/tests/components/roku/fixtures/active-app-screensaver.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Roku - Default screensaver - diff --git a/tests/components/roku/fixtures/active-app-tvinput-dtv.xml b/tests/components/roku/fixtures/active-app-tvinput-dtv.xml deleted file mode 100644 index 7dd4cdc8f40..00000000000 --- a/tests/components/roku/fixtures/active-app-tvinput-dtv.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Antenna TV - diff --git a/tests/components/roku/fixtures/apps-tv.xml b/tests/components/roku/fixtures/apps-tv.xml deleted file mode 100644 index e5862268d90..00000000000 --- a/tests/components/roku/fixtures/apps-tv.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Satellite TV - Blu-ray player - Antenna TV - Roku Channel Store - Netflix - Amazon Video on Demand - MLB.TV® - Free FrameChannel Service - Mediafly - Pandora - Pluto TV - It's Free TV - diff --git a/tests/components/roku/fixtures/apps.xml b/tests/components/roku/fixtures/apps.xml deleted file mode 100644 index 477304c09e8..00000000000 --- a/tests/components/roku/fixtures/apps.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - Roku Channel Store - Netflix - Amazon Video on Demand - MLB.TV® - Free FrameChannel Service - Mediafly - Pandora - Pluto TV - It's Free TV - diff --git a/tests/components/roku/fixtures/media-player-close.xml b/tests/components/roku/fixtures/media-player-close.xml deleted file mode 100644 index 0f542941d8c..00000000000 --- a/tests/components/roku/fixtures/media-player-close.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - false - diff --git a/tests/components/roku/fixtures/media-player-live.xml b/tests/components/roku/fixtures/media-player-live.xml deleted file mode 100644 index 62d819f228c..00000000000 --- a/tests/components/roku/fixtures/media-player-live.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - 73313 ms - 95000 ms - true - 25106 ms - - diff --git a/tests/components/roku/fixtures/media-player-pause.xml b/tests/components/roku/fixtures/media-player-pause.xml deleted file mode 100644 index a771208ef57..00000000000 --- a/tests/components/roku/fixtures/media-player-pause.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - 313813 ms - 6496762 ms - false - 15000 ms - - diff --git a/tests/components/roku/fixtures/media-player-play.xml b/tests/components/roku/fixtures/media-player-play.xml deleted file mode 100644 index eceb3ce59a2..00000000000 --- a/tests/components/roku/fixtures/media-player-play.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - 38813 ms - 6496762 ms - false - 15000 ms - - diff --git a/tests/components/roku/fixtures/roku3-app.json b/tests/components/roku/fixtures/roku3-app.json new file mode 100644 index 00000000000..0bf9411ef5c --- /dev/null +++ b/tests/components/roku/fixtures/roku3-app.json @@ -0,0 +1,93 @@ +{ + "available": true, + "standby": false, + "info": { + "udn": "015e5108-9000-1046-8035-b0a737964dfb", + "serial-number": "1GU48T017973", + "device-id": "1GU48T017973", + "vendor-name": "Roku", + "model-number": "4200X", + "model-name": "Roku 3", + "model-region": "US", + "supports-ethernet": "true", + "wifi-mac": "b0:a7:37:96:4d:fb", + "ethernet-mac": "b0:a7:37:96:4d:fa", + "network-type": "ethernet", + "user-device-name": "My Roku 3", + "software-version": "7.5.0", + "software-build": "09021", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone": "US/Pacific", + "time-zone-offset": "-480", + "power-mode": "PowerOn", + "supports-suspend": "false", + "supports-find-remote": "false", + "supports-audio-guide": "false", + "developer-enabled": "true", + "keyed-developer-id": "70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558", + "search-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "false", + "headphones-connected": "false" + }, + "app": { + "@id": "12", + "@type": "appl", + "@version": "4.1.218", + "#text": "Netflix" + }, + "apps": [ + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "media": { + "@error": "false", + "@state": "close", + "format": + { + "@audio": "eac3", + "@captions": "none", + "@drm": "none", + "@video": "hevc_b" + }, + "is_live": "false" + } +} diff --git a/tests/components/roku/fixtures/roku3-device-info-power-off.xml b/tests/components/roku/fixtures/roku3-device-info-power-off.xml deleted file mode 100644 index 4a89724016b..00000000000 --- a/tests/components/roku/fixtures/roku3-device-info-power-off.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - 015e5108-9000-1046-8035-b0a737964dfb - 1GU48T017973 - 1GU48T017973 - Roku - 4200X - Roku 3 - US - true - b0:a7:37:96:4d:fb - b0:a7:37:96:4d:fa - ethernet - My Roku 3 - 7.5.0 - 09021 - true - en - US - en_US - US/Pacific - -480 - PowerOff - false - false - false - true - 70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558 - true - true - true - false - false - false - diff --git a/tests/components/roku/fixtures/roku3-device-info.xml b/tests/components/roku/fixtures/roku3-device-info.xml deleted file mode 100644 index e41e4201518..00000000000 --- a/tests/components/roku/fixtures/roku3-device-info.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - 015e5108-9000-1046-8035-b0a737964dfb - 1GU48T017973 - 1GU48T017973 - Roku - 4200X - Roku 3 - US - true - b0:a7:37:96:4d:fb - b0:a7:37:96:4d:fa - ethernet - My Roku 3 - 7.5.0 - 09021 - true - en - US - en_US - US/Pacific - -480 - PowerOn - false - false - false - true - 70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558 - true - true - true - false - false - false - diff --git a/tests/components/roku/fixtures/roku3-idle.json b/tests/components/roku/fixtures/roku3-idle.json new file mode 100644 index 00000000000..d092b16df69 --- /dev/null +++ b/tests/components/roku/fixtures/roku3-idle.json @@ -0,0 +1,90 @@ +{ + "available": true, + "standby": true, + "info": { + "udn": "015e5108-9000-1046-8035-b0a737964dfb", + "serial-number": "1GU48T017973", + "device-id": "1GU48T017973", + "vendor-name": "Roku", + "model-number": "4200X", + "model-name": "Roku 3", + "model-region": "US", + "supports-ethernet": "true", + "wifi-mac": "b0:a7:37:96:4d:fb", + "ethernet-mac": "b0:a7:37:96:4d:fa", + "network-type": "ethernet", + "user-device-name": "My Roku 3", + "software-version": "7.5.0", + "software-build": "09021", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone": "US/Pacific", + "time-zone-offset": "-480", + "power-mode": "PowerOn", + "supports-suspend": "false", + "supports-find-remote": "false", + "supports-audio-guide": "false", + "developer-enabled": "true", + "keyed-developer-id": "70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558", + "search-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "false", + "headphones-connected": "false" + }, + "app": { + "#text": "Roku" + }, + "apps": [ + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "media": { + "@error": "false", + "@state": "close", + "format": + { + "@audio": "eac3", + "@captions": "none", + "@drm": "none", + "@video": "hevc_b" + }, + "is_live": "false" + } +} diff --git a/tests/components/roku/fixtures/roku3-media-paused.json b/tests/components/roku/fixtures/roku3-media-paused.json new file mode 100644 index 00000000000..cf37d05528d --- /dev/null +++ b/tests/components/roku/fixtures/roku3-media-paused.json @@ -0,0 +1,116 @@ +{ + "available": true, + "standby": false, + "info": { + "udn": "015e5108-9000-1046-8035-b0a737964dfb", + "serial-number": "1GU48T017973", + "device-id": "1GU48T017973", + "vendor-name": "Roku", + "model-number": "4200X", + "model-name": "Roku 3", + "model-region": "US", + "supports-ethernet": "true", + "wifi-mac": "b0:a7:37:96:4d:fb", + "ethernet-mac": "b0:a7:37:96:4d:fa", + "network-type": "ethernet", + "user-device-name": "My Roku 3", + "software-version": "7.5.0", + "software-build": "09021", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone": "US/Pacific", + "time-zone-offset": "-480", + "power-mode": "PowerOn", + "supports-suspend": "false", + "supports-find-remote": "false", + "supports-audio-guide": "false", + "developer-enabled": "true", + "keyed-developer-id": "70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558", + "search-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "false", + "headphones-connected": "false" + }, + "app": { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + }, + "apps": [ + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "media": { + "@error": "false", + "@state": "pause", + "plugin": { + "@bandwidth": "10000000 bps", + "@id": "74519", + "@name": "Pluto TV - It's Free TV" + }, + "format": { + "@audio": "aac_adts", + "@captions": "webvtt", + "@container": "hls", + "@drm": "none", + "@video": "mpeg4_10b" + }, + "buffering": { + "@current": "1000", + "@max": "1000", + "@target": "0" + }, + "new_stream": { + "@speed": "128000 bps" + }, + "position": "313813 ms", + "duration": "6496762 ms", + "is_live": "false", + "runtime": "15000 ms", + "stream_segment": { + "@bitrate": "3063648", + "@media_sequence": "61", + "@segment_type": "mux", + "@time": "310013" + } + } +} diff --git a/tests/components/roku/fixtures/roku3-media-playing.json b/tests/components/roku/fixtures/roku3-media-playing.json new file mode 100644 index 00000000000..17f016a409f --- /dev/null +++ b/tests/components/roku/fixtures/roku3-media-playing.json @@ -0,0 +1,116 @@ +{ + "available": true, + "standby": false, + "info": { + "udn": "015e5108-9000-1046-8035-b0a737964dfb", + "serial-number": "1GU48T017973", + "device-id": "1GU48T017973", + "vendor-name": "Roku", + "model-number": "4200X", + "model-name": "Roku 3", + "model-region": "US", + "supports-ethernet": "true", + "wifi-mac": "b0:a7:37:96:4d:fb", + "ethernet-mac": "b0:a7:37:96:4d:fa", + "network-type": "ethernet", + "user-device-name": "My Roku 3", + "software-version": "7.5.0", + "software-build": "09021", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone": "US/Pacific", + "time-zone-offset": "-480", + "power-mode": "PowerOn", + "supports-suspend": "false", + "supports-find-remote": "false", + "supports-audio-guide": "false", + "developer-enabled": "true", + "keyed-developer-id": "70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558", + "search-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "false", + "headphones-connected": "false" + }, + "app": { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + }, + "apps": [ + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "media": { + "@error": "false", + "@state": "play", + "plugin": { + "@bandwidth": "10000000 bps", + "@id": "74519", + "@name": "Pluto TV - It's Free TV" + }, + "format": { + "@audio": "aac_adts", + "@captions": "webvtt", + "@container": "hls", + "@drm": "none", + "@video": "mpeg4_10b" + }, + "buffering": { + "@current": "1000", + "@max": "1000", + "@target": "0" + }, + "new_stream": { + "@speed": "128000 bps" + }, + "position": "38813 ms", + "duration": "6496762 ms", + "is_live": "false", + "runtime": "15000 ms", + "stream_segment": { + "@bitrate": "2000", + "@media_sequence": "68", + "@segment_type": "captions", + "@time": "39013" + } + } +} diff --git a/tests/components/roku/fixtures/roku3-screensaver.json b/tests/components/roku/fixtures/roku3-screensaver.json new file mode 100644 index 00000000000..c2e9d36ec38 --- /dev/null +++ b/tests/components/roku/fixtures/roku3-screensaver.json @@ -0,0 +1,96 @@ +{ + "available": true, + "standby": false, + "info": { + "udn": "015e5108-9000-1046-8035-b0a737964dfb", + "serial-number": "1GU48T017973", + "device-id": "1GU48T017973", + "vendor-name": "Roku", + "model-number": "4200X", + "model-name": "Roku 3", + "model-region": "US", + "supports-ethernet": "true", + "wifi-mac": "b0:a7:37:96:4d:fb", + "ethernet-mac": "b0:a7:37:96:4d:fa", + "network-type": "ethernet", + "user-device-name": "My Roku 3", + "software-version": "7.5.0", + "software-build": "09021", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone": "US/Pacific", + "time-zone-offset": "-480", + "power-mode": "PowerOn", + "supports-suspend": "false", + "supports-find-remote": "false", + "supports-audio-guide": "false", + "developer-enabled": "true", + "keyed-developer-id": "70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558", + "search-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "false", + "headphones-connected": "false" + }, + "app": { + "app": "Roku", + "screensaver": { + "@id": "55545", + "@type": "ssvr", + "@version": "2.0.1", + "#text": "Default screensaver" + } + }, + "apps": [ + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "media": { + "@error": "false", + "@state": "close", + "format": + { + "@audio": "eac3", + "@captions": "none", + "@drm": "none", + "@video": "hevc_b" + }, + "is_live": "false" + } +} diff --git a/tests/components/roku/fixtures/roku3.json b/tests/components/roku/fixtures/roku3.json new file mode 100644 index 00000000000..bf731a3b200 --- /dev/null +++ b/tests/components/roku/fixtures/roku3.json @@ -0,0 +1,90 @@ +{ + "available": true, + "standby": false, + "info": { + "udn": "015e5108-9000-1046-8035-b0a737964dfb", + "serial-number": "1GU48T017973", + "device-id": "1GU48T017973", + "vendor-name": "Roku", + "model-number": "4200X", + "model-name": "Roku 3", + "model-region": "US", + "supports-ethernet": "true", + "wifi-mac": "b0:a7:37:96:4d:fb", + "ethernet-mac": "b0:a7:37:96:4d:fa", + "network-type": "ethernet", + "user-device-name": "My Roku 3", + "software-version": "7.5.0", + "software-build": "09021", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone": "US/Pacific", + "time-zone-offset": "-480", + "power-mode": "PowerOn", + "supports-suspend": "false", + "supports-find-remote": "false", + "supports-audio-guide": "false", + "developer-enabled": "true", + "keyed-developer-id": "70f6ed9c90cf60718a26f3a7c3e5af1c3ec29558", + "search-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "false", + "headphones-connected": "false" + }, + "app": { + "#text": "Roku" + }, + "apps": [ + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "media": { + "@error": "false", + "@state": "close", + "format": + { + "@audio": "eac3", + "@captions": "none", + "@drm": "none", + "@video": "hevc_b" + }, + "is_live": "false" + } +} diff --git a/tests/components/roku/fixtures/rokutv-7820x.json b/tests/components/roku/fixtures/rokutv-7820x.json new file mode 100644 index 00000000000..42181b08745 --- /dev/null +++ b/tests/components/roku/fixtures/rokutv-7820x.json @@ -0,0 +1,184 @@ +{ + "available": true, + "standby": false, + "info": { + "udn": "015e5555-9000-5555-5555-b0a555555dfb", + "serial-number": "YN00H5555555", + "device-id": "0S596H055555", + "advertising-id": "055555a9-d82b-5c75-b8fe-5555550cb7ee", + "vendor-name": "Onn", + "model-name": "100005844", + "model-number": "7820X", + "model-region": "US", + "is-tv": "true", + "is-stick": "false", + "screen-size": "58", + "panel-id": "2", + "tuner-type": "ATSC", + "supports-ethernet": "true", + "wifi-mac": "d8:13:99:f8:b0:c6", + "wifi-driver": "realtek", + "ethernet-mac": "d4:3a:2e:07:fd:cb", + "network-type": "wifi", + "network-name": "NetworkSSID", + "friendly-device-name": "58\" Onn Roku TV", + "friendly-model-name": "Onn Roku TV", + "default-device-name": "Onn Roku TV - YN00H5555555", + "user-device-name": "58\" Onn Roku TV", + "user-device-location": "Living room", + "build-number": "AT9.20E04502A", + "software-version": "9.2.0", + "software-build": "4502", + "secure-device": "true", + "language": "en", + "country": "US", + "locale": "en_US", + "time-zone-auto": "true", + "time-zone": "US/Central", + "time-zone-name": "United States/Central", + "time-zone-tz": "America/Chicago", + "time-zone-offset": "-300", + "clock-format": "12-hour", + "uptime": "264789", + "power-mode": "PowerOn", + "supports-suspend": "true", + "supports-find-remote": "true", + "find-remote-is-possible": "false", + "supports-audio-guide": "true", + "supports-rva": "true", + "developer-enabled": "false", + "keyed-developer-id": [], + "search-enabled": "true", + "search-channels-enabled": "true", + "voice-search-enabled": "true", + "notifications-enabled": "true", + "notifications-first-use": "false", + "supports-private-listening": "true", + "supports-private-listening-dtv": "true", + "supports-warm-standby": "true", + "headphones-connected": "false", + "expert-pq-enabled": "0.9", + "supports-ecs-textedit": "true", + "supports-ecs-microphone": "true", + "supports-wake-on-wlan": "true", + "supports-airplay": "true", + "has-play-on-roku": "true", + "has-mobile-screensaver": "true", + "support-url": "https://www.onntvsupport.com/", + "grandcentral-version": "2.9.57", + "trc-version": "3.0", + "trc-channel-version": "2.9.42", + "davinci-version": "2.8.20", + "has-wifi-extender": "false", + "has-wifi-5G-support": "true", + "can-use-wifi-extender": "true" + }, + "app": { + "@id": "tvinput.dtv", + "@type": "tvin", + "@version": "1.0.0", + "#text": "Antenna TV" + }, + "apps": [ + { + "@id": "tvinput.hdmi2", + "@type": "tvin", + "@version": "1.0.0", + "#text": "Satellite TV" + }, + { + "@id": "tvinput.hdmi1", + "@type": "tvin", + "@version": "1.0.0", + "#text": "Blu-ray player" + }, + { + "@id": "tvinput.dtv", + "@type": "tvin", + "@version": "1.0.0", + "#text": "Antenna TV" + }, + { + "@id": "11", + "#text": "Roku Channel Store" + }, + { + "@id": "12", + "#text": "Netflix" + }, + { + "@id": "13", + "#text": "Amazon Video on Demand" + }, + { + "@id": "14", + "#text": "MLB.TV®" + }, + { + "@id": "26", + "#text": "Free FrameChannel Service" + }, + { + "@id": "27", + "#text": "Mediafly" + }, + { + "@id": "28", + "#text": "Pandora" + }, + { + "@id": "74519", + "@subtype": "rsga", + "@type": "appl", + "@version": "5.2.0", + "#text": "Pluto TV - It's Free TV" + } + ], + "channel": { + "number": "14.3", + "name": "getTV", + "type": "air-digital", + "user-hidden": "false", + "active-input": "true", + "signal-state": "valid", + "signal-mode": "480i", + "signal-quality": "20", + "signal-strength": "-75", + "program-title": "Airwolf", + "program-description": "The team will travel all around the world in order to shut down a global crime ring.", + "program-ratings": "TV-14-D-V", + "program-analog-audio": "none", + "program-digital-audio": "stereo", + "program-audio-languages": "eng", + "program-audio-formats": "AC3", + "program-audio-language": "eng", + "program-audio-format": "AC3", + "program-has-cc": "true" + }, + "channels": [ + { + "number": "1.1", + "name": "WhatsOn", + "type": "air-digital", + "user-hidden": "false" + }, + { + "number": "1.3", + "name": "QVC", + "type": "air-digital", + "user-hidden": "false" + } + ], + "media": { + "@error": "false", + "@state": "close", + "format": + { + "@audio": "eac3", + "@captions": "none", + "@drm": "none", + "@video": "hevc_b" + }, + "is_live": "false" + } +} diff --git a/tests/components/roku/fixtures/rokutv-device-info-power-off.xml b/tests/components/roku/fixtures/rokutv-device-info-power-off.xml deleted file mode 100644 index 658fc130629..00000000000 --- a/tests/components/roku/fixtures/rokutv-device-info-power-off.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - 015e5555-9000-5555-5555-b0a555555dfb - YN00H5555555 - 0S596H055555 - 055555a9-d82b-5c75-b8fe-5555550cb7ee - Onn - 100005844 - 7820X - US - true - false - 58 - 2 - ATSC - true - d8:13:99:f8:b0:c6 - realtek - d4:3a:2e:07:fd:cb - wifi - NetworkSSID - 58" Onn Roku TV - Onn Roku TV - Onn Roku TV - YN00H5555555 - 58" Onn Roku TV - Living room - AT9.20E04502A - 9.2.0 - 4502 - true - en - US - en_US - true - US/Central - United States/Central - America/Chicago - -300 - 12-hour - 264789 - PowerOn - true - true - false - true - true - false - - true - true - true - true - false - true - true - true - false - 0.9 - true - true - true - true - true - https://www.onntvsupport.com/ - 2.9.57 - 3.0 - 2.9.42 - 2.8.20 - false - true - true - diff --git a/tests/components/roku/fixtures/rokutv-device-info.xml b/tests/components/roku/fixtures/rokutv-device-info.xml deleted file mode 100644 index cbb538ba4c1..00000000000 --- a/tests/components/roku/fixtures/rokutv-device-info.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - 015e5555-9000-5555-5555-b0a555555dfb - YN00H5555555 - 0S596H055555 - 055555a9-d82b-5c75-b8fe-5555550cb7ee - Onn - 100005844 - 7820X - US - true - false - 58 - 2 - ATSC - true - d8:13:99:f8:b0:c6 - realtek - d4:3a:2e:07:fd:cb - wifi - NetworkSSID - 58" Onn Roku TV - Onn Roku TV - Onn Roku TV - YN00H5555555 - 58" Onn Roku TV - Living room - AT9.20E04502A - 9.2.0 - 4502 - true - en - US - en_US - true - US/Central - United States/Central - America/Chicago - -300 - 12-hour - 264789 - PowerOn - true - true - false - true - true - false - - true - true - true - true - false - true - true - true - false - 0.9 - true - true - true - true - true - true - https://www.onntvsupport.com/ - 2.9.57 - 3.0 - 2.9.42 - 2.8.20 - false - true - true - diff --git a/tests/components/roku/fixtures/rokutv-tv-active-channel.xml b/tests/components/roku/fixtures/rokutv-tv-active-channel.xml deleted file mode 100644 index 9d6bf582726..00000000000 --- a/tests/components/roku/fixtures/rokutv-tv-active-channel.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - 14.3 - getTV - air-digital - false - true - valid - 480i - 20 - -75 - Airwolf - The team will travel all around the world in order to shut down a global crime ring. - TV-14-D-V - none - stereo - eng - AC3 - eng - AC3 - true - - diff --git a/tests/components/roku/fixtures/rokutv-tv-channels.xml b/tests/components/roku/fixtures/rokutv-tv-channels.xml deleted file mode 100644 index db4b816c9e2..00000000000 --- a/tests/components/roku/fixtures/rokutv-tv-channels.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - 1.1 - WhatsOn - air-digital - false - - - 1.3 - QVC - air-digital - false - - diff --git a/tests/components/roku/test_binary_sensor.py b/tests/components/roku/test_binary_sensor.py index d04e2db623b..d551a548c4c 100644 --- a/tests/components/roku/test_binary_sensor.py +++ b/tests/components/roku/test_binary_sensor.py @@ -1,4 +1,8 @@ """Tests for the sensors provided by the Roku integration.""" +from unittest.mock import MagicMock + +import pytest + from homeassistant.components.binary_sensor import STATE_OFF, STATE_ON from homeassistant.components.roku.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON @@ -6,17 +10,14 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory -from tests.components.roku import UPNP_SERIAL, setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry +from tests.components.roku import UPNP_SERIAL async def test_roku_binary_sensors( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, + hass: HomeAssistant, init_integration: MockConfigEntry ) -> None: """Test the Roku binary sensors.""" - await setup_integration(hass, aioclient_mock) - entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) @@ -77,22 +78,17 @@ async def test_roku_binary_sensors( assert device_entry.name == "My Roku 3" assert device_entry.entry_type is None assert device_entry.sw_version == "7.5.0" + assert device_entry.hw_version == "4200X" + assert device_entry.suggested_area is None +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) async def test_rokutv_binary_sensors( hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test the Roku binary sensors.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host="192.168.1.161", - unique_id="YN00H5555555", - ) - entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) @@ -167,3 +163,5 @@ async def test_rokutv_binary_sensors( assert device_entry.name == '58" Onn Roku TV' assert device_entry.entry_type is None assert device_entry.sw_version == "9.2.0" + assert device_entry.hw_version == "7820X" + assert device_entry.suggested_area == "Living room" diff --git a/tests/components/roku/test_config_flow.py b/tests/components/roku/test_config_flow.py index 8aa015d3e01..99d0d1bb2c0 100644 --- a/tests/components/roku/test_config_flow.py +++ b/tests/components/roku/test_config_flow.py @@ -1,6 +1,9 @@ """Test the Roku config flow.""" import dataclasses -from unittest.mock import patch +from unittest.mock import MagicMock + +import pytest +from rokuecp import RokuConnectionError from homeassistant.components.roku.const import DOMAIN from homeassistant.config_entries import SOURCE_HOMEKIT, SOURCE_SSDP, SOURCE_USER @@ -12,6 +15,7 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) +from tests.common import MockConfigEntry from tests.components.roku import ( HOMEKIT_HOST, HOST, @@ -19,20 +23,18 @@ from tests.components.roku import ( MOCK_SSDP_DISCOVERY_INFO, NAME_ROKUTV, UPNP_FRIENDLY_NAME, - mock_connection, - setup_integration, ) -from tests.test_util.aiohttp import AiohttpClientMocker async def test_duplicate_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_roku_config_flow: MagicMock, ) -> None: """Test that errors are shown when duplicates are added.""" - await setup_integration(hass, aioclient_mock, skip_entry_setup=True) - mock_connection(aioclient_mock) + mock_config_entry.add_to_hass(hass) - user_input = {CONF_HOST: HOST} + user_input = {CONF_HOST: mock_config_entry.data[CONF_HOST]} result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input ) @@ -40,7 +42,7 @@ async def test_duplicate_error( assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" - user_input = {CONF_HOST: HOST} + user_input = {CONF_HOST: mock_config_entry.data[CONF_HOST]} result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input ) @@ -57,11 +59,12 @@ async def test_duplicate_error( assert result["reason"] == "already_configured" -async def test_form(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None: +async def test_form( + hass: HomeAssistant, + mock_roku_config_flow: MagicMock, + mock_setup_entry: None, +) -> None: """Test the user step.""" - - mock_connection(aioclient_mock) - result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER} ) @@ -69,29 +72,26 @@ async def test_form(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> assert result["errors"] == {} user_input = {CONF_HOST: HOST} - with patch( - "homeassistant.components.roku.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - flow_id=result["flow_id"], user_input=user_input - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], user_input=user_input + ) + await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == UPNP_FRIENDLY_NAME + assert result["title"] == "My Roku 3" - assert result["data"] + assert "data" in result assert result["data"][CONF_HOST] == HOST - assert len(mock_setup_entry.mock_calls) == 1 + assert "result" in result + assert result["result"].unique_id == "1GU48T017973" async def test_form_cannot_connect( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_roku_config_flow: MagicMock ) -> None: """Test we handle cannot connect roku error.""" - mock_connection(aioclient_mock, error=True) + mock_roku_config_flow.update.side_effect = RokuConnectionError result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER} @@ -106,40 +106,29 @@ async def test_form_cannot_connect( async def test_form_unknown_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_roku_config_flow: MagicMock ) -> None: """Test we handle unknown error.""" - mock_connection(aioclient_mock) + mock_roku_config_flow.update.side_effect = Exception result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER} ) user_input = {CONF_HOST: HOST} - with patch( - "homeassistant.components.roku.config_flow.Roku._request", - side_effect=Exception, - ) as mock_validate_input: - result = await hass.config_entries.flow.async_configure( - flow_id=result["flow_id"], user_input=user_input - ) + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], user_input=user_input + ) assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "unknown" - await hass.async_block_till_done() - assert len(mock_validate_input.mock_calls) == 1 - async def test_homekit_cannot_connect( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_roku_config_flow: MagicMock ) -> None: """Test we abort homekit flow on connection error.""" - mock_connection( - aioclient_mock, - host=HOMEKIT_HOST, - error=True, - ) + mock_roku_config_flow.update.side_effect = RokuConnectionError discovery_info = dataclasses.replace(MOCK_HOMEKIT_DISCOVERY_INFO) result = await hass.config_entries.flow.async_init( @@ -153,32 +142,31 @@ async def test_homekit_cannot_connect( async def test_homekit_unknown_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_roku_config_flow: MagicMock ) -> None: """Test we abort homekit flow on unknown error.""" - mock_connection(aioclient_mock) + mock_roku_config_flow.update.side_effect = Exception discovery_info = dataclasses.replace(MOCK_HOMEKIT_DISCOVERY_INFO) - with patch( - "homeassistant.components.roku.config_flow.Roku._request", - side_effect=Exception, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_HOMEKIT}, - data=discovery_info, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_HOMEKIT}, + data=discovery_info, + ) assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "unknown" +@pytest.mark.parametrize( + "mock_roku_config_flow", ["roku/rokutv-7820x.json"], indirect=True +) async def test_homekit_discovery( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_roku_config_flow: MagicMock, + mock_setup_entry: None, ) -> None: """Test the homekit discovery flow.""" - mock_connection(aioclient_mock, device="rokutv", host=HOMEKIT_HOST) - discovery_info = dataclasses.replace(MOCK_HOMEKIT_DISCOVERY_INFO) result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_HOMEKIT}, data=discovery_info @@ -188,24 +176,18 @@ async def test_homekit_discovery( assert result["step_id"] == "discovery_confirm" assert result["description_placeholders"] == {CONF_NAME: NAME_ROKUTV} - with patch( - "homeassistant.components.roku.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - flow_id=result["flow_id"], user_input={} - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], user_input={} + ) + await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == NAME_ROKUTV - assert result["data"] + assert "data" in result assert result["data"][CONF_HOST] == HOMEKIT_HOST assert result["data"][CONF_NAME] == NAME_ROKUTV - assert len(mock_setup_entry.mock_calls) == 1 - # test abort on existing host discovery_info = dataclasses.replace(MOCK_HOMEKIT_DISCOVERY_INFO) result = await hass.config_entries.flow.async_init( @@ -217,10 +199,10 @@ async def test_homekit_discovery( async def test_ssdp_cannot_connect( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_roku_config_flow: MagicMock ) -> None: """Test we abort SSDP flow on connection error.""" - mock_connection(aioclient_mock, error=True) + mock_roku_config_flow.update.side_effect = RokuConnectionError discovery_info = dataclasses.replace(MOCK_SSDP_DISCOVERY_INFO) result = await hass.config_entries.flow.async_init( @@ -234,32 +216,28 @@ async def test_ssdp_cannot_connect( async def test_ssdp_unknown_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_roku_config_flow: MagicMock ) -> None: """Test we abort SSDP flow on unknown error.""" - mock_connection(aioclient_mock) + mock_roku_config_flow.update.side_effect = Exception discovery_info = dataclasses.replace(MOCK_SSDP_DISCOVERY_INFO) - with patch( - "homeassistant.components.roku.config_flow.Roku._request", - side_effect=Exception, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_SSDP}, - data=discovery_info, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_SSDP}, + data=discovery_info, + ) assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "unknown" async def test_ssdp_discovery( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_roku_config_flow: MagicMock, + mock_setup_entry: None, ) -> None: """Test the SSDP discovery flow.""" - mock_connection(aioclient_mock) - discovery_info = dataclasses.replace(MOCK_SSDP_DISCOVERY_INFO) result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=discovery_info @@ -269,14 +247,10 @@ async def test_ssdp_discovery( assert result["step_id"] == "discovery_confirm" assert result["description_placeholders"] == {CONF_NAME: UPNP_FRIENDLY_NAME} - with patch( - "homeassistant.components.roku.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - flow_id=result["flow_id"], user_input={} - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], user_input={} + ) + await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == UPNP_FRIENDLY_NAME @@ -284,5 +258,3 @@ async def test_ssdp_discovery( assert result["data"] assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_NAME] == UPNP_FRIENDLY_NAME - - assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/roku/test_init.py b/tests/components/roku/test_init.py index fc624f5cb64..f8820e711a2 100644 --- a/tests/components/roku/test_init.py +++ b/tests/components/roku/test_init.py @@ -1,41 +1,45 @@ """Tests for the Roku integration.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, MagicMock, patch + +from rokuecp import RokuConnectionError from homeassistant.components.roku.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from tests.components.roku import setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry +@patch( + "homeassistant.components.roku.coordinator.Roku._request", + side_effect=RokuConnectionError, +) async def test_config_entry_not_ready( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + mock_request: MagicMock, hass: HomeAssistant, mock_config_entry: MockConfigEntry ) -> None: """Test the Roku configuration entry not ready.""" - entry = await setup_integration(hass, aioclient_mock, error=True) - - assert entry.state is ConfigEntryState.SETUP_RETRY - - -async def test_unload_config_entry( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test the Roku configuration entry unloading.""" - with patch( - "homeassistant.components.roku.media_player.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.roku.remote.async_setup_entry", - return_value=True, - ): - entry = await setup_integration(hass, aioclient_mock) - - assert hass.data[DOMAIN][entry.entry_id] - assert entry.state is ConfigEntryState.LOADED - - await hass.config_entries.async_unload(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_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 + assert mock_request.call_count == 1 + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_load_unload_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_roku: AsyncMock, +) -> None: + """Test the Roku configuration entry loading/unloading.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.entry_id in hass.data[DOMAIN] + assert mock_config_entry.state is ConfigEntryState.LOADED + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert mock_config_entry.entry_id not in hass.data[DOMAIN] + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 5f3e5ebe956..154bcedded5 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -1,7 +1,8 @@ """Tests for the Roku Media Player platform.""" from datetime import timedelta -from unittest.mock import patch +from unittest.mock import MagicMock, patch +import pytest from rokuecp import RokuError from homeassistant.components.media_player import MediaPlayerDeviceClass @@ -76,25 +77,14 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import dt as dt_util -from tests.common import async_fire_time_changed -from tests.components.roku import NAME_ROKUTV, UPNP_SERIAL, setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry, async_fire_time_changed MAIN_ENTITY_ID = f"{MP_DOMAIN}.my_roku_3" TV_ENTITY_ID = f"{MP_DOMAIN}.58_onn_roku_tv" -TV_HOST = "192.168.1.161" -TV_LOCATION = "Living room" -TV_MANUFACTURER = "Onn" -TV_MODEL = "100005844" -TV_SERIAL = "YN00H5555555" -TV_SW_VERSION = "9.2.0" - -async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None: +async def test_setup(hass: HomeAssistant, init_integration: MockConfigEntry) -> None: """Test setup with basic config.""" - await setup_integration(hass, aioclient_mock) - entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) @@ -104,12 +94,12 @@ async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) - assert state assert entry assert entry.original_device_class is MediaPlayerDeviceClass.RECEIVER - assert entry.unique_id == UPNP_SERIAL + assert entry.unique_id == "1GU48T017973" assert entry.device_id device_entry = device_registry.async_get(entry.device_id) assert device_entry - assert device_entry.identifiers == {(DOMAIN, UPNP_SERIAL)} + assert device_entry.identifiers == {(DOMAIN, "1GU48T017973")} assert device_entry.connections == { (dr.CONNECTION_NETWORK_MAC, "b0:a7:37:96:4d:fb"), (dr.CONNECTION_NETWORK_MAC, "b0:a7:37:96:4d:fa"), @@ -118,34 +108,30 @@ async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) - assert device_entry.model == "Roku 3" assert device_entry.name == "My Roku 3" assert device_entry.entry_type is None - assert device_entry.hw_version == "4200X" assert device_entry.sw_version == "7.5.0" + assert device_entry.hw_version == "4200X" + assert device_entry.suggested_area is None +@pytest.mark.parametrize("mock_roku", ["roku/roku3-idle.json"], indirect=True) async def test_idle_setup( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test setup with idle device.""" - await setup_integration(hass, aioclient_mock, power=False) - state = hass.states.get(MAIN_ENTITY_ID) assert state assert state.state == STATE_STANDBY +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_setup( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test Roku TV setup.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, - ) - entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) @@ -155,37 +141,41 @@ async def test_tv_setup( assert state assert entry assert entry.original_device_class is MediaPlayerDeviceClass.TV - assert entry.unique_id == TV_SERIAL + assert entry.unique_id == "YN00H5555555" assert entry.device_id device_entry = device_registry.async_get(entry.device_id) assert device_entry - assert device_entry.identifiers == {(DOMAIN, TV_SERIAL)} + assert device_entry.identifiers == {(DOMAIN, "YN00H5555555")} assert device_entry.connections == { (dr.CONNECTION_NETWORK_MAC, "d8:13:99:f8:b0:c6"), (dr.CONNECTION_NETWORK_MAC, "d4:3a:2e:07:fd:cb"), } - assert device_entry.manufacturer == TV_MANUFACTURER - assert device_entry.model == TV_MODEL + assert device_entry.manufacturer == "Onn" + assert device_entry.model == "100005844" assert device_entry.name == '58" Onn Roku TV' assert device_entry.entry_type is None + assert device_entry.sw_version == "9.2.0" assert device_entry.hw_version == "7820X" - assert device_entry.sw_version == TV_SW_VERSION + assert device_entry.suggested_area == "Living room" async def test_availability( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_roku: MagicMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test entity availability.""" now = dt_util.utcnow() future = now + timedelta(minutes=1) + mock_config_entry.add_to_hass(hass) with patch("homeassistant.util.dt.utcnow", return_value=now): - await setup_integration(hass, aioclient_mock) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.roku.coordinator.Roku.update", side_effect=RokuError - ), patch("homeassistant.util.dt.utcnow", return_value=future): + with patch("homeassistant.util.dt.utcnow", return_value=future): + mock_roku.update.side_effect = RokuError async_fire_time_changed(hass, future) await hass.async_block_till_done() assert hass.states.get(MAIN_ENTITY_ID).state == STATE_UNAVAILABLE @@ -193,17 +183,18 @@ async def test_availability( future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): + mock_roku.update.side_effect = None async_fire_time_changed(hass, future) await hass.async_block_till_done() assert hass.states.get(MAIN_ENTITY_ID).state == STATE_HOME async def test_supported_features( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test supported features.""" - await setup_integration(hass, aioclient_mock) - # Features supported for Rokus state = hass.states.get(MAIN_ENTITY_ID) assert ( @@ -222,20 +213,15 @@ async def test_supported_features( ) +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_supported_features( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test supported features for Roku TV.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, - ) - state = hass.states.get(TV_ENTITY_ID) + assert state assert ( SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK @@ -253,12 +239,11 @@ async def test_tv_supported_features( async def test_attributes( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, init_integration: MockConfigEntry ) -> None: """Test attributes.""" - await setup_integration(hass, aioclient_mock) - state = hass.states.get(MAIN_ENTITY_ID) + assert state assert state.state == STATE_HOME assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) is None @@ -267,13 +252,15 @@ async def test_attributes( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Roku" +@pytest.mark.parametrize("mock_roku", ["roku/roku3-app.json"], indirect=True) async def test_attributes_app( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test attributes for app.""" - await setup_integration(hass, aioclient_mock, app="netflix") - state = hass.states.get(MAIN_ENTITY_ID) + assert state assert state.state == STATE_ON assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MEDIA_TYPE_APP @@ -282,13 +269,15 @@ async def test_attributes_app( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Netflix" +@pytest.mark.parametrize("mock_roku", ["roku/roku3-media-playing.json"], indirect=True) async def test_attributes_app_media_playing( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test attributes for app with playing media.""" - await setup_integration(hass, aioclient_mock, app="pluto", media_state="play") - state = hass.states.get(MAIN_ENTITY_ID) + assert state assert state.state == STATE_PLAYING assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MEDIA_TYPE_APP @@ -299,13 +288,15 @@ async def test_attributes_app_media_playing( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Pluto TV - It's Free TV" +@pytest.mark.parametrize("mock_roku", ["roku/roku3-media-paused.json"], indirect=True) async def test_attributes_app_media_paused( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test attributes for app with paused media.""" - await setup_integration(hass, aioclient_mock, app="pluto", media_state="pause") - state = hass.states.get(MAIN_ENTITY_ID) + assert state assert state.state == STATE_PAUSED assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MEDIA_TYPE_APP @@ -316,13 +307,15 @@ async def test_attributes_app_media_paused( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Pluto TV - It's Free TV" +@pytest.mark.parametrize("mock_roku", ["roku/roku3-screensaver.json"], indirect=True) async def test_attributes_screensaver( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test attributes for app with screensaver.""" - await setup_integration(hass, aioclient_mock, app="screensaver") - state = hass.states.get(MAIN_ENTITY_ID) + assert state assert state.state == STATE_IDLE assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) is None @@ -331,20 +324,13 @@ async def test_attributes_screensaver( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Roku" +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_attributes( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, init_integration: MockConfigEntry ) -> None: """Test attributes for Roku TV.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, - ) - state = hass.states.get(TV_ENTITY_ID) + assert state assert state.state == STATE_ON assert state.attributes.get(ATTR_APP_ID) == "tvinput.dtv" @@ -355,277 +341,245 @@ async def test_tv_attributes( assert state.attributes.get(ATTR_MEDIA_TITLE) == "Airwolf" -async def test_tv_device_registry( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test device registered for Roku TV in the device registry.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, - ) - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TV_SERIAL)}) - - assert reg_device.model == TV_MODEL - assert reg_device.sw_version == TV_SW_VERSION - assert reg_device.manufacturer == TV_MANUFACTURER - assert reg_device.suggested_area == TV_LOCATION - assert reg_device.name == NAME_ROKUTV - - async def test_services( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test the different media player services.""" - await setup_integration(hass, aioclient_mock) + await hass.services.async_call( + MP_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, blocking=True + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, blocking=True - ) + assert mock_roku.remote.call_count == 1 + mock_roku.remote.assert_called_with("poweroff") - remote_mock.assert_called_once_with("poweroff") + await hass.services.async_call( + MP_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, blocking=True + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, blocking=True - ) + assert mock_roku.remote.call_count == 2 + mock_roku.remote.assert_called_with("poweron") - remote_mock.assert_called_once_with("poweron") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_PAUSE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_MEDIA_PAUSE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) + assert mock_roku.remote.call_count == 3 + mock_roku.remote.assert_called_with("play") - remote_mock.assert_called_once_with("play") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_PLAY, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_MEDIA_PLAY, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) + assert mock_roku.remote.call_count == 4 + mock_roku.remote.assert_called_with("play") - remote_mock.assert_called_once_with("play") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_PLAY_PAUSE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_MEDIA_PLAY_PAUSE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) + assert mock_roku.remote.call_count == 5 + mock_roku.remote.assert_called_with("play") - remote_mock.assert_called_once_with("play") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_NEXT_TRACK, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_MEDIA_NEXT_TRACK, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) + assert mock_roku.remote.call_count == 6 + mock_roku.remote.assert_called_with("forward") - remote_mock.assert_called_once_with("forward") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_PREVIOUS_TRACK, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_MEDIA_PREVIOUS_TRACK, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) + assert mock_roku.remote.call_count == 7 + mock_roku.remote.assert_called_with("reverse") - remote_mock.assert_called_once_with("reverse") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: "Home"}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.launch") as launch_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: MAIN_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_APP, - ATTR_MEDIA_CONTENT_ID: "11", + assert mock_roku.remote.call_count == 8 + mock_roku.remote.assert_called_with("home") + + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_APP, + ATTR_MEDIA_CONTENT_ID: "11", + }, + blocking=True, + ) + + assert mock_roku.launch.call_count == 1 + mock_roku.launch.assert_called_with("11", {}) + + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_APP, + ATTR_MEDIA_CONTENT_ID: "291097", + ATTR_MEDIA_EXTRA: { + ATTR_MEDIA_TYPE: "movie", + ATTR_CONTENT_ID: "8e06a8b7-d667-4e31-939d-f40a6dd78a88", }, - blocking=True, - ) + }, + blocking=True, + ) - launch_mock.assert_called_once_with("11", {}) + assert mock_roku.launch.call_count == 2 + mock_roku.launch.assert_called_with( + "291097", + { + "contentID": "8e06a8b7-d667-4e31-939d-f40a6dd78a88", + "MediaType": "movie", + }, + ) - with patch("homeassistant.components.roku.coordinator.Roku.launch") as launch_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: MAIN_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_APP, - ATTR_MEDIA_CONTENT_ID: "291097", - ATTR_MEDIA_EXTRA: { - ATTR_MEDIA_TYPE: "movie", - ATTR_CONTENT_ID: "8e06a8b7-d667-4e31-939d-f40a6dd78a88", - }, + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_URL, + ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/media.mp4", + ATTR_MEDIA_EXTRA: { + ATTR_NAME: "Sent from HA", + ATTR_FORMAT: "mp4", }, - blocking=True, - ) + }, + blocking=True, + ) - launch_mock.assert_called_once_with( - "291097", - { - "contentID": "8e06a8b7-d667-4e31-939d-f40a6dd78a88", - "MediaType": "movie", - }, - ) + assert mock_roku.play_video.call_count == 1 + mock_roku.play_video.assert_called_with( + "https://awesome.tld/media.mp4", + { + "videoName": "Sent from HA", + "videoFormat": "mp4", + }, + ) - with patch("homeassistant.components.roku.coordinator.Roku.play_video") as pv_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: MAIN_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_URL, - ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/media.mp4", - ATTR_MEDIA_EXTRA: { - ATTR_NAME: "Sent from HA", - ATTR_FORMAT: "mp4", - }, - }, - blocking=True, - ) + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[HLS_PROVIDER], + ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", + }, + blocking=True, + ) - pv_mock.assert_called_once_with( - "https://awesome.tld/media.mp4", - { - "videoName": "Sent from HA", - "videoFormat": "mp4", - }, - ) + assert mock_roku.play_video.call_count == 2 + mock_roku.play_video.assert_called_with( + "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", + { + "MediaType": "hls", + }, + ) - with patch("homeassistant.components.roku.coordinator.Roku.play_video") as pv_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: MAIN_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[HLS_PROVIDER], - ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", - }, - blocking=True, - ) + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: "Netflix"}, + blocking=True, + ) - pv_mock.assert_called_once_with( - "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", - { - "MediaType": "hls", - }, - ) + assert mock_roku.launch.call_count == 3 + mock_roku.launch.assert_called_with("12") - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_SELECT_SOURCE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: "Home"}, - blocking=True, - ) + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12}, + blocking=True, + ) - remote_mock.assert_called_once_with("home") - - with patch("homeassistant.components.roku.coordinator.Roku.launch") as launch_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_SELECT_SOURCE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: "Netflix"}, - blocking=True, - ) - - launch_mock.assert_called_once_with("12") - - with patch("homeassistant.components.roku.coordinator.Roku.launch") as launch_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_SELECT_SOURCE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12}, - blocking=True, - ) - - launch_mock.assert_called_once_with("12") + assert mock_roku.launch.call_count == 4 + mock_roku.launch.assert_called_with("12") +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_services( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test the media player services related to Roku TV.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, + await hass.services.async_call( + MP_DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: TV_ENTITY_ID}, blocking=True ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: TV_ENTITY_ID}, blocking=True - ) + assert mock_roku.remote.call_count == 1 + mock_roku.remote.assert_called_with("volume_up") - remote_mock.assert_called_once_with("volume_up") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_VOLUME_DOWN, + {ATTR_ENTITY_ID: TV_ENTITY_ID}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_VOLUME_DOWN, - {ATTR_ENTITY_ID: TV_ENTITY_ID}, - blocking=True, - ) + assert mock_roku.remote.call_count == 2 + mock_roku.remote.assert_called_with("volume_down") - remote_mock.assert_called_once_with("volume_down") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_VOLUME_MUTE, + {ATTR_ENTITY_ID: TV_ENTITY_ID, ATTR_MEDIA_VOLUME_MUTED: True}, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_VOLUME_MUTE, - {ATTR_ENTITY_ID: TV_ENTITY_ID, ATTR_MEDIA_VOLUME_MUTED: True}, - blocking=True, - ) + assert mock_roku.remote.call_count == 3 + mock_roku.remote.assert_called_with("volume_mute") - remote_mock.assert_called_once_with("volume_mute") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: TV_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_CHANNEL, + ATTR_MEDIA_CONTENT_ID: "55", + }, + blocking=True, + ) - with patch("homeassistant.components.roku.coordinator.Roku.tune") as tune_mock: - await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: TV_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_CHANNEL, - ATTR_MEDIA_CONTENT_ID: "55", - }, - blocking=True, - ) - - tune_mock.assert_called_once_with("55") + assert mock_roku.tune.call_count == 1 + mock_roku.tune.assert_called_with("55") -async def test_media_browse(hass, aioclient_mock, hass_ws_client): +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +async def test_media_browse( + hass, + init_integration, + mock_roku, + hass_ws_client, +): """Test browsing media.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, - ) - client = await hass_ws_client(hass) await client.send_json( @@ -741,7 +695,13 @@ async def test_media_browse(hass, aioclient_mock, hass_ws_client): assert not msg["success"] -async def test_media_browse_internal(hass, aioclient_mock, hass_ws_client): +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +async def test_media_browse_internal( + hass, + init_integration, + mock_roku, + hass_ws_client, +): """Test browsing media with internal url.""" await async_process_ha_core_config( hass, @@ -750,15 +710,6 @@ async def test_media_browse_internal(hass, aioclient_mock, hass_ws_client): assert hass.config.internal_url == "http://example.local:8123" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host=TV_HOST, - unique_id=TV_SERIAL, - ) - client = await hass_ws_client(hass) with patch( @@ -804,16 +755,15 @@ async def test_media_browse_internal(hass, aioclient_mock, hass_ws_client): async def test_integration_services( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test integration services.""" - await setup_integration(hass, aioclient_mock) - - with patch("homeassistant.components.roku.coordinator.Roku.search") as search_mock: - await hass.services.async_call( - DOMAIN, - SERVICE_SEARCH, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_KEYWORD: "Space Jam"}, - blocking=True, - ) - search_mock.assert_called_once_with("Space Jam") + await hass.services.async_call( + DOMAIN, + SERVICE_SEARCH, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_KEYWORD: "Space Jam"}, + blocking=True, + ) + mock_roku.search.assert_called_once_with("Space Jam") diff --git a/tests/components/roku/test_remote.py b/tests/components/roku/test_remote.py index c0df380c1e8..f1685609563 100644 --- a/tests/components/roku/test_remote.py +++ b/tests/components/roku/test_remote.py @@ -1,5 +1,5 @@ """The tests for the Roku remote platform.""" -from unittest.mock import patch +from unittest.mock import MagicMock from homeassistant.components.remote import ( ATTR_COMMAND, @@ -10,26 +10,23 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from tests.components.roku import UPNP_SERIAL, setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry +from tests.components.roku import UPNP_SERIAL MAIN_ENTITY_ID = f"{REMOTE_DOMAIN}.my_roku_3" # pylint: disable=redefined-outer-name -async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None: +async def test_setup(hass: HomeAssistant, init_integration: MockConfigEntry) -> None: """Test setup with basic config.""" - await setup_integration(hass, aioclient_mock) assert hass.states.get(MAIN_ENTITY_ID) async def test_unique_id( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, init_integration: MockConfigEntry ) -> None: """Test unique id.""" - await setup_integration(hass, aioclient_mock) - entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) @@ -37,34 +34,34 @@ async def test_unique_id( async def test_main_services( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test platform services.""" - await setup_integration(hass, aioclient_mock) + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) + assert mock_roku.remote.call_count == 1 + mock_roku.remote.assert_called_with("poweroff") - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) - remote_mock.assert_called_once_with("poweroff") + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) + assert mock_roku.remote.call_count == 2 + mock_roku.remote.assert_called_with("poweron") - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, - blocking=True, - ) - remote_mock.assert_called_once_with("poweron") - - with patch("homeassistant.components.roku.coordinator.Roku.remote") as remote_mock: - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_SEND_COMMAND, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_COMMAND: ["home"]}, - blocking=True, - ) - remote_mock.assert_called_once_with("home") + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_COMMAND: ["home"]}, + blocking=True, + ) + assert mock_roku.remote.call_count == 3 + mock_roku.remote.assert_called_with("home") diff --git a/tests/components/roku/test_sensor.py b/tests/components/roku/test_sensor.py index 670cf69a8dc..6ca27635d30 100644 --- a/tests/components/roku/test_sensor.py +++ b/tests/components/roku/test_sensor.py @@ -1,4 +1,8 @@ """Tests for the sensors provided by the Roku integration.""" +from unittest.mock import MagicMock + +import pytest + from homeassistant.components.roku.const import DOMAIN from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -10,17 +14,15 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory -from tests.components.roku import UPNP_SERIAL, setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry +from tests.components.roku import UPNP_SERIAL async def test_roku_sensors( hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, + init_integration: MockConfigEntry, ) -> None: """Test the Roku sensors.""" - await setup_integration(hass, aioclient_mock) - entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) @@ -59,22 +61,17 @@ async def test_roku_sensors( assert device_entry.name == "My Roku 3" assert device_entry.entry_type is None assert device_entry.sw_version == "7.5.0" + assert device_entry.hw_version == "4200X" + assert device_entry.suggested_area is None +@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) async def test_rokutv_sensors( hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, + init_integration: MockConfigEntry, + mock_roku: MagicMock, ) -> None: """Test the Roku TV sensors.""" - await setup_integration( - hass, - aioclient_mock, - device="rokutv", - app="tvinput-dtv", - host="192.168.1.161", - unique_id="YN00H5555555", - ) - entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) @@ -113,3 +110,5 @@ async def test_rokutv_sensors( assert device_entry.name == '58" Onn Roku TV' assert device_entry.entry_type is None assert device_entry.sw_version == "9.2.0" + assert device_entry.hw_version == "7820X" + assert device_entry.suggested_area == "Living room"