core/tests/components/cloud/test_iot.py

244 lines
7.2 KiB
Python

"""Test the cloud.iot module."""
import asyncio
from unittest.mock import patch, MagicMock, PropertyMock
from aiohttp import WSMsgType, client_exceptions
import pytest
from homeassistant.components.cloud import iot, auth_api
from tests.common import mock_coro
@pytest.fixture
def mock_client():
"""Mock the IoT client."""
client = MagicMock()
type(client).closed = PropertyMock(side_effect=[False, True])
with patch('asyncio.sleep'), \
patch('homeassistant.components.cloud.iot'
'.async_get_clientsession') as session:
session().ws_connect.return_value = mock_coro(client)
yield client
@pytest.fixture
def mock_handle_message():
"""Mock handle message."""
with patch('homeassistant.components.cloud.iot'
'.async_handle_message') as mock:
yield mock
@asyncio.coroutine
def test_cloud_calling_handler(mock_client, mock_handle_message):
"""Test we call handle message with correct info."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.return_value = mock_coro(MagicMock(
type=WSMsgType.text,
json=MagicMock(return_value={
'msgid': 'test-msg-id',
'handler': 'test-handler',
'payload': 'test-payload'
})
))
mock_handle_message.return_value = mock_coro('response')
mock_client.send_json.return_value = mock_coro(None)
yield from conn.connect()
# Check that we sent message to handler correctly
assert len(mock_handle_message.mock_calls) == 1
p_hass, p_cloud, handler_name, payload = \
mock_handle_message.mock_calls[0][1]
assert p_hass is cloud.hass
assert p_cloud is cloud
assert handler_name == 'test-handler'
assert payload == 'test-payload'
# Check that we forwarded response from handler to cloud
assert len(mock_client.send_json.mock_calls) == 1
assert mock_client.send_json.mock_calls[0][1][0] == {
'msgid': 'test-msg-id',
'payload': 'response'
}
@asyncio.coroutine
def test_connection_msg_for_unknown_handler(mock_client):
"""Test a msg for an unknown handler."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.return_value = mock_coro(MagicMock(
type=WSMsgType.text,
json=MagicMock(return_value={
'msgid': 'test-msg-id',
'handler': 'non-existing-handler',
'payload': 'test-payload'
})
))
mock_client.send_json.return_value = mock_coro(None)
yield from conn.connect()
# Check that we sent the correct error
assert len(mock_client.send_json.mock_calls) == 1
assert mock_client.send_json.mock_calls[0][1][0] == {
'msgid': 'test-msg-id',
'error': 'unknown-handler',
}
@asyncio.coroutine
def test_connection_msg_for_handler_raising(mock_client, mock_handle_message):
"""Test we sent error when handler raises exception."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.return_value = mock_coro(MagicMock(
type=WSMsgType.text,
json=MagicMock(return_value={
'msgid': 'test-msg-id',
'handler': 'test-handler',
'payload': 'test-payload'
})
))
mock_handle_message.side_effect = Exception('Broken')
mock_client.send_json.return_value = mock_coro(None)
yield from conn.connect()
# Check that we sent the correct error
assert len(mock_client.send_json.mock_calls) == 1
assert mock_client.send_json.mock_calls[0][1][0] == {
'msgid': 'test-msg-id',
'error': 'exception',
}
@asyncio.coroutine
def test_handler_forwarding():
"""Test we forward messages to correct handler."""
handler = MagicMock()
handler.return_value = mock_coro()
hass = object()
cloud = object()
with patch.dict(iot.HANDLERS, {'test': handler}):
yield from iot.async_handle_message(
hass, cloud, 'test', 'payload')
assert len(handler.mock_calls) == 1
r_hass, r_cloud, payload = handler.mock_calls[0][1]
assert r_hass is hass
assert r_cloud is cloud
assert payload == 'payload'
@asyncio.coroutine
def test_handling_core_messages(hass):
"""Test handling core messages."""
cloud = MagicMock()
cloud.logout.return_value = mock_coro()
yield from iot.async_handle_cloud(hass, cloud, {
'action': 'logout',
'reason': 'Logged in at two places.'
})
assert len(cloud.logout.mock_calls) == 1
@asyncio.coroutine
def test_cloud_getting_disconnected_by_server(mock_client, caplog):
"""Test server disconnecting instance."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.return_value = mock_coro(MagicMock(
type=WSMsgType.CLOSING,
))
yield from conn.connect()
assert 'Connection closed: Closed by server' in caplog.text
assert 'connect' in str(cloud.hass.async_add_job.mock_calls[-1][1][0])
@asyncio.coroutine
def test_cloud_receiving_bytes(mock_client, caplog):
"""Test server disconnecting instance."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.return_value = mock_coro(MagicMock(
type=WSMsgType.BINARY,
))
yield from conn.connect()
assert 'Connection closed: Received non-Text message' in caplog.text
assert 'connect' in str(cloud.hass.async_add_job.mock_calls[-1][1][0])
@asyncio.coroutine
def test_cloud_sending_invalid_json(mock_client, caplog):
"""Test cloud sending invalid JSON."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.return_value = mock_coro(MagicMock(
type=WSMsgType.TEXT,
json=MagicMock(side_effect=ValueError)
))
yield from conn.connect()
assert 'Connection closed: Received invalid JSON.' in caplog.text
assert 'connect' in str(cloud.hass.async_add_job.mock_calls[-1][1][0])
@asyncio.coroutine
def test_cloud_check_token_raising(mock_client, caplog):
"""Test cloud sending invalid JSON."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.side_effect = auth_api.CloudError
yield from conn.connect()
assert 'Unable to connect: Unable to refresh token.' in caplog.text
assert 'connect' in str(cloud.hass.async_add_job.mock_calls[-1][1][0])
@asyncio.coroutine
def test_cloud_connect_invalid_auth(mock_client, caplog):
"""Test invalid auth detected by server."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.side_effect = \
client_exceptions.WSServerHandshakeError(None, None, code=401)
yield from conn.connect()
assert 'Connection closed: Invalid auth.' in caplog.text
@asyncio.coroutine
def test_cloud_unable_to_connect(mock_client, caplog):
"""Test unable to connect error."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.side_effect = client_exceptions.ClientError(None, None)
yield from conn.connect()
assert 'Unable to connect:' in caplog.text
@asyncio.coroutine
def test_cloud_random_exception(mock_client, caplog):
"""Test random exception."""
cloud = MagicMock()
conn = iot.CloudIoT(cloud)
mock_client.receive.side_effect = Exception
yield from conn.connect()
assert 'Unexpected error' in caplog.text