mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-12 11:46:31 +00:00
Initial WS support (#2439)
* Initial WS support * test * Update frontend to fc7c4af2 * Fix issue with closing states * log error * make data optional * limit stopping states * Move wrappers to HomeAssistantWebSocket * use info * Use call_soon * Use lookuptable for WS commands * Fix tests
This commit is contained in:
parent
c342231052
commit
b31ecfefcd
@ -1 +1 @@
|
||||
Subproject commit 03d4174163166fa420b0cc7077ef2fa469f1164d
|
||||
Subproject commit fc7c4af27ac0d8ccfa0d23b2405b7523adcb0f92
|
@ -1,9 +1,9 @@
|
||||
|
||||
try {
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.5c3eb78c.js')")();
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.17d3e180.js')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.9603bcfc.js';
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.1e6aec4d.js';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.165d124a6f3e8a41abb2.js","sources":["webpack://home-assistant-frontend/chunk.165d124a6f3e8a41abb2.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.581c71aa259dd86618ad.js","sources":["webpack://home-assistant-frontend/chunk.581c71aa259dd86618ad.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.ca8af569f5af755b6c71.js","sources":["webpack://home-assistant-frontend/chunk.ca8af569f5af755b6c71.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.d0222f0b152d21991ec4.js","sources":["webpack://home-assistant-frontend/chunk.d0222f0b152d21991ec4.js"],"mappings":"AAAA","sourceRoot":""}
|
3
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js
Normal file
3
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js.gz
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"entrypoint.1e6aec4d.js","sources":["webpack://home-assistant-frontend/entrypoint.1e6aec4d.js"],"mappings":";AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"entrypoint.9603bcfc.js","sources":["webpack://home-assistant-frontend/entrypoint.9603bcfc.js"],"mappings":";AAAA","sourceRoot":""}
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.9603bcfc.js"
|
||||
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.1e6aec4d.js"
|
||||
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
||||
{"version":3,"file":"chunk.608800a294b1d02a0765.js","sources":["webpack://home-assistant-frontend/chunk.608800a294b1d02a0765.js"],"mappings":"AAAA;;;AA+LA;AACA;;;AAGA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;;;AAOA;;;;;;;;;;;;;;;;AAuBA;AAwNA;AACA;;AAIA;;;;;AAsBA;;;;;;;AAkGA;;AA0FA;AACA;AACA;AACA;AACA;;AAEA;;AAIA;;AA6NA;;AAEA;;AAEA;AACA;;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;;;;AAIA;;;AAGA;AACA;AACA;AACA;AACA;;AAIA;;;;;;;;AAgDA;;;;;;;;AAuHA;AACA;;;;;;;;;;;;;;;;AAgBA;AACA;AACA;;AAIA;AAIA;;AAEA;;;AAGA;;;;;AAQA;;;;;;;;;;;;;;;AAwEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiOA;;AAwYA;AACA;AACA;;;AAGA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;AAWA;;AAwLA;AACA;AACA;;AAMA;AA0GA;;;;AAIA;AACA;;AAIA;AACA;AACA;;;;;AAOA;;;;AAqCA;;AAoGA;AACA;AACA;AACA;AACA;AACA;;;;;AAKA;;;AAGA;;;;;AAKA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAIA;AA+IA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAMA;AACA;;AAEA;;AAEA;AACA;AASA;;;;AAsDA;AA2hBA;;AAEA;;AAEA;AACA;;AAIA;AAsLA;;;;;AAKA;;AAEA;;AAEA;AACA;;;;;;;;;;AAUA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;;AAIA;AACA;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyLA;;;AAyGA;AACA;;;;;;;;AAQA;;AAGA;;;AAGA;;AAEA;AACA;;;;AAIA;;;;;;;AAQA;;;AAGA;;;;;AAvCA;;;;;;;;;;;;;;;AA8KA;;AAkFA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAIA;;;;;;;;;;;AAoBA;;;AA0GA;;AAEA;;;;AARA;;;;;;;;;;;;AAiCA;;AA4BA;AACA;AACA;;;AAMA;;;;AA0IA;;;AAMA;AACA;;;AAGA;;AAEA;;AAKA;;AAEA;;AAEA;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAqNA;;;;AAIA;AACA;AACA;AACA;;;AAGA;;;AASA;;AAEA;;AAXA;;;;;;;;;;AAiBA;AACA;AACA;;;;AAIA;AACA;;;AAGA;;;AAGA;AACA;;;;;;;AAOA;;;;;;;;;AASA;;AAEA;AACA;;;;AAIA;;AAEA;;;;AAIA;;;AAGA;;;;AAIA;AACA;AACA;;;AAGA;;;;;;AAMA;;AAEA;AACA;;;;;;AAMA;;;AAGA;;AAEA;;AAEA;AACA;AAIA;;;;;;AAMA;;AAEA;AACA;;AAEA;AAKA;;AAEA;;;;AAIA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;AAGA;;AAEA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;AACA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;;AAMA;;;AAGA;;;AAGA;;AAEA;;AAKA;;;;;;;;AAQA;AACA;;;;;AAKA;AACA;;;;;;;;AAQA;AACA;;;;AAIA;AACA;AACA;;;;;;;;;AASA;AACA;;;;AAIA;AACA;AACA;;;;;AAKA;;;AAGA;AACA;AACA;;;;AAIA;AACA;AACA;;;;;;;;AAQA;AACA;;;;AAIA;;AAEA;;;AAGA;;;;;AAKA;;;AAGA;;AAKA;AACA;AACA;;AAGA;;;AAGA;AACA;;;AASA;AACA;;;AAVA;;;;;;;;;;AAiBA;;;AAGA;AACA;;;;;;AAMA;AACA;;;;AAIA;AACA;;;AAGA;;AAEA;AACA;;;;;;;AAOA;;AAEA;;;;;;;;;AASA;AACA;AACA;;;AAGA;;;AAGA;;;;AAIA;;;AAGA;AACA;;;;AAIA;;;;;AAKA;;;;AAIA;;;;AAIA;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkeA;;;AA2FA;AACA;AACA;AACA;;;AATA;;;;;;AA6BA;AAoGA;;AAEA;;AAEA;AACA;AACA;;;AAGA;;;AAKA;;;;;;;;;AAgBA;;;AAiGA;AACA;;;AAPA;;;;;;AA2BA;AA+PA;AAIA;;AAkCA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA","sourceRoot":""}
|
||||
{"version":3,"file":"chunk.2ec57cdfeeb3748fc49f.js","sources":["webpack://home-assistant-frontend/chunk.2ec57cdfeeb3748fc49f.js"],"mappings":"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","sourceRoot":""}
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.c7a82086c5d680500dd2.js","sources":["webpack://home-assistant-frontend/chunk.c7a82086c5d680500dd2.js"],"mappings":"AAAA;;AAqMA;AACA;;AAEA;;AAEA;;AAEA;AAjBA;AACA;AACA;;AAEA;AAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyIA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.e24a51f3c66fd6498ace.js","sources":["webpack://home-assistant-frontend/chunk.e24a51f3c66fd6498ace.js"],"mappings":"AAAA;;AAyKA;AACA;;AAEA;;AAEA;;AAEA;AAjBA;AACA;AACA;;AAEA;AAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyIA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_latest/entrypoint.17d3e180.js.gz
Normal file
BIN
supervisor/api/panel/frontend_latest/entrypoint.17d3e180.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,3 +1,3 @@
|
||||
{
|
||||
"entrypoint.js": "/api/hassio/app/frontend_latest/entrypoint.5c3eb78c.js"
|
||||
"entrypoint.js": "/api/hassio/app/frontend_latest/entrypoint.17d3e180.js"
|
||||
}
|
@ -55,6 +55,9 @@ class Core(CoreSysAttributes):
|
||||
)
|
||||
finally:
|
||||
self._state = new_state
|
||||
self.sys_homeassistant.websocket.supervisor_update_event(
|
||||
"info", {"state": new_state}
|
||||
)
|
||||
|
||||
async def connect(self):
|
||||
"""Connect Supervisor container."""
|
||||
|
@ -39,6 +39,14 @@ class HomeAssistantAuthError(HomeAssistantAPIError):
|
||||
"""Home Assistant Auth API exception."""
|
||||
|
||||
|
||||
class HomeAssistantWSError(HomeAssistantAPIError):
|
||||
"""Home Assistant websocket error."""
|
||||
|
||||
|
||||
class HomeAssistantWSNotSupported(HomeAssistantWSError):
|
||||
"""Raise when WebSockets are not supported."""
|
||||
|
||||
|
||||
class HomeAssistantJobError(HomeAssistantError, JobException):
|
||||
"""Raise on Home Assistant job error."""
|
||||
|
||||
|
@ -30,6 +30,7 @@ from ..validate import SCHEMA_HASS_CONFIG
|
||||
from .api import HomeAssistantAPI
|
||||
from .core import HomeAssistantCore
|
||||
from .secrets import HomeAssistantSecrets
|
||||
from .websocket import HomeAssistantWebSocket
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -42,6 +43,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||
self.coresys: CoreSys = coresys
|
||||
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
||||
self._websocket: HomeAssistantWebSocket = HomeAssistantWebSocket(coresys)
|
||||
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
||||
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
||||
|
||||
@ -50,6 +52,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
"""Return API handler for core."""
|
||||
return self._api
|
||||
|
||||
@property
|
||||
def websocket(self) -> HomeAssistantWebSocket:
|
||||
"""Return Websocket handler for core."""
|
||||
return self._websocket
|
||||
|
||||
@property
|
||||
def core(self) -> HomeAssistantCore:
|
||||
"""Return Core handler for docker."""
|
||||
|
167
supervisor/homeassistant/websocket.py
Normal file
167
supervisor/homeassistant/websocket.py
Normal file
@ -0,0 +1,167 @@
|
||||
"""Home Assistant Websocket API."""
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import aiohttp
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from ..const import CoreState
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import (
|
||||
HomeAssistantAPIError,
|
||||
HomeAssistantWSError,
|
||||
HomeAssistantWSNotSupported,
|
||||
)
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
CLOSING_STATES = [
|
||||
CoreState.SHUTDOWN,
|
||||
CoreState.STOPPING,
|
||||
CoreState.CLOSE,
|
||||
]
|
||||
|
||||
MIN_VERSION = {"supervisor/event": "2021.2.4"}
|
||||
|
||||
|
||||
class WSClient:
|
||||
"""Home Assistant Websocket client."""
|
||||
|
||||
def __init__(
|
||||
self, ha_version: AwesomeVersion, client: aiohttp.ClientWebSocketResponse
|
||||
):
|
||||
"""Initialise the WS client."""
|
||||
self.ha_version = ha_version
|
||||
self.client = client
|
||||
self.message_id = 0
|
||||
|
||||
async def async_send_command(self, message: Dict[str, Any]):
|
||||
"""Send a websocket command."""
|
||||
self.message_id += 1
|
||||
message["id"] = self.message_id
|
||||
|
||||
_LOGGER.debug("Sending: %s", message)
|
||||
try:
|
||||
await self.client.send_json(message)
|
||||
except HomeAssistantWSNotSupported:
|
||||
return
|
||||
|
||||
response = await self.client.receive_json()
|
||||
|
||||
_LOGGER.debug("Received: %s", response)
|
||||
|
||||
if response["success"]:
|
||||
return response["result"]
|
||||
|
||||
raise HomeAssistantWSError(response)
|
||||
|
||||
@classmethod
|
||||
async def connect_with_auth(
|
||||
cls, session: aiohttp.ClientSession, url: str, token: str
|
||||
) -> "WSClient":
|
||||
"""Create an authenticated websocket client."""
|
||||
try:
|
||||
client = await session.ws_connect(url)
|
||||
except aiohttp.client_exceptions.ClientConnectorError:
|
||||
raise HomeAssistantWSError("Can't connect") from None
|
||||
|
||||
hello_message = await client.receive_json()
|
||||
|
||||
try:
|
||||
await client.send_json({"type": "auth", "access_token": token})
|
||||
except HomeAssistantWSNotSupported:
|
||||
return
|
||||
|
||||
auth_ok_message = await client.receive_json()
|
||||
|
||||
if auth_ok_message["type"] != "auth_ok":
|
||||
raise HomeAssistantAPIError("AUTH NOT OK")
|
||||
|
||||
return cls(AwesomeVersion(hello_message["ha_version"]), client)
|
||||
|
||||
|
||||
class HomeAssistantWebSocket(CoreSysAttributes):
|
||||
"""Home Assistant Websocket API."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize Home Assistant object."""
|
||||
self.coresys: CoreSys = coresys
|
||||
self._client: Optional[WSClient] = None
|
||||
|
||||
async def _get_ws_client(self) -> WSClient:
|
||||
"""Return a websocket client."""
|
||||
await self.sys_homeassistant.api.ensure_access_token()
|
||||
client = await WSClient.connect_with_auth(
|
||||
self.sys_websession_ssl,
|
||||
f"{self.sys_homeassistant.api_url}/api/websocket",
|
||||
self.sys_homeassistant.api.access_token,
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
async def async_send_command(self, message: Dict[str, Any]):
|
||||
"""Send a command with the WS client."""
|
||||
if self.sys_core.state in CLOSING_STATES:
|
||||
raise HomeAssistantWSNotSupported(
|
||||
f"Can't execute in a ${self.sys_core.state} state"
|
||||
)
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
# No core access, don't try.
|
||||
return
|
||||
|
||||
if not self._client:
|
||||
self._client = await self._get_ws_client()
|
||||
|
||||
message_type = message.get("type")
|
||||
|
||||
if (
|
||||
message_type is not None
|
||||
and message_type in MIN_VERSION
|
||||
and self._client.ha_version < MIN_VERSION[message_type]
|
||||
):
|
||||
_LOGGER.info(
|
||||
"WebSocket command %s is not supported untill core-%s. Ignoring WebSocket message.",
|
||||
message_type,
|
||||
MIN_VERSION[message_type],
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
return await self._client.async_send_command(message)
|
||||
except HomeAssistantAPIError as err:
|
||||
raise HomeAssistantWSError from err
|
||||
|
||||
async def async_supervisor_update_event(
|
||||
self, key: str, data: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""Send a supervisor/event command."""
|
||||
try:
|
||||
await self.async_send_command(
|
||||
{
|
||||
"type": "supervisor/event",
|
||||
"data": {
|
||||
"event": "supervisor-update",
|
||||
"update_key": key,
|
||||
"data": data or {},
|
||||
},
|
||||
}
|
||||
)
|
||||
except HomeAssistantWSNotSupported:
|
||||
pass
|
||||
except HomeAssistantWSError as err:
|
||||
_LOGGER.error(err)
|
||||
|
||||
def supervisor_update_event(self, key: str, data: Optional[Dict[str, Any]] = None):
|
||||
"""Send a supervisor/event command."""
|
||||
if self.sys_core.state not in CLOSING_STATES:
|
||||
self.sys_loop.call_soon(
|
||||
self.sys_loop.create_task,
|
||||
self.async_supervisor_update_event(key, data),
|
||||
)
|
||||
|
||||
def send_command(self, message: Dict[str, Any]):
|
||||
"""Send a supervisor/event command."""
|
||||
if self.sys_core.state not in CLOSING_STATES:
|
||||
self.sys_loop.call_soon(
|
||||
self.sys_loop.create_task, self.async_send_command(message)
|
||||
)
|
@ -6,6 +6,7 @@ from uuid import uuid4
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.test_utils import TestClient
|
||||
from awesomeversion import AwesomeVersion
|
||||
import pytest
|
||||
|
||||
from supervisor.api import RestAPI
|
||||
@ -20,6 +21,12 @@ from tests.common import exists_fixture, load_fixture, load_json_fixture
|
||||
# pylint: disable=redefined-outer-name, protected-access
|
||||
|
||||
|
||||
async def mock_async_return_true() -> bool:
|
||||
"""Mock methods to return True."""
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def docker() -> DockerAPI:
|
||||
"""Mock DockerAPI."""
|
||||
@ -148,6 +155,12 @@ async def coresys(loop, docker, network_manager, aiohttp_client) -> CoreSys:
|
||||
coresys_obj.supervisor._connectivity = True
|
||||
coresys_obj.host.network._connectivity = True
|
||||
|
||||
# WebSocket
|
||||
coresys_obj.homeassistant.api.check_api_state = mock_async_return_true
|
||||
coresys_obj.homeassistant._websocket._client = AsyncMock(
|
||||
ha_version=AwesomeVersion("2021.2.4")
|
||||
)
|
||||
|
||||
yield coresys_obj
|
||||
|
||||
|
||||
|
@ -2,3 +2,4 @@
|
||||
|
||||
TEST_INTERFACE = "eth0"
|
||||
TEST_INTERFACE_WLAN = "wlan0"
|
||||
TEST_WS_URL = "ws://test.org:3000"
|
||||
|
49
tests/homeassistant/test_websocket.py
Normal file
49
tests/homeassistant/test_websocket.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""Test websocket."""
|
||||
# pylint: disable=protected-access, import-error
|
||||
import logging
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
|
||||
|
||||
async def test_send_command(coresys: CoreSys):
|
||||
"""Test websocket error on listen."""
|
||||
client = coresys.homeassistant.websocket._client
|
||||
await coresys.homeassistant.websocket.async_send_command({"type": "test"})
|
||||
client.async_send_command.assert_called_with({"type": "test"})
|
||||
|
||||
await coresys.homeassistant.websocket.async_supervisor_update_event(
|
||||
"test", {"lorem": "ipsum"}
|
||||
)
|
||||
client.async_send_command.assert_called_with(
|
||||
{
|
||||
"type": "supervisor/event",
|
||||
"data": {
|
||||
"event": "supervisor-update",
|
||||
"update_key": "test",
|
||||
"data": {"lorem": "ipsum"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def test_send_command_old_core_version(coresys: CoreSys, caplog):
|
||||
"""Test websocket error on listen."""
|
||||
caplog.set_level(logging.INFO)
|
||||
client = coresys.homeassistant.websocket._client
|
||||
client.ha_version = AwesomeVersion("1970.1.1")
|
||||
|
||||
await coresys.homeassistant.websocket.async_send_command(
|
||||
{"type": "supervisor/event"}
|
||||
)
|
||||
|
||||
assert (
|
||||
"WebSocket command supervisor/event is not supported untill core-2021.2.4"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
await coresys.homeassistant.websocket.async_supervisor_update_event(
|
||||
"test", {"lorem": "ipsum"}
|
||||
)
|
||||
client.async_send_command.assert_not_called()
|
Loading…
x
Reference in New Issue
Block a user