From 6b9abfc2c647df8fab1326341c51e84f6418abf2 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 13:37:33 +0100 Subject: [PATCH] Add init test to Freebox (#46998) * Add init test to Freebox * Review : more readable conftest * Expect 2 blank lines between defs * Review : Not I/O in the event loop * Fix test_setup test * remove useless const * Review : mock setup methods * Add service test * Add import test --- .coveragerc | 1 - homeassistant/components/freebox/__init__.py | 5 +- homeassistant/components/freebox/const.py | 1 + homeassistant/components/freebox/router.py | 23 +- tests/components/freebox/conftest.py | 32 +- tests/components/freebox/const.py | 376 +++++++++++++++++++ tests/components/freebox/test_config_flow.py | 92 +++-- tests/components/freebox/test_init.py | 119 ++++++ 8 files changed, 585 insertions(+), 64 deletions(-) create mode 100644 tests/components/freebox/const.py create mode 100644 tests/components/freebox/test_init.py diff --git a/.coveragerc b/.coveragerc index 281c8d5be83..36d4399466f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -314,7 +314,6 @@ omit = homeassistant/components/foscam/camera.py homeassistant/components/foursquare/* homeassistant/components/free_mobile/notify.py - homeassistant/components/freebox/__init__.py homeassistant/components/freebox/device_tracker.py homeassistant/components/freebox/router.py homeassistant/components/freebox/sensor.py diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index f36a2303b6d..c6c98e6c2df 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, PLATFORMS +from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT from .router import FreeboxRouter _LOGGER = logging.getLogger(__name__) @@ -55,7 +55,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Handle reboot service call.""" await router.reboot() - hass.services.async_register(DOMAIN, "reboot", async_reboot) + hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot) async def async_close_connection(event): """Close Freebox connection on HA Stop.""" @@ -79,5 +79,6 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): if unload_ok: router = hass.data[DOMAIN].pop(entry.unique_id) await router.close() + hass.services.async_remove(DOMAIN, SERVICE_REBOOT) return unload_ok diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index d0ac63fa9bb..e47211f0926 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -8,6 +8,7 @@ from homeassistant.const import ( ) DOMAIN = "freebox" +SERVICE_REBOOT = "reboot" APP_DESC = { "app_id": "hass", diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 2511280f719..623c1fcc564 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,6 +1,7 @@ """Represent the Freebox router and its devices and sensors.""" from datetime import datetime, timedelta import logging +import os from pathlib import Path from typing import Any, Dict, List, Optional @@ -31,6 +32,18 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=30) +async def get_api(hass: HomeAssistantType, host: str) -> Freepybox: + """Get the Freebox API.""" + freebox_path = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path + + if not os.path.exists(freebox_path): + await hass.async_add_executor_job(os.makedirs, freebox_path) + + token_file = Path(f"{freebox_path}/{slugify(host)}.conf") + + return Freepybox(APP_DESC, token_file, API_VERSION) + + class FreeboxRouter: """Representation of a Freebox router.""" @@ -188,13 +201,3 @@ class FreeboxRouter: def wifi(self) -> Wifi: """Return the wifi.""" return self._api.wifi - - -async def get_api(hass: HomeAssistantType, host: str) -> Freepybox: - """Get the Freebox API.""" - freebox_path = Path(hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path) - freebox_path.mkdir(exist_ok=True) - - token_file = Path(f"{freebox_path}/{slugify(host)}.conf") - - return Freepybox(APP_DESC, token_file, API_VERSION) diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index e813469cbbf..3220552b6cf 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -1,11 +1,41 @@ """Test helpers for Freebox.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest +from .const import ( + DATA_CALL_GET_CALLS_LOG, + DATA_CONNECTION_GET_STATUS, + DATA_LAN_GET_HOSTS_LIST, + DATA_STORAGE_GET_DISKS, + DATA_SYSTEM_GET_CONFIG, + WIFI_GET_GLOBAL_CONFIG, +) + @pytest.fixture(autouse=True) def mock_path(): """Mock path lib.""" with patch("homeassistant.components.freebox.router.Path"): yield + + +@pytest.fixture(name="router") +def mock_router(): + """Mock a successful connection.""" + with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: + instance = service_mock.return_value + instance.open = AsyncMock() + instance.system.get_config = AsyncMock(return_value=DATA_SYSTEM_GET_CONFIG) + # sensor + instance.call.get_calls_log = AsyncMock(return_value=DATA_CALL_GET_CALLS_LOG) + instance.storage.get_disks = AsyncMock(return_value=DATA_STORAGE_GET_DISKS) + instance.connection.get_status = AsyncMock( + return_value=DATA_CONNECTION_GET_STATUS + ) + # switch + instance.wifi.get_global_config = AsyncMock(return_value=WIFI_GET_GLOBAL_CONFIG) + # device_tracker + instance.lan.get_hosts_list = AsyncMock(return_value=DATA_LAN_GET_HOSTS_LIST) + instance.close = AsyncMock() + yield service_mock diff --git a/tests/components/freebox/const.py b/tests/components/freebox/const.py new file mode 100644 index 00000000000..cc3d720d7ef --- /dev/null +++ b/tests/components/freebox/const.py @@ -0,0 +1,376 @@ +"""Test constants.""" + +MOCK_HOST = "myrouter.freeboxos.fr" +MOCK_PORT = 1234 + +# router +DATA_SYSTEM_GET_CONFIG = { + "mac": "68:A3:78:00:00:00", + "model_info": { + "has_ext_telephony": True, + "has_speakers_jack": True, + "wifi_type": "2d4_5g", + "pretty_name": "Freebox Server (r2)", + "customer_hdd_slots": 0, + "name": "fbxgw-r2/full", + "has_speakers": True, + "internal_hdd_size": 250, + "has_femtocell_exp": True, + "has_internal_hdd": True, + "has_dect": True, + }, + "fans": [{"id": "fan0_speed", "name": "Ventilateur 1", "value": 2130}], + "sensors": [ + {"id": "temp_hdd", "name": "Disque dur", "value": 40}, + {"id": "temp_sw", "name": "Température Switch", "value": 50}, + {"id": "temp_cpum", "name": "Température CPU M", "value": 60}, + {"id": "temp_cpub", "name": "Température CPU B", "value": 56}, + ], + "board_name": "fbxgw2r", + "disk_status": "active", + "uptime": "156 jours 19 heures 56 minutes 16 secondes", + "uptime_val": 13550176, + "user_main_storage": "Disque dur", + "box_authenticated": True, + "serial": "762601T190510709", + "firmware_version": "4.2.5", +} + +# sensors +DATA_CONNECTION_GET_STATUS = { + "type": "ethernet", + "rate_down": 198900, + "bytes_up": 12035728872949, + "ipv4_port_range": [0, 65535], + "rate_up": 1440000, + "bandwidth_up": 700000000, + "ipv6": "2a01:e35:ffff:ffff::1", + "bandwidth_down": 1000000000, + "media": "ftth", + "state": "up", + "bytes_down": 2355966141297, + "ipv4": "82.67.00.00", +} + +DATA_CALL_GET_CALLS_LOG = [ + { + "number": "0988290475", + "type": "missed", + "id": 94, + "duration": 15, + "datetime": 1613752718, + "contact_id": 0, + "line_id": 0, + "name": "0988290475", + "new": True, + }, + { + "number": "0367250217", + "type": "missed", + "id": 93, + "duration": 25, + "datetime": 1613662328, + "contact_id": 0, + "line_id": 0, + "name": "0367250217", + "new": True, + }, + { + "number": "0184726018", + "type": "missed", + "id": 92, + "duration": 25, + "datetime": 1613225098, + "contact_id": 0, + "line_id": 0, + "name": "0184726018", + "new": True, + }, +] + +DATA_STORAGE_GET_DISKS = [ + { + "idle_duration": 0, + "read_error_requests": 0, + "read_requests": 110, + "spinning": True, + # "table_type": "ms-dos", API returns without dash, but codespell isn't agree + "firmware": "SC1D", + "type": "internal", + "idle": False, + "connector": 0, + "id": 0, + "write_error_requests": 0, + "state": "enabled", + "write_requests": 2708929, + "total_bytes": 250050000000, + "model": "ST9250311CS", + "active_duration": 0, + "temp": 40, + "serial": "6VCQY907", + "partitions": [ + { + "fstype": "ext4", + "total_bytes": 244950000000, + "label": "Disque dur", + "id": 2, + "internal": True, + "fsck_result": "no_run_yet", + "state": "mounted", + "disk_id": 0, + "free_bytes": 227390000000, + "used_bytes": 5090000000, + "path": "L0Rpc3F1ZSBkdXI=", + } + ], + } +] + +# switch +WIFI_GET_GLOBAL_CONFIG = {"enabled": True, "mac_filter_state": "disabled"} + +# device_tracker +DATA_LAN_GET_HOSTS_LIST = [ + { + "l2ident": {"id": "8C:97:EA:00:00:00", "type": "mac_address"}, + "active": True, + "persistent": False, + "names": [ + {"name": "d633d0c8-958c-43cc-e807-d881b076924b", "source": "mdns"}, + {"name": "Freebox Player POP", "source": "mdns_srv"}, + ], + "vendor_name": "Freebox SAS", + "host_type": "smartphone", + "interface": "pub", + "id": "ether-8c:97:ea:00:00:00", + "last_time_reachable": 1614107652, + "primary_name_manual": False, + "l3connectivities": [ + { + "addr": "192.168.1.180", + "active": True, + "reachable": True, + "last_activity": 1614107614, + "af": "ipv4", + "last_time_reachable": 1614104242, + }, + { + "addr": "fe80::dcef:dbba:6604:31d1", + "active": True, + "reachable": True, + "last_activity": 1614107645, + "af": "ipv6", + "last_time_reachable": 1614107645, + }, + { + "addr": "2a01:e34:eda1:eb40:8102:4704:7ce0:2ace", + "active": False, + "reachable": False, + "last_activity": 1611574428, + "af": "ipv6", + "last_time_reachable": 1611574428, + }, + { + "addr": "2a01:e34:eda1:eb40:c8e5:c524:c96d:5f5e", + "active": False, + "reachable": False, + "last_activity": 1612475101, + "af": "ipv6", + "last_time_reachable": 1612475101, + }, + { + "addr": "2a01:e34:eda1:eb40:583a:49df:1df0:c2df", + "active": True, + "reachable": True, + "last_activity": 1614107652, + "af": "ipv6", + "last_time_reachable": 1614107652, + }, + { + "addr": "2a01:e34:eda1:eb40:147e:3569:86ab:6aaa", + "active": False, + "reachable": False, + "last_activity": 1612486752, + "af": "ipv6", + "last_time_reachable": 1612486752, + }, + ], + "default_name": "Freebox Player POP", + "model": "fbx8am", + "reachable": True, + "last_activity": 1614107652, + "primary_name": "Freebox Player POP", + }, + { + "l2ident": {"id": "DE:00:B0:00:00:00", "type": "mac_address"}, + "active": False, + "persistent": False, + "vendor_name": "", + "host_type": "workstation", + "interface": "pub", + "id": "ether-de:00:b0:00:00:00", + "last_time_reachable": 1607125599, + "primary_name_manual": False, + "default_name": "", + "l3connectivities": [ + { + "addr": "192.168.1.181", + "active": False, + "reachable": False, + "last_activity": 1607125599, + "af": "ipv4", + "last_time_reachable": 1607125599, + }, + { + "addr": "192.168.1.182", + "active": False, + "reachable": False, + "last_activity": 1605958758, + "af": "ipv4", + "last_time_reachable": 1605958758, + }, + { + "addr": "2a01:e34:eda1:eb40:dc00:b0ff:fedf:e30", + "active": False, + "reachable": False, + "last_activity": 1607125594, + "af": "ipv6", + "last_time_reachable": 1607125594, + }, + ], + "reachable": False, + "last_activity": 1607125599, + "primary_name": "", + }, + { + "l2ident": {"id": "DC:00:B0:00:00:00", "type": "mac_address"}, + "active": True, + "persistent": False, + "names": [ + {"name": "Repeteur-Wifi-Freebox", "source": "mdns"}, + {"name": "Repeteur Wifi Freebox", "source": "mdns_srv"}, + ], + "vendor_name": "", + "host_type": "freebox_wifi", + "interface": "pub", + "id": "ether-dc:00:b0:00:00:00", + "last_time_reachable": 1614107678, + "primary_name_manual": False, + "l3connectivities": [ + { + "addr": "192.168.1.145", + "active": True, + "reachable": True, + "last_activity": 1614107678, + "af": "ipv4", + "last_time_reachable": 1614107678, + }, + { + "addr": "fe80::de00:b0ff:fe52:6ef6", + "active": True, + "reachable": True, + "last_activity": 1614107608, + "af": "ipv6", + "last_time_reachable": 1614107603, + }, + { + "addr": "2a01:e34:eda1:eb40:de00:b0ff:fe52:6ef6", + "active": True, + "reachable": True, + "last_activity": 1614107618, + "af": "ipv6", + "last_time_reachable": 1614107618, + }, + ], + "default_name": "Repeteur Wifi Freebox", + "model": "fbxwmr", + "reachable": True, + "last_activity": 1614107678, + "primary_name": "Repeteur Wifi Freebox", + }, + { + "l2ident": {"id": "5E:65:55:00:00:00", "type": "mac_address"}, + "active": False, + "persistent": False, + "names": [ + {"name": "iPhoneofQuentin", "source": "dhcp"}, + {"name": "iPhone-of-Quentin", "source": "mdns"}, + ], + "vendor_name": "", + "host_type": "smartphone", + "interface": "pub", + "id": "ether-5e:65:55:00:00:00", + "last_time_reachable": 1612611982, + "primary_name_manual": False, + "default_name": "iPhonedeQuentin", + "l3connectivities": [ + { + "addr": "192.168.1.148", + "active": False, + "reachable": False, + "last_activity": 1612611973, + "af": "ipv4", + "last_time_reachable": 1612611973, + }, + { + "addr": "fe80::14ca:6c30:938b:e281", + "active": False, + "reachable": False, + "last_activity": 1609693223, + "af": "ipv6", + "last_time_reachable": 1609693223, + }, + { + "addr": "fe80::1c90:2b94:1ba2:bd8b", + "active": False, + "reachable": False, + "last_activity": 1610797303, + "af": "ipv6", + "last_time_reachable": 1610797303, + }, + { + "addr": "fe80::8c8:e58b:838e:6785", + "active": False, + "reachable": False, + "last_activity": 1612611951, + "af": "ipv6", + "last_time_reachable": 1612611946, + }, + { + "addr": "2a01:e34:eda1:eb40:f0e7:e198:3a69:58", + "active": False, + "reachable": False, + "last_activity": 1609693245, + "af": "ipv6", + "last_time_reachable": 1609693245, + }, + { + "addr": "2a01:e34:eda1:eb40:1dc4:c6f8:aa20:c83b", + "active": False, + "reachable": False, + "last_activity": 1610797176, + "af": "ipv6", + "last_time_reachable": 1610797176, + }, + { + "addr": "2a01:e34:eda1:eb40:6cf6:5811:1770:c662", + "active": False, + "reachable": False, + "last_activity": 1612611982, + "af": "ipv6", + "last_time_reachable": 1612611982, + }, + { + "addr": "2a01:e34:eda1:eb40:438:9b2c:4f8f:f48a", + "active": False, + "reachable": False, + "last_activity": 1612611946, + "af": "ipv6", + "last_time_reachable": 1612611946, + }, + ], + "reachable": False, + "last_activity": 1612611982, + "primary_name": "iPhoneofQuentin", + }, +] diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 5f3aace9465..565387d3fb4 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -1,23 +1,22 @@ """Tests for the Freebox config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import Mock, patch from freebox_api.exceptions import ( AuthorizationError, HttpRequestError, InvalidTokenError, ) -import pytest from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers.typing import HomeAssistantType + +from .const import MOCK_HOST, MOCK_PORT from tests.common import MockConfigEntry -HOST = "myrouter.freeboxos.fr" -PORT = 1234 - MOCK_ZEROCONF_DATA = { "host": "192.168.0.254", "port": 80, @@ -30,33 +29,15 @@ MOCK_ZEROCONF_DATA = { "api_base_url": "/api/", "uid": "b15ab20debb399f95001a9ca207d2777", "https_available": "1", - "https_port": f"{PORT}", + "https_port": f"{MOCK_PORT}", "box_model": "fbxgw-r2/full", "box_model_name": "Freebox Server (r2)", - "api_domain": HOST, + "api_domain": MOCK_HOST, }, } -@pytest.fixture(name="connect") -def mock_controller_connect(): - """Mock a successful connection.""" - with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: - service_mock.return_value.open = AsyncMock() - service_mock.return_value.system.get_config = AsyncMock( - return_value={ - "mac": "abcd", - "model_info": {"pretty_name": "Pretty Model"}, - "firmware_version": "123", - } - ) - service_mock.return_value.lan.get_hosts_list = AsyncMock() - service_mock.return_value.connection.get_status = AsyncMock() - service_mock.return_value.close = AsyncMock() - yield service_mock - - -async def test_user(hass): +async def test_user(hass: HomeAssistantType): """Test user config.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -68,24 +49,24 @@ async def test_user(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" -async def test_import(hass): +async def test_import(hass: HomeAssistantType): """Test import step.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" -async def test_zeroconf(hass): +async def test_zeroconf(hass: HomeAssistantType): """Test zeroconf step.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -96,53 +77,64 @@ async def test_zeroconf(hass): assert result["step_id"] == "link" -async def test_link(hass, connect): +async def test_link(hass: HomeAssistantType, router: Mock): """Test linking.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, - ) + with patch( + "homeassistant.components.freebox.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.freebox.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == HOST - assert result["title"] == HOST - assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_PORT] == PORT + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == MOCK_HOST + assert result["title"] == MOCK_HOST + assert result["data"][CONF_HOST] == MOCK_HOST + assert result["data"][CONF_PORT] == MOCK_PORT + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 -async def test_abort_if_already_setup(hass): +async def test_abort_if_already_setup(hass: HomeAssistantType): """Test we abort if component is already setup.""" MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: HOST, CONF_PORT: PORT}, unique_id=HOST + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, ).add_to_hass(hass) - # Should fail, same HOST (import) + # Should fail, same MOCK_HOST (import) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" - # Should fail, same HOST (flow) + # Should fail, same MOCK_HOST (flow) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" -async def test_on_link_failed(hass): +async def test_on_link_failed(hass: HomeAssistantType): """Test when we have errors during linking the router.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) with patch( diff --git a/tests/components/freebox/test_init.py b/tests/components/freebox/test_init.py new file mode 100644 index 00000000000..aae5f911e10 --- /dev/null +++ b/tests/components/freebox/test_init.py @@ -0,0 +1,119 @@ +"""Tests for the Freebox config flow.""" +from unittest.mock import Mock, patch + +from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN +from homeassistant.components.freebox.const import DOMAIN as DOMAIN, SERVICE_REBOOT +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.setup import async_setup_component + +from .const import MOCK_HOST, MOCK_PORT + +from tests.common import MockConfigEntry + + +async def test_setup(hass: HomeAssistantType, router: Mock): + """Test setup of integration.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, + ) + entry.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert hass.config_entries.async_entries() == [entry] + + assert router.call_count == 1 + assert router().open.call_count == 1 + + assert hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + with patch( + "homeassistant.components.freebox.router.FreeboxRouter.reboot" + ) as mock_service: + await hass.services.async_call( + DOMAIN, + SERVICE_REBOOT, + blocking=True, + ) + await hass.async_block_till_done() + mock_service.assert_called_once() + + +async def test_setup_import(hass: HomeAssistantType, router: Mock): + """Test setup of integration from import.""" + await async_setup_component(hass, "persistent_notification", {}) + + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, + ) + entry.add_to_hass(hass) + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}} + ) + await hass.async_block_till_done() + assert hass.config_entries.async_entries() == [entry] + + assert router.call_count == 1 + assert router().open.call_count == 1 + + assert hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + +async def test_unload_remove(hass: HomeAssistantType, router: Mock): + """Test unload and remove of integration.""" + entity_id_dt = f"{DT_DOMAIN}.freebox_server_r2" + entity_id_sensor = f"{SENSOR_DOMAIN}.freebox_download_speed" + entity_id_switch = f"{SWITCH_DOMAIN}.freebox_wifi" + + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) + entry.add_to_hass(hass) + + config_entries = hass.config_entries.async_entries(DOMAIN) + assert len(config_entries) == 1 + assert entry is config_entries[0] + + assert await async_setup_component(hass, DOMAIN, {}) is True + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor + state_switch = hass.states.get(entity_id_switch) + assert state_switch + + await hass.config_entries.async_unload(entry.entry_id) + + assert entry.state == ENTRY_STATE_NOT_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt.state == STATE_UNAVAILABLE + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor.state == STATE_UNAVAILABLE + state_switch = hass.states.get(entity_id_switch) + assert state_switch.state == STATE_UNAVAILABLE + + assert router().close.call_count == 1 + assert not hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert router().close.call_count == 1 + assert entry.state == ENTRY_STATE_NOT_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt is None + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor is None + state_switch = hass.states.get(entity_id_switch) + assert state_switch is None