2023-11-11 00:14:00 +01:00
|
|
|
"""Test fixtures."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
import socket
|
2023-11-25 15:59:36 +01:00
|
|
|
from dataclasses import replace
|
2023-11-25 16:58:30 +01:00
|
|
|
from functools import partial
|
|
|
|
from typing import Callable
|
2023-11-11 00:14:00 +01:00
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import pytest_asyncio
|
|
|
|
|
|
|
|
from aioesphomeapi._frame_helper import APIPlaintextFrameHelper
|
|
|
|
from aioesphomeapi.client import APIClient, ConnectionParams
|
|
|
|
from aioesphomeapi.connection import APIConnection
|
|
|
|
from aioesphomeapi.host_resolver import AddrInfo, IPv4Sockaddr
|
2023-11-17 20:11:36 +01:00
|
|
|
from aioesphomeapi.zeroconf import ZeroconfManager
|
2023-11-11 00:14:00 +01:00
|
|
|
|
2023-11-26 20:50:45 +01:00
|
|
|
from .common import (
|
|
|
|
connect,
|
|
|
|
connect_client,
|
|
|
|
get_mock_async_zeroconf,
|
|
|
|
send_plaintext_hello,
|
|
|
|
)
|
2023-11-17 20:11:36 +01:00
|
|
|
|
2023-11-21 14:01:58 +01:00
|
|
|
KEEP_ALIVE_INTERVAL = 15.0
|
|
|
|
|
2023-11-17 20:11:36 +01:00
|
|
|
|
2023-11-23 16:46:56 +01:00
|
|
|
class PatchableAPIConnection(APIConnection):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2023-11-17 20:11:36 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def async_zeroconf():
|
|
|
|
return get_mock_async_zeroconf()
|
2023-11-11 00:14:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
@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-11-26 14:53:48 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def patchable_api_client() -> APIClient:
|
|
|
|
class PatchableAPIClient(APIClient):
|
|
|
|
pass
|
|
|
|
|
|
|
|
cli = PatchableAPIClient(
|
|
|
|
address="1.2.3.4",
|
|
|
|
port=6052,
|
|
|
|
password=None,
|
|
|
|
)
|
|
|
|
return cli
|
|
|
|
|
|
|
|
|
2023-11-24 16:42:56 +01:00
|
|
|
def get_mock_connection_params() -> ConnectionParams:
|
2023-11-11 00:14:00 +01:00
|
|
|
return ConnectionParams(
|
|
|
|
address="fake.address",
|
|
|
|
port=6052,
|
|
|
|
password=None,
|
|
|
|
client_info="Tests client",
|
2023-11-21 14:01:58 +01:00
|
|
|
keepalive=KEEP_ALIVE_INTERVAL,
|
2023-11-17 20:11:36 +01:00
|
|
|
zeroconf_manager=ZeroconfManager(),
|
2023-11-11 00:14:00 +01:00
|
|
|
noise_psk=None,
|
|
|
|
expected_name=None,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-11-24 16:42:56 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def connection_params() -> ConnectionParams:
|
|
|
|
return get_mock_connection_params()
|
|
|
|
|
|
|
|
|
2023-11-26 20:50:45 +01:00
|
|
|
def mock_on_stop(expected_disconnect: bool) -> None:
|
2023-11-21 16:42:23 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def conn(connection_params: ConnectionParams) -> APIConnection:
|
2023-11-26 20:50:45 +01:00
|
|
|
return PatchableAPIConnection(connection_params, mock_on_stop, True, None)
|
2023-11-11 00:14:00 +01:00
|
|
|
|
|
|
|
|
2023-11-21 16:42:23 +01:00
|
|
|
@pytest.fixture
|
2023-11-25 15:59:36 +01:00
|
|
|
def conn_with_password(connection_params: ConnectionParams) -> APIConnection:
|
|
|
|
connection_params = replace(connection_params, password="password")
|
2023-11-26 20:50:45 +01:00
|
|
|
return PatchableAPIConnection(connection_params, mock_on_stop, True, None)
|
2023-11-25 15:59:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def noise_conn(connection_params: ConnectionParams) -> APIConnection:
|
|
|
|
connection_params = replace(
|
|
|
|
connection_params, noise_psk="QRTIErOb/fcE9Ukd/5qA3RGYMn0Y+p06U58SCtOXvPc="
|
|
|
|
)
|
2023-11-26 20:50:45 +01:00
|
|
|
return PatchableAPIConnection(connection_params, mock_on_stop, True, None)
|
2023-11-21 16:42:23 +01:00
|
|
|
|
|
|
|
|
2023-11-25 16:58:30 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def conn_with_expected_name(connection_params: ConnectionParams) -> APIConnection:
|
|
|
|
connection_params = replace(connection_params, expected_name="test")
|
2023-11-26 20:50:45 +01:00
|
|
|
return PatchableAPIConnection(connection_params, mock_on_stop, True, None)
|
2023-11-25 16:58:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _create_mock_transport_protocol(
|
|
|
|
transport: asyncio.Transport,
|
|
|
|
connected: asyncio.Event,
|
|
|
|
create_func: Callable[[], APIPlaintextFrameHelper],
|
|
|
|
**kwargs,
|
|
|
|
) -> tuple[asyncio.Transport, APIPlaintextFrameHelper]:
|
|
|
|
protocol: APIPlaintextFrameHelper = create_func()
|
|
|
|
protocol.connection_made(transport)
|
|
|
|
connected.set()
|
|
|
|
return transport, protocol
|
|
|
|
|
|
|
|
|
2023-11-11 00:14:00 +01:00
|
|
|
@pytest_asyncio.fixture(name="plaintext_connect_task_no_login")
|
|
|
|
async def plaintext_connect_task_no_login(
|
|
|
|
conn: APIConnection, resolve_host, socket_socket, event_loop
|
|
|
|
) -> tuple[APIConnection, asyncio.Transport, APIPlaintextFrameHelper, asyncio.Task]:
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
transport = MagicMock()
|
|
|
|
connected = asyncio.Event()
|
|
|
|
|
|
|
|
with patch.object(event_loop, "sock_connect"), patch.object(
|
2023-11-25 16:58:30 +01:00
|
|
|
loop,
|
|
|
|
"create_connection",
|
|
|
|
side_effect=partial(_create_mock_transport_protocol, transport, connected),
|
2023-11-11 00:14:00 +01:00
|
|
|
):
|
|
|
|
connect_task = asyncio.create_task(connect(conn, login=False))
|
|
|
|
await connected.wait()
|
2023-11-25 16:58:30 +01:00
|
|
|
yield conn, transport, conn._frame_helper, connect_task
|
|
|
|
|
|
|
|
|
|
|
|
@pytest_asyncio.fixture(name="plaintext_connect_task_expected_name")
|
|
|
|
async def plaintext_connect_task_no_login_with_expected_name(
|
|
|
|
conn_with_expected_name: APIConnection, resolve_host, socket_socket, event_loop
|
|
|
|
) -> tuple[APIConnection, asyncio.Transport, APIPlaintextFrameHelper, asyncio.Task]:
|
|
|
|
transport = MagicMock()
|
|
|
|
connected = asyncio.Event()
|
|
|
|
|
|
|
|
with patch.object(event_loop, "sock_connect"), patch.object(
|
|
|
|
event_loop,
|
|
|
|
"create_connection",
|
|
|
|
side_effect=partial(_create_mock_transport_protocol, transport, connected),
|
|
|
|
):
|
|
|
|
connect_task = asyncio.create_task(
|
|
|
|
connect(conn_with_expected_name, login=False)
|
|
|
|
)
|
|
|
|
await connected.wait()
|
|
|
|
yield conn_with_expected_name, transport, conn_with_expected_name._frame_helper, connect_task
|
2023-11-11 00:14:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest_asyncio.fixture(name="plaintext_connect_task_with_login")
|
|
|
|
async def plaintext_connect_task_with_login(
|
2023-11-25 15:59:36 +01:00
|
|
|
conn_with_password: APIConnection, resolve_host, socket_socket, event_loop
|
2023-11-11 00:14:00 +01:00
|
|
|
) -> tuple[APIConnection, asyncio.Transport, APIPlaintextFrameHelper, asyncio.Task]:
|
|
|
|
transport = MagicMock()
|
|
|
|
connected = asyncio.Event()
|
|
|
|
|
|
|
|
with patch.object(event_loop, "sock_connect"), patch.object(
|
2023-11-25 16:58:30 +01:00
|
|
|
event_loop,
|
|
|
|
"create_connection",
|
|
|
|
side_effect=partial(_create_mock_transport_protocol, transport, connected),
|
2023-11-11 00:14:00 +01:00
|
|
|
):
|
2023-11-25 15:59:36 +01:00
|
|
|
connect_task = asyncio.create_task(connect(conn_with_password, login=True))
|
2023-11-11 00:14:00 +01:00
|
|
|
await connected.wait()
|
2023-11-25 16:58:30 +01:00
|
|
|
yield conn_with_password, transport, conn_with_password._frame_helper, connect_task
|
2023-11-11 00:14:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest_asyncio.fixture(name="api_client")
|
|
|
|
async def api_client(
|
2023-11-26 20:50:45 +01:00
|
|
|
resolve_host, socket_socket, event_loop
|
2023-11-11 00:14:00 +01:00
|
|
|
) -> tuple[APIClient, APIConnection, asyncio.Transport, APIPlaintextFrameHelper]:
|
|
|
|
protocol: APIPlaintextFrameHelper | None = None
|
|
|
|
transport = MagicMock()
|
|
|
|
connected = asyncio.Event()
|
|
|
|
client = APIClient(
|
|
|
|
address="mydevice.local",
|
|
|
|
port=6052,
|
|
|
|
password=None,
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch.object(event_loop, "sock_connect"), patch.object(
|
2023-11-25 16:58:30 +01:00
|
|
|
event_loop,
|
|
|
|
"create_connection",
|
|
|
|
side_effect=partial(_create_mock_transport_protocol, transport, connected),
|
2023-11-26 20:50:45 +01:00
|
|
|
), patch("aioesphomeapi.client.APIConnection", PatchableAPIConnection):
|
|
|
|
connect_task = asyncio.create_task(connect_client(client, login=False))
|
2023-11-11 00:14:00 +01:00
|
|
|
await connected.wait()
|
2023-11-26 20:50:45 +01:00
|
|
|
conn = client._connection
|
2023-11-25 16:58:30 +01:00
|
|
|
protocol = conn._frame_helper
|
2023-11-11 20:06:27 +01:00
|
|
|
send_plaintext_hello(protocol)
|
2023-11-11 00:14:00 +01:00
|
|
|
await connect_task
|
|
|
|
transport.reset_mock()
|
|
|
|
yield client, conn, transport, protocol
|