mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Hass.io Add-on panel support for Ingress (#23185)
* Hass.io Add-on panel support for Ingress * Revert part of discovery startup handling * Add type * Fix tests * Add tests * Fix lint * Fix lint on test
This commit is contained in:
parent
6a7bd19a5a
commit
3e443d253c
@ -16,11 +16,12 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .auth import async_setup_auth
|
||||
from .discovery import async_setup_discovery
|
||||
from .auth import async_setup_auth_view
|
||||
from .addon_panel import async_setup_addon_panel
|
||||
from .discovery import async_setup_discovery_view
|
||||
from .handler import HassIO, HassioAPIError
|
||||
from .http import HassIOView
|
||||
from .ingress import async_setup_ingress
|
||||
from .ingress import async_setup_ingress_view
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -265,12 +266,15 @@ async def async_setup(hass, config):
|
||||
HASS_DOMAIN, service, async_handle_core_service)
|
||||
|
||||
# Init discovery Hass.io feature
|
||||
async_setup_discovery(hass, hassio, config)
|
||||
async_setup_discovery_view(hass, hassio)
|
||||
|
||||
# Init auth Hass.io feature
|
||||
async_setup_auth(hass)
|
||||
async_setup_auth_view(hass)
|
||||
|
||||
# Init ingress Hass.io feature
|
||||
async_setup_ingress(hass, host)
|
||||
async_setup_ingress_view(hass, host)
|
||||
|
||||
# Init add-on ingress panels
|
||||
await async_setup_addon_panel(hass, hassio)
|
||||
|
||||
return True
|
||||
|
93
homeassistant/components/hassio/addon_panel.py
Normal file
93
homeassistant/components/hassio/addon_panel.py
Normal file
@ -0,0 +1,93 @@
|
||||
"""Implement the Ingress Panel feature for Hass.io Add-ons."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import ATTR_PANELS, ATTR_TITLE, ATTR_ICON, ATTR_ADMIN, ATTR_ENABLE
|
||||
from .handler import HassioAPIError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_addon_panel(hass: HomeAssistantType, hassio):
|
||||
"""Add-on Ingress Panel setup."""
|
||||
hassio_addon_panel = HassIOAddonPanel(hass, hassio)
|
||||
hass.http.register_view(hassio_addon_panel)
|
||||
|
||||
# If panels are exists
|
||||
panels = await hassio_addon_panel.get_panels()
|
||||
if not panels:
|
||||
return
|
||||
|
||||
# Register available panels
|
||||
jobs = []
|
||||
for addon, data in panels.items():
|
||||
if not data[ATTR_ENABLE]:
|
||||
continue
|
||||
jobs.append(_register_panel(hass, addon, data))
|
||||
|
||||
if jobs:
|
||||
await asyncio.wait(jobs)
|
||||
|
||||
|
||||
class HassIOAddonPanel(HomeAssistantView):
|
||||
"""Hass.io view to handle base part."""
|
||||
|
||||
name = "api:hassio_push:panel"
|
||||
url = "/api/hassio_push/panel/{addon}"
|
||||
|
||||
def __init__(self, hass, hassio):
|
||||
"""Initialize WebView."""
|
||||
self.hass = hass
|
||||
self.hassio = hassio
|
||||
|
||||
async def post(self, request, addon):
|
||||
"""Handle new add-on panel requests."""
|
||||
panels = await self.get_panels()
|
||||
|
||||
# Panel exists for add-on slug
|
||||
if addon not in panels or not panels[addon][ATTR_ENABLE]:
|
||||
_LOGGER.error("Panel is not enable for %s", addon)
|
||||
return web.Response(status=400)
|
||||
data = panels[addon]
|
||||
|
||||
# Register panel
|
||||
await _register_panel(self.hass, addon, data)
|
||||
return web.Response()
|
||||
|
||||
async def delete(self, request, addon):
|
||||
"""Handle remove add-on panel requests."""
|
||||
# Currently not supported by backend / frontend
|
||||
return web.Response()
|
||||
|
||||
async def get_panels(self):
|
||||
"""Return panels add-on info data."""
|
||||
try:
|
||||
data = await self.hassio.get_ingress_panels()
|
||||
return data[ATTR_PANELS]
|
||||
except HassioAPIError as err:
|
||||
_LOGGER.error("Can't read panel info: %s", err)
|
||||
return {}
|
||||
|
||||
|
||||
def _register_panel(hass, addon, data):
|
||||
"""Init coroutine to register the panel.
|
||||
|
||||
Return coroutine.
|
||||
"""
|
||||
return hass.components.frontend.async_register_built_in_panel(
|
||||
frontend_url_path=addon,
|
||||
webcomponent_name='hassio-main',
|
||||
sidebar_title=data[ATTR_TITLE],
|
||||
sidebar_icon=data[ATTR_ICON],
|
||||
js_url='/api/hassio/app/entrypoint.js',
|
||||
embed_iframe=True,
|
||||
require_admin=data[ATTR_ADMIN],
|
||||
config={
|
||||
"ingress": addon
|
||||
}
|
||||
)
|
@ -1,18 +1,19 @@
|
||||
"""Implement the auth feature from Hass.io for Add-ons."""
|
||||
from ipaddress import ip_address
|
||||
import logging
|
||||
import os
|
||||
from ipaddress import ip_address
|
||||
|
||||
import voluptuous as vol
|
||||
from aiohttp import web
|
||||
from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.http.const import KEY_REAL_IP
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import ATTR_ADDON, ATTR_PASSWORD, ATTR_USERNAME
|
||||
|
||||
@ -27,7 +28,7 @@ SCHEMA_API_AUTH = vol.Schema({
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_auth(hass):
|
||||
def async_setup_auth_view(hass: HomeAssistantType):
|
||||
"""Auth setup."""
|
||||
hassio_auth = HassIOAuth(hass)
|
||||
hass.http.register_view(hassio_auth)
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Hass.io const variables."""
|
||||
|
||||
ATTR_ADDONS = 'addons'
|
||||
ATTR_DISCOVERY = 'discovery'
|
||||
ATTR_ADDON = 'addon'
|
||||
ATTR_NAME = 'name'
|
||||
@ -8,6 +9,11 @@ ATTR_CONFIG = 'config'
|
||||
ATTR_UUID = 'uuid'
|
||||
ATTR_USERNAME = 'username'
|
||||
ATTR_PASSWORD = 'password'
|
||||
ATTR_PANELS = 'panels'
|
||||
ATTR_ENABLE = 'enable'
|
||||
ATTR_TITLE = 'title'
|
||||
ATTR_ICON = 'icon'
|
||||
ATTR_ADMIN = 'admin'
|
||||
|
||||
X_HASSIO = 'X-Hassio-Key'
|
||||
X_INGRESS_PATH = "X-Ingress-Path"
|
||||
|
@ -5,9 +5,9 @@ import logging
|
||||
from aiohttp import web
|
||||
from aiohttp.web_exceptions import HTTPServiceUnavailable
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.core import CoreState, callback
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
|
||||
from .const import (
|
||||
ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_NAME, ATTR_SERVICE,
|
||||
@ -18,12 +18,13 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_discovery(hass, hassio, config):
|
||||
def async_setup_discovery_view(hass: HomeAssistantView, hassio):
|
||||
"""Discovery setup."""
|
||||
hassio_discovery = HassIODiscovery(hass, hassio, config)
|
||||
hassio_discovery = HassIODiscovery(hass, hassio)
|
||||
hass.http.register_view(hassio_discovery)
|
||||
|
||||
# Handle exists discovery messages
|
||||
async def async_discovery_start_handler(event):
|
||||
async def _async_discovery_start_handler(event):
|
||||
"""Process all exists discovery on startup."""
|
||||
try:
|
||||
data = await hassio.retrieve_discovery_messages()
|
||||
@ -36,13 +37,8 @@ def async_setup_discovery(hass, hassio, config):
|
||||
if jobs:
|
||||
await asyncio.wait(jobs)
|
||||
|
||||
if hass.state == CoreState.running:
|
||||
hass.async_create_task(async_discovery_start_handler(None))
|
||||
else:
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, async_discovery_start_handler)
|
||||
|
||||
hass.http.register_view(hassio_discovery)
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, _async_discovery_start_handler)
|
||||
|
||||
|
||||
class HassIODiscovery(HomeAssistantView):
|
||||
@ -51,11 +47,10 @@ class HassIODiscovery(HomeAssistantView):
|
||||
name = "api:hassio_push:discovery"
|
||||
url = "/api/hassio_push/discovery/{uuid}"
|
||||
|
||||
def __init__(self, hass, hassio, config):
|
||||
def __init__(self, hass: HomeAssistantView, hassio):
|
||||
"""Initialize WebView."""
|
||||
self.hass = hass
|
||||
self.hassio = hassio
|
||||
self.config = config
|
||||
|
||||
async def post(self, request, uuid):
|
||||
"""Handle new discovery requests."""
|
||||
|
@ -81,6 +81,14 @@ class HassIO:
|
||||
return self.send_command(
|
||||
"/addons/{}/info".format(addon), method="get")
|
||||
|
||||
@_api_data
|
||||
def get_ingress_panels(self):
|
||||
"""Return data for Add-on ingress panels.
|
||||
|
||||
This method return a coroutine.
|
||||
"""
|
||||
return self.send_command("/ingress/panels", method="get")
|
||||
|
||||
@_api_bool
|
||||
def restart_homeassistant(self):
|
||||
"""Restart Home-Assistant container.
|
||||
|
@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_ingress(hass: HomeAssistantType, host: str):
|
||||
def async_setup_ingress_view(hass: HomeAssistantType, host: str):
|
||||
"""Auth setup."""
|
||||
websession = hass.helpers.aiohttp_client.async_get_clientsession()
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
"requirements": [],
|
||||
"dependencies": [
|
||||
"http",
|
||||
"frontend",
|
||||
"panel_custom"
|
||||
],
|
||||
"codeowners": [
|
||||
|
128
tests/components/hassio/test_addon_panel.py
Normal file
128
tests/components/hassio/test_addon_panel.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Test add-on panel."""
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.const import HTTP_HEADER_HA_AUTH
|
||||
|
||||
from tests.common import mock_coro
|
||||
from . import API_PASSWORD
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_all(aioclient_mock):
|
||||
"""Mock all setup requests."""
|
||||
aioclient_mock.post(
|
||||
"http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
|
||||
aioclient_mock.post(
|
||||
"http://127.0.0.1/supervisor/options", json={'result': 'ok'})
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/homeassistant/info", json={
|
||||
'result': 'ok', 'data': {'last_version': '10.0'}})
|
||||
|
||||
|
||||
async def test_hassio_addon_panel_startup(hass, aioclient_mock, hassio_env):
|
||||
"""Test startup and panel setup after event."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/ingress/panels", json={
|
||||
'result': 'ok', 'data': {'panels': {
|
||||
"test1": {
|
||||
"enable": True,
|
||||
"title": "Test",
|
||||
"icon": "mdi:test",
|
||||
"admin": False
|
||||
},
|
||||
"test2": {
|
||||
"enable": False,
|
||||
"title": "Test 2",
|
||||
"icon": "mdi:test2",
|
||||
"admin": True
|
||||
},
|
||||
}}})
|
||||
|
||||
assert aioclient_mock.call_count == 0
|
||||
|
||||
with patch(
|
||||
'homeassistant.components.hassio.addon_panel._register_panel',
|
||||
Mock(return_value=mock_coro())
|
||||
) as mock_panel:
|
||||
await async_setup_component(hass, 'hassio', {
|
||||
'http': {
|
||||
'api_password': API_PASSWORD
|
||||
}
|
||||
})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 2
|
||||
assert mock_panel.called
|
||||
mock_panel.assert_called_with(
|
||||
hass, 'test1', {
|
||||
'enable': True, 'title': 'Test',
|
||||
'icon': 'mdi:test', 'admin': False
|
||||
})
|
||||
|
||||
|
||||
async def test_hassio_addon_panel_api(hass, aioclient_mock, hassio_env,
|
||||
hass_client):
|
||||
"""Test panel api after event."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/ingress/panels", json={
|
||||
'result': 'ok', 'data': {'panels': {
|
||||
"test1": {
|
||||
"enable": True,
|
||||
"title": "Test",
|
||||
"icon": "mdi:test",
|
||||
"admin": False
|
||||
},
|
||||
"test2": {
|
||||
"enable": False,
|
||||
"title": "Test 2",
|
||||
"icon": "mdi:test2",
|
||||
"admin": True
|
||||
},
|
||||
}}})
|
||||
|
||||
assert aioclient_mock.call_count == 0
|
||||
|
||||
with patch(
|
||||
'homeassistant.components.hassio.addon_panel._register_panel',
|
||||
Mock(return_value=mock_coro())
|
||||
) as mock_panel:
|
||||
await async_setup_component(hass, 'hassio', {
|
||||
'http': {
|
||||
'api_password': API_PASSWORD
|
||||
}
|
||||
})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 2
|
||||
assert mock_panel.called
|
||||
mock_panel.assert_called_with(
|
||||
hass, 'test1', {
|
||||
'enable': True, 'title': 'Test',
|
||||
'icon': 'mdi:test', 'admin': False
|
||||
})
|
||||
|
||||
hass_client = await hass_client()
|
||||
|
||||
resp = await hass_client.post(
|
||||
'/api/hassio_push/panel/test2', headers={
|
||||
HTTP_HEADER_HA_AUTH: API_PASSWORD
|
||||
})
|
||||
assert resp.status == 400
|
||||
|
||||
resp = await hass_client.post(
|
||||
'/api/hassio_push/panel/test1', headers={
|
||||
HTTP_HEADER_HA_AUTH: API_PASSWORD
|
||||
})
|
||||
assert resp.status == 200
|
||||
assert mock_panel.call_count == 2
|
||||
|
||||
mock_panel.assert_called_with(
|
||||
hass, 'test1', {
|
||||
'enable': True, 'title': 'Test',
|
||||
'icon': 'mdi:test', 'admin': False
|
||||
})
|
@ -105,3 +105,23 @@ async def test_api_retrieve_discovery(hassio_handler, aioclient_mock):
|
||||
data = await hassio_handler.retrieve_discovery_messages()
|
||||
assert data['discovery'][-1]['service'] == "mqtt"
|
||||
assert aioclient_mock.call_count == 1
|
||||
|
||||
|
||||
async def test_api_ingress_panels(hassio_handler, aioclient_mock):
|
||||
"""Test setup with API Ingress panels."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/ingress/panels", json={'result': 'ok', 'data': {
|
||||
"panels": {
|
||||
"slug": {
|
||||
"enable": True,
|
||||
"title": "Test",
|
||||
"icon": "mdi:test",
|
||||
"admin": False
|
||||
}
|
||||
}
|
||||
}})
|
||||
|
||||
data = await hassio_handler.get_ingress_panels()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert data['panels']
|
||||
assert "slug" in data['panels']
|
||||
|
@ -31,6 +31,9 @@ def mock_all(aioclient_mock):
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/homeassistant/info", json={
|
||||
'result': 'ok', 'data': {'last_version': '10.0'}})
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/ingress/panels", json={
|
||||
'result': 'ok', 'data': {'panels': {}}})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
@ -40,7 +43,7 @@ def test_setup_api_ping(hass, aioclient_mock):
|
||||
result = yield from async_setup_component(hass, 'hassio', {})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert hass.components.hassio.get_homeassistant_version() == "10.0"
|
||||
assert hass.components.hassio.is_hassio()
|
||||
|
||||
@ -79,7 +82,7 @@ def test_setup_api_push_api_data(hass, aioclient_mock):
|
||||
})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert not aioclient_mock.mock_calls[1][2]['ssl']
|
||||
assert aioclient_mock.mock_calls[1][2]['port'] == 9999
|
||||
assert aioclient_mock.mock_calls[1][2]['watchdog']
|
||||
@ -98,7 +101,7 @@ def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
|
||||
})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert not aioclient_mock.mock_calls[1][2]['ssl']
|
||||
assert aioclient_mock.mock_calls[1][2]['port'] == 9999
|
||||
assert not aioclient_mock.mock_calls[1][2]['watchdog']
|
||||
@ -114,7 +117,7 @@ async def test_setup_api_push_api_data_default(hass, aioclient_mock,
|
||||
})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert not aioclient_mock.mock_calls[1][2]['ssl']
|
||||
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
|
||||
refresh_token = aioclient_mock.mock_calls[1][2]['refresh_token']
|
||||
@ -174,7 +177,7 @@ async def test_setup_api_existing_hassio_user(hass, aioclient_mock,
|
||||
})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert not aioclient_mock.mock_calls[1][2]['ssl']
|
||||
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
|
||||
assert aioclient_mock.mock_calls[1][2]['refresh_token'] == token.token
|
||||
@ -192,7 +195,7 @@ def test_setup_core_push_timezone(hass, aioclient_mock):
|
||||
})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert aioclient_mock.call_count == 5
|
||||
assert aioclient_mock.mock_calls[2][2]['timezone'] == "testzone"
|
||||
|
||||
|
||||
@ -206,7 +209,7 @@ def test_setup_hassio_no_additional_data(hass, aioclient_mock):
|
||||
})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
assert aioclient_mock.mock_calls[-1][3]['X-Hassio-Key'] == "123456"
|
||||
|
||||
|
||||
@ -285,14 +288,14 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
|
||||
'hassio', 'addon_stdin', {'addon': 'test', 'input': 'test'})
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 5
|
||||
assert aioclient_mock.call_count == 6
|
||||
assert aioclient_mock.mock_calls[-1][2] == 'test'
|
||||
|
||||
yield from hass.services.async_call('hassio', 'host_shutdown', {})
|
||||
yield from hass.services.async_call('hassio', 'host_reboot', {})
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 8
|
||||
|
||||
yield from hass.services.async_call('hassio', 'snapshot_full', {})
|
||||
yield from hass.services.async_call('hassio', 'snapshot_partial', {
|
||||
@ -302,7 +305,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
|
||||
})
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert aioclient_mock.call_count == 10
|
||||
assert aioclient_mock.mock_calls[-1][2] == {
|
||||
'addons': ['test'], 'folders': ['ssl'], 'password': "123456"}
|
||||
|
||||
@ -318,7 +321,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
|
||||
})
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 11
|
||||
assert aioclient_mock.call_count == 12
|
||||
assert aioclient_mock.mock_calls[-1][2] == {
|
||||
'addons': ['test'], 'folders': ['ssl'], 'homeassistant': False,
|
||||
'password': "123456"
|
||||
@ -338,12 +341,12 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
|
||||
yield from hass.services.async_call('homeassistant', 'stop')
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 2
|
||||
assert aioclient_mock.call_count == 3
|
||||
|
||||
yield from hass.services.async_call('homeassistant', 'check_config')
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 2
|
||||
assert aioclient_mock.call_count == 3
|
||||
|
||||
with patch(
|
||||
'homeassistant.config.async_check_ha_config_file',
|
||||
@ -353,4 +356,4 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
|
||||
yield from hass.async_block_till_done()
|
||||
assert mock_check_config.called
|
||||
|
||||
assert aioclient_mock.call_count == 3
|
||||
assert aioclient_mock.call_count == 4
|
||||
|
Loading…
x
Reference in New Issue
Block a user