2021-07-12 20:09:17 +02:00
|
|
|
import asyncio
|
|
|
|
import socket
|
|
|
|
|
|
|
|
import pytest
|
2023-01-08 01:24:24 +01:00
|
|
|
from mock import MagicMock, patch
|
2021-07-12 20:09:17 +02:00
|
|
|
|
2023-01-08 01:24:24 +01:00
|
|
|
from aioesphomeapi._frame_helper import APIPlaintextFrameHelper
|
|
|
|
from aioesphomeapi.api_pb2 import DeviceInfoResponse, HelloResponse
|
2021-10-05 10:56:35 +02:00
|
|
|
from aioesphomeapi.connection import APIConnection, ConnectionParams, ConnectionState
|
2023-01-08 01:24:24 +01:00
|
|
|
from aioesphomeapi.core import RequiresEncryptionAPIError
|
2021-07-12 20:09:17 +02:00
|
|
|
from aioesphomeapi.host_resolver import AddrInfo, IPv4Sockaddr
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def connection_params() -> ConnectionParams:
|
|
|
|
return ConnectionParams(
|
|
|
|
address="fake.address",
|
|
|
|
port=6052,
|
|
|
|
password=None,
|
|
|
|
client_info="Tests client",
|
|
|
|
keepalive=15.0,
|
|
|
|
zeroconf_instance=None,
|
2021-09-08 23:12:07 +02:00
|
|
|
noise_psk=None,
|
2022-01-20 12:03:36 +01:00
|
|
|
expected_name=None,
|
2021-07-12 20:09:17 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def conn(connection_params) -> APIConnection:
|
2023-03-06 05:54:54 +01:00
|
|
|
async def on_stop(expected_disconnect: bool) -> None:
|
2021-07-12 20:09:17 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
return APIConnection(connection_params, on_stop)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def resolve_host():
|
|
|
|
with patch("aioesphomeapi.host_resolver.async_resolve_host") as func:
|
|
|
|
func.return_value = AddrInfo(
|
|
|
|
family=socket.AF_INET,
|
|
|
|
type=socket.SOCK_STREAM,
|
|
|
|
proto=socket.IPPROTO_TCP,
|
|
|
|
sockaddr=IPv4Sockaddr("10.0.0.512", 6052),
|
|
|
|
)
|
|
|
|
yield func
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def socket_socket():
|
|
|
|
with patch("socket.socket") as func:
|
|
|
|
yield func
|
|
|
|
|
|
|
|
|
2023-01-08 01:24:24 +01:00
|
|
|
def _get_mock_protocol(conn: APIConnection):
|
|
|
|
protocol = APIPlaintextFrameHelper(
|
|
|
|
on_pkt=conn._process_packet, on_error=conn._report_fatal_error
|
|
|
|
)
|
2023-01-06 05:24:10 +01:00
|
|
|
protocol._connected_event.set()
|
|
|
|
protocol._transport = MagicMock()
|
|
|
|
return protocol
|
|
|
|
|
|
|
|
|
2021-07-12 20:09:17 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_connect(conn, resolve_host, socket_socket, event_loop):
|
2023-01-06 05:24:10 +01:00
|
|
|
loop = asyncio.get_event_loop()
|
2023-01-08 01:24:24 +01:00
|
|
|
protocol = _get_mock_protocol(conn)
|
2023-01-06 05:24:10 +01:00
|
|
|
with patch.object(event_loop, "sock_connect"), patch.object(
|
|
|
|
loop, "create_connection", return_value=(MagicMock(), protocol)
|
|
|
|
), patch.object(conn, "_connect_start_ping"), patch.object(
|
2021-07-12 20:09:17 +02:00
|
|
|
conn, "send_message_await_response", return_value=HelloResponse()
|
|
|
|
):
|
2021-10-21 19:20:05 +02:00
|
|
|
await conn.connect(login=False)
|
2021-07-12 20:09:17 +02:00
|
|
|
|
|
|
|
assert conn.is_connected
|
2021-10-05 10:56:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2023-01-08 01:24:24 +01:00
|
|
|
async def test_requires_encryption_propagates(conn: APIConnection):
|
2023-01-06 05:24:10 +01:00
|
|
|
loop = asyncio.get_event_loop()
|
2023-01-08 01:24:24 +01:00
|
|
|
protocol = _get_mock_protocol(conn)
|
2023-01-06 05:24:10 +01:00
|
|
|
with patch.object(loop, "create_connection") as create_connection, patch.object(
|
|
|
|
protocol, "perform_handshake"
|
|
|
|
):
|
|
|
|
create_connection.return_value = (MagicMock(), protocol)
|
2021-10-05 10:56:35 +02:00
|
|
|
|
|
|
|
await conn._connect_init_frame_helper()
|
2023-01-08 01:24:24 +01:00
|
|
|
conn._connection_state = ConnectionState.CONNECTED
|
2023-01-06 05:24:10 +01:00
|
|
|
|
2021-10-05 10:56:35 +02:00
|
|
|
with pytest.raises(RequiresEncryptionAPIError):
|
2023-01-08 01:24:24 +01:00
|
|
|
task = asyncio.create_task(conn._connect_hello())
|
|
|
|
await asyncio.sleep(0)
|
2023-01-06 05:24:10 +01:00
|
|
|
protocol.data_received(b"\x01\x00\x00")
|
2023-01-08 01:24:24 +01:00
|
|
|
await task
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_plaintext_connection(conn: APIConnection, resolve_host, socket_socket):
|
|
|
|
"""Test that a plaintext connection works."""
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
protocol = _get_mock_protocol(conn)
|
|
|
|
messages = []
|
|
|
|
|
|
|
|
def on_msg(msg):
|
|
|
|
messages.append(msg)
|
|
|
|
|
|
|
|
remove = conn.add_message_callback(on_msg, {HelloResponse, DeviceInfoResponse})
|
|
|
|
transport = MagicMock()
|
|
|
|
|
|
|
|
with patch.object(conn, "_connect_hello"), patch.object(
|
|
|
|
loop, "sock_connect"
|
|
|
|
), patch.object(loop, "create_connection") as create_connection, patch.object(
|
|
|
|
protocol, "perform_handshake"
|
|
|
|
):
|
|
|
|
create_connection.return_value = (transport, protocol)
|
|
|
|
await conn.connect(login=False)
|
|
|
|
|
|
|
|
protocol.data_received(
|
|
|
|
b'\x00@\x02\x08\x01\x10\x07\x1a(m5stackatomproxy (esphome v2023.1.0-dev)"\x10m'
|
|
|
|
)
|
|
|
|
protocol.data_received(b"5stackatomproxy")
|
|
|
|
protocol.data_received(b"\x00\x00$")
|
|
|
|
protocol.data_received(b"\x00\x00\x04")
|
|
|
|
protocol.data_received(
|
|
|
|
b'\x00e\n\x12\x10m5stackatomproxy\x1a\x11E8:9F:6D:0A:68:E0"\x0c2023.1.0-d'
|
|
|
|
)
|
|
|
|
protocol.data_received(
|
|
|
|
b"ev*\x15Jan 7 2023, 13:19:532\x0cm5stack-atomX\x03b\tEspressif"
|
|
|
|
)
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
assert conn.is_connected
|
|
|
|
assert len(messages) == 2
|
|
|
|
assert isinstance(messages[0], HelloResponse)
|
|
|
|
assert isinstance(messages[1], DeviceInfoResponse)
|
|
|
|
assert messages[1].name == "m5stackatomproxy"
|
|
|
|
remove()
|
|
|
|
await conn.force_disconnect()
|
|
|
|
await asyncio.sleep(0)
|