* Upgrade pytest-aiohttp * Make sure executors, tasks and timers are closed Some test will trigger warnings on garbage collect, these warnings spills over into next test. Some test trigger tasks that raise errors on shutdown, these spill over into next test. This is to mimic older pytest-aiohttp and it's behaviour on test cleanup. Discussions on similar changes for pytest-aiohttp are here: https://github.com/pytest-dev/pytest-asyncio/pull/309 * Replace loop with event_loop * Make sure time is frozen for tests * Make sure the ConditionType is not async /home-assistant/homeassistant/helpers/template.py:2082: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited def wrapper(*args, **kwargs): Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. * Increase litejet press tests with a factor 10 The times are simulated anyway, and we can't stop the normal event from occuring. * Use async handlers for aiohttp tests/components/motioneye/test_camera.py::test_get_still_image_from_camera tests/components/motioneye/test_camera.py::test_get_still_image_from_camera tests/components/motioneye/test_camera.py::test_get_stream_from_camera tests/components/motioneye/test_camera.py::test_get_stream_from_camera tests/components/motioneye/test_camera.py::test_camera_option_stream_url_template tests/components/motioneye/test_camera.py::test_camera_option_stream_url_template /Users/joakim/src/hass/home-assistant/venv/lib/python3.9/site-packages/aiohttp/web_urldispatcher.py:189: DeprecationWarning: Bare functions are deprecated, use async ones warnings.warn( * Switch to freezegun in modbus tests The tests allowed clock to tick in between steps * Make sure skybell object are fully mocked Old tests would trigger attempts to post to could services: ``` DEBUG:aioskybell:HTTP post https://cloud.myskybell.com/api/v3/login/ Request with headers: {'content-type': 'application/json', 'accept': '*/*', 'x-skybell-app-id': 'd2b542c7-a7e4-4e1e-b77d-2b76911c7c46', 'x-skybell-client-id': '1f36a3c0-6dee-4997-a6db-4e1c67338e57'} ``` * Fix sorting that broke after rebase
288 lines
8.3 KiB
Python
288 lines
8.3 KiB
Python
"""Common libraries for test setup."""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Generator
|
|
import copy
|
|
import shutil
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock, patch
|
|
import uuid
|
|
|
|
import aiohttp
|
|
from google_nest_sdm import diagnostics
|
|
from google_nest_sdm.auth import AbstractAuth
|
|
from google_nest_sdm.device_manager import DeviceManager
|
|
import pytest
|
|
|
|
from homeassistant.components.application_credentials import (
|
|
async_import_client_credential,
|
|
)
|
|
from homeassistant.components.nest import DOMAIN
|
|
from homeassistant.components.nest.const import CONF_SUBSCRIBER_ID
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from .common import (
|
|
DEVICE_ID,
|
|
PROJECT_ID,
|
|
SUBSCRIBER_ID,
|
|
TEST_CONFIG_APP_CREDS,
|
|
TEST_CONFIG_YAML_ONLY,
|
|
CreateDevice,
|
|
FakeSubscriber,
|
|
NestTestConfig,
|
|
PlatformSetup,
|
|
YieldFixture,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
class FakeAuth(AbstractAuth):
|
|
"""A fake implementation of the auth class that records requests.
|
|
|
|
This class captures the outgoing requests, and can also be used by
|
|
tests to set up fake responses. This class is registered as a response
|
|
handler for a fake aiohttp_server and can simulate successes or failures
|
|
from the API.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize FakeAuth."""
|
|
super().__init__(None, None)
|
|
# Tests can set fake responses here.
|
|
self.responses = []
|
|
# The last request is recorded here.
|
|
self.method = None
|
|
self.url = None
|
|
self.json = None
|
|
self.headers = None
|
|
self.captured_requests = []
|
|
# Set up by fixture
|
|
self.client = None
|
|
|
|
async def async_get_access_token(self) -> str:
|
|
"""Return a valid access token."""
|
|
return ""
|
|
|
|
async def request(self, method, url, **kwargs):
|
|
"""Capure the request arguments for tests to assert on."""
|
|
self.method = method
|
|
self.url = url
|
|
self.json = kwargs.get("json")
|
|
self.headers = kwargs.get("headers")
|
|
self.captured_requests.append((method, url, self.json, self.headers))
|
|
return await self.client.get("/")
|
|
|
|
async def response_handler(self, request):
|
|
"""Handle fake responess for aiohttp_server."""
|
|
if len(self.responses) > 0:
|
|
return self.responses.pop(0)
|
|
return aiohttp.web.json_response()
|
|
|
|
|
|
@pytest.fixture
|
|
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
|
|
"""Return aiohttp_client and allow opening sockets."""
|
|
return aiohttp_client
|
|
|
|
|
|
@pytest.fixture
|
|
async def auth(aiohttp_client):
|
|
"""Fixture for an AbstractAuth."""
|
|
auth = FakeAuth()
|
|
app = aiohttp.web.Application()
|
|
app.router.add_get("/", auth.response_handler)
|
|
app.router.add_post("/", auth.response_handler)
|
|
auth.client = await aiohttp_client(app)
|
|
return auth
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def cleanup_media_storage(hass):
|
|
"""Test cleanup, remove any media storage persisted during the test."""
|
|
tmp_path = str(uuid.uuid4())
|
|
with patch("homeassistant.components.nest.media_source.MEDIA_PATH", new=tmp_path):
|
|
yield
|
|
shutil.rmtree(hass.config.path(tmp_path), ignore_errors=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def subscriber() -> YieldFixture[FakeSubscriber]:
|
|
"""Set up the FakeSusbcriber."""
|
|
subscriber = FakeSubscriber()
|
|
with patch(
|
|
"homeassistant.components.nest.api.GoogleNestSubscriber",
|
|
return_value=subscriber,
|
|
):
|
|
yield subscriber
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_subscriber() -> YieldFixture[AsyncMock]:
|
|
"""Fixture for injecting errors into the subscriber."""
|
|
mock_subscriber = AsyncMock(FakeSubscriber)
|
|
with patch(
|
|
"homeassistant.components.nest.api.GoogleNestSubscriber",
|
|
return_value=mock_subscriber,
|
|
):
|
|
yield mock_subscriber
|
|
|
|
|
|
@pytest.fixture
|
|
async def device_manager(subscriber: FakeSubscriber) -> DeviceManager:
|
|
"""Set up the DeviceManager."""
|
|
return await subscriber.async_get_device_manager()
|
|
|
|
|
|
@pytest.fixture
|
|
async def device_id() -> str:
|
|
"""Fixture to set default device id used when creating devices."""
|
|
return DEVICE_ID
|
|
|
|
|
|
@pytest.fixture
|
|
async def device_type() -> str:
|
|
"""Fixture to set default device type used when creating devices."""
|
|
return "sdm.devices.types.THERMOSTAT"
|
|
|
|
|
|
@pytest.fixture
|
|
async def device_traits() -> dict[str, Any]:
|
|
"""Fixture to set default device traits used when creating devices."""
|
|
return {}
|
|
|
|
|
|
@pytest.fixture
|
|
async def create_device(
|
|
device_manager: DeviceManager,
|
|
auth: FakeAuth,
|
|
device_id: str,
|
|
device_type: str,
|
|
device_traits: dict[str, Any],
|
|
) -> None:
|
|
"""Fixture for creating devices."""
|
|
factory = CreateDevice(device_manager, auth)
|
|
factory.data.update(
|
|
{
|
|
"name": device_id,
|
|
"type": device_type,
|
|
"traits": device_traits,
|
|
}
|
|
)
|
|
return factory
|
|
|
|
|
|
@pytest.fixture
|
|
def platforms() -> list[str]:
|
|
"""Fixture to specify platforms to test."""
|
|
return []
|
|
|
|
|
|
@pytest.fixture
|
|
def subscriber_id() -> str:
|
|
"""Fixture to let tests override subscriber id regardless of configuration type used."""
|
|
return SUBSCRIBER_ID
|
|
|
|
|
|
@pytest.fixture
|
|
def auth_implementation(nest_test_config: NestTestConfig) -> str | None:
|
|
"""Fixture to let tests override the auth implementation in the config entry."""
|
|
return nest_test_config.auth_implementation
|
|
|
|
|
|
@pytest.fixture(
|
|
params=[TEST_CONFIG_YAML_ONLY, TEST_CONFIG_APP_CREDS],
|
|
ids=["yaml-config-only", "app-creds"],
|
|
)
|
|
def nest_test_config(request) -> NestTestConfig:
|
|
"""Fixture that sets up the configuration used for the test."""
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture
|
|
def config(
|
|
subscriber_id: str | None, nest_test_config: NestTestConfig
|
|
) -> dict[str, Any]:
|
|
"""Fixture that sets up the configuration.yaml for the test."""
|
|
config = copy.deepcopy(nest_test_config.config)
|
|
if CONF_SUBSCRIBER_ID in config.get(DOMAIN, {}):
|
|
if subscriber_id:
|
|
config[DOMAIN][CONF_SUBSCRIBER_ID] = subscriber_id
|
|
else:
|
|
del config[DOMAIN][CONF_SUBSCRIBER_ID]
|
|
return config
|
|
|
|
|
|
@pytest.fixture
|
|
def config_entry_unique_id() -> str:
|
|
"""Fixture to set ConfigEntry unique id."""
|
|
return PROJECT_ID
|
|
|
|
|
|
@pytest.fixture
|
|
def config_entry(
|
|
subscriber_id: str | None,
|
|
auth_implementation: str | None,
|
|
nest_test_config: NestTestConfig,
|
|
config_entry_unique_id: str,
|
|
) -> MockConfigEntry | None:
|
|
"""Fixture that sets up the ConfigEntry for the test."""
|
|
if nest_test_config.config_entry_data is None:
|
|
return None
|
|
data = copy.deepcopy(nest_test_config.config_entry_data)
|
|
if CONF_SUBSCRIBER_ID in data:
|
|
if subscriber_id:
|
|
data[CONF_SUBSCRIBER_ID] = subscriber_id
|
|
else:
|
|
del data[CONF_SUBSCRIBER_ID]
|
|
data["auth_implementation"] = auth_implementation
|
|
return MockConfigEntry(domain=DOMAIN, data=data, unique_id=config_entry_unique_id)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def credential(hass: HomeAssistant, nest_test_config: NestTestConfig) -> None:
|
|
"""Fixture that provides the ClientCredential for the test if any."""
|
|
if not nest_test_config.credential:
|
|
return
|
|
assert await async_setup_component(hass, "application_credentials", {})
|
|
await async_import_client_credential(
|
|
hass,
|
|
DOMAIN,
|
|
nest_test_config.credential,
|
|
nest_test_config.auth_implementation,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_base_platform(
|
|
hass: HomeAssistant,
|
|
platforms: list[str],
|
|
config: dict[str, Any],
|
|
config_entry: MockConfigEntry | None,
|
|
) -> YieldFixture[PlatformSetup]:
|
|
"""Fixture to setup the integration platform."""
|
|
if config_entry:
|
|
config_entry.add_to_hass(hass)
|
|
with patch("homeassistant.components.nest.PLATFORMS", platforms):
|
|
|
|
async def _setup_func() -> bool:
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
yield _setup_func
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_platform(
|
|
setup_base_platform: PlatformSetup, subscriber: FakeSubscriber
|
|
) -> PlatformSetup:
|
|
"""Fixture to setup the integration platform and subscriber."""
|
|
return setup_base_platform
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_diagnostics() -> Generator[None, None, None]:
|
|
"""Fixture to reset client library diagnostic counters."""
|
|
yield
|
|
diagnostics.reset()
|