Bump python-google-nest-sdm to 7.0.0 (#134016)

Update python-google-nest-sdm to 7.0.0
This commit is contained in:
Allen Porter 2024-12-25 21:03:44 -08:00 committed by GitHub
parent 299250ebec
commit c75222e63c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 329 additions and 377 deletions

View File

@ -55,6 +55,7 @@ from homeassistant.helpers.typing import ConfigType
from . import api
from .const import (
CONF_CLOUD_PROJECT_ID,
CONF_PROJECT_ID,
CONF_SUBSCRIBER_ID,
CONF_SUBSCRIBER_ID_IMPORTED,
@ -214,33 +215,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: NestConfigEntry) -> bool
update_callback = SignalUpdateCallback(hass, async_config_reload, entry)
subscriber.set_update_callback(update_callback.async_handle_event)
try:
await subscriber.start_async()
unsub = await subscriber.start_async()
except AuthException as err:
raise ConfigEntryAuthFailed(
f"Subscriber authentication error: {err!s}"
) from err
except ConfigurationException as err:
_LOGGER.error("Configuration error: %s", err)
subscriber.stop_async()
return False
except SubscriberException as err:
subscriber.stop_async()
raise ConfigEntryNotReady(f"Subscriber error: {err!s}") from err
try:
device_manager = await subscriber.async_get_device_manager()
except ApiException as err:
subscriber.stop_async()
unsub()
raise ConfigEntryNotReady(f"Device manager error: {err!s}") from err
@callback
def on_hass_stop(_: Event) -> None:
"""Close connection when hass stops."""
subscriber.stop_async()
unsub()
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
)
entry.async_on_unload(unsub)
entry.runtime_data = NestData(
subscriber=subscriber,
device_manager=device_manager,
@ -253,12 +254,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: NestConfigEntry) -> bool
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if DATA_SDM not in entry.data:
# Legacy API
return True
_LOGGER.debug("Stopping nest subscriber")
subscriber = entry.runtime_data.subscriber
subscriber.stop_async()
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@ -272,24 +267,25 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
or CONF_SUBSCRIBER_ID_IMPORTED in entry.data
):
return
subscriber = await api.new_subscriber(hass, entry)
if not subscriber:
return
_LOGGER.debug("Deleting subscriber '%s'", subscriber.subscriber_id)
if (subscription_name := entry.data.get(CONF_SUBSCRIPTION_NAME)) is None:
subscription_name = entry.data[CONF_SUBSCRIBER_ID]
admin_client = api.new_pubsub_admin_client(
hass,
access_token=entry.data["token"]["access_token"],
cloud_project_id=entry.data[CONF_CLOUD_PROJECT_ID],
)
_LOGGER.debug("Deleting subscription '%s'", subscription_name)
try:
await subscriber.delete_subscription()
except (AuthException, SubscriberException) as err:
await admin_client.delete_subscription(subscription_name)
except ApiException as err:
_LOGGER.warning(
(
"Unable to delete subscription '%s'; Will be automatically cleaned up"
" by cloud console: %s"
),
subscriber.subscriber_id,
subscription_name,
err,
)
finally:
subscriber.stop_async()
class NestEventViewBase(HomeAssistantView, ABC):

View File

@ -19,5 +19,5 @@
"documentation": "https://www.home-assistant.io/integrations/nest",
"iot_class": "cloud_push",
"loggers": ["google_nest_sdm"],
"requirements": ["google-nest-sdm==6.1.5"]
"requirements": ["google-nest-sdm==7.0.0"]
}

View File

@ -1024,7 +1024,7 @@ google-cloud-texttospeech==2.17.2
google-generativeai==0.8.2
# homeassistant.components.nest
google-nest-sdm==6.1.5
google-nest-sdm==7.0.0
# homeassistant.components.google_photos
google-photos-library-api==0.12.1

View File

@ -874,7 +874,7 @@ google-cloud-texttospeech==2.17.2
google-generativeai==0.8.2
# homeassistant.components.nest
google-nest-sdm==6.1.5
google-nest-sdm==7.0.0
# homeassistant.components.google_photos
google-photos-library-api==0.12.1

View File

@ -5,17 +5,14 @@ from __future__ import annotations
from collections.abc import Awaitable, Callable, Generator
import copy
from dataclasses import dataclass
import re
from typing import Any
from google_nest_sdm.auth import AbstractAuth
from google_nest_sdm.device import Device
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.event import EventMessage
from google_nest_sdm.event_media import CachePolicy
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
from google_nest_sdm.streaming_manager import Message
from homeassistant.components.application_credentials import ClientCredential
from homeassistant.components.nest import DOMAIN
from homeassistant.components.nest.const import API_URL
# Typing helpers
type PlatformSetup = Callable[[], Awaitable[None]]
@ -66,68 +63,22 @@ TEST_CONFIG_NEW_SUBSCRIPTION = NestTestConfig(
credential=ClientCredential(CLIENT_ID, CLIENT_SECRET),
)
class FakeSubscriber(GoogleNestSubscriber):
"""Fake subscriber that supplies a FakeDeviceManager."""
stop_calls = 0
def __init__(self) -> None: # pylint: disable=super-init-not-called
"""Initialize Fake Subscriber."""
self._device_manager = DeviceManager()
self._subscriber_name = "fake-name"
def set_update_callback(self, target: Callable[[EventMessage], Awaitable[None]]):
"""Capture the callback set by Home Assistant."""
self._device_manager.set_update_callback(target)
async def create_subscription(self):
"""Create the subscription."""
return
async def delete_subscription(self):
"""Delete the subscription."""
return
async def start_async(self):
"""Return the fake device manager."""
return self._device_manager
async def async_get_device_manager(self) -> DeviceManager:
"""Return the fake device manager."""
return self._device_manager
@property
def cache_policy(self) -> CachePolicy:
"""Return the cache policy."""
return self._device_manager.cache_policy
def stop_async(self):
"""No-op to stop the subscriber."""
self.stop_calls += 1
async def async_receive_event(self, event_message: EventMessage):
"""Simulate a received pubsub message, invoked by tests."""
# Update device state, then invoke HomeAssistant to refresh
await self._device_manager.async_handle_event(event_message)
DEVICE_ID = "enterprise/project-id/devices/device-id"
DEVICE_ID = "enterprises/project-id/devices/device-id"
DEVICE_COMMAND = f"{DEVICE_ID}:executeCommand"
DEVICE_URL_MATCH = re.compile(
f"{API_URL}/enterprises/project-id/devices/[^:]+:executeCommand"
)
TEST_IMAGE_URL = "https://domain/sdm_event_snapshot/dGTZwR3o4Y1..."
TEST_CLIP_URL = "https://domain/clip/XyZ.mp4"
class CreateDevice:
"""Fixture used for creating devices."""
def __init__(
self,
device_manager: DeviceManager,
auth: AbstractAuth,
) -> None:
def __init__(self) -> None:
"""Initialize CreateDevice."""
self.device_manager = device_manager
self.auth = auth
self.data = {"traits": {}}
self.devices = []
def create(
self,
@ -138,4 +89,9 @@ class CreateDevice:
data = copy.deepcopy(self.data)
data.update(raw_data if raw_data else {})
data["traits"].update(raw_traits if raw_traits else {})
self.device_manager.add_device(Device.MakeDevice(data, auth=self.auth))
self.devices.append(data)
def create_nest_event(data: dict[str, Any]) -> Message:
"""Create a pub/sub event message for testing."""
return Message.from_data(data)

View File

@ -2,7 +2,6 @@
from __future__ import annotations
from asyncio import AbstractEventLoop
from collections.abc import Generator
import copy
import shutil
@ -13,100 +12,131 @@ 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
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
from google_nest_sdm.streaming_manager import StreamingManager
import pytest
from yarl import URL
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, SDM_SCOPES
from homeassistant.components.nest.const import API_URL, CONF_SUBSCRIBER_ID, SDM_SCOPES
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .common import (
DEVICE_ID,
DEVICE_URL_MATCH,
PROJECT_ID,
SUBSCRIBER_ID,
TEST_CLIP_URL,
TEST_CONFIG_APP_CREDS,
TEST_IMAGE_URL,
CreateDevice,
FakeSubscriber,
NestTestConfig,
PlatformSetup,
YieldFixture,
)
from tests.common import MockConfigEntry
from tests.typing import ClientSessionGenerator
from tests.test_util.aiohttp import AiohttpClientMocker, AiohttpClientMockResponse
FAKE_TOKEN = "some-token"
FAKE_REFRESH_TOKEN = "some-refresh-token"
class FakeAuth(AbstractAuth):
"""A fake implementation of the auth class that records requests.
class FakeAuth:
"""A fixture for request handling 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.
This class is used with AiohttpClientMocker to capture outgoing requests
and can also be used by tests to set up fake responses.
"""
def __init__(self) -> None:
def __init__(
self,
aioclient_mock: AiohttpClientMocker,
device_factory: CreateDevice,
project_id: str,
) -> None:
"""Initialize FakeAuth."""
super().__init__(None, None)
# Tests can set fake responses here.
self.responses = []
# Tests can factory fixture to create fake device responses.
self.device_factory = device_factory
# Tests can set fake structure responses here.
self.structures: list[dict[str, Any]] = []
# Tests can set fake command responses here.
self.responses: list[aiohttp.web.Response] = []
# 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 ""
# API makes a call to request structures to initiate pubsub feed, but the
# integration does not use this.
aioclient_mock.get(
f"{API_URL}/enterprises/{project_id}/structures",
side_effect=self.request_structures,
)
aioclient_mock.get(
f"{API_URL}/enterprises/{project_id}/devices",
side_effect=self.request_devices,
)
aioclient_mock.post(DEVICE_URL_MATCH, side_effect=self.request)
aioclient_mock.get(TEST_IMAGE_URL, side_effect=self.request)
aioclient_mock.get(TEST_CLIP_URL, side_effect=self.request)
async def request(self, method, url, **kwargs):
async def request_structures(
self, method: str, url: str, data: dict[str, Any]
) -> AiohttpClientMockResponse:
"""Handle requests to create devices."""
return AiohttpClientMockResponse(
method, url, json={"structures": self.structures}
)
async def request_devices(
self, method: str, url: str, data: dict[str, Any]
) -> AiohttpClientMockResponse:
"""Handle requests to create devices."""
return AiohttpClientMockResponse(
method, url, json={"devices": self.device_factory.devices}
)
async def request(
self, method: str, url: URL, data: dict[str, Any]
) -> AiohttpClientMockResponse:
"""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("/")
str_url = str(url)
self.url = str_url[len(API_URL) + 1 :]
self.json = data
self.captured_requests.append((method, url, self.json))
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()
response = self.responses.pop(0)
return AiohttpClientMockResponse(
method, url, response=response.body, status=response.status
)
return AiohttpClientMockResponse(method, url)
@pytest.fixture(name="device_access_project_id")
def mock_device_access_project_id() -> str:
"""Fixture to configure the device access console project id used in tests."""
return PROJECT_ID
@pytest.fixture
def aiohttp_client(
event_loop: AbstractEventLoop,
aiohttp_client: ClientSessionGenerator,
socket_enabled: None,
) -> ClientSessionGenerator:
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client
@pytest.fixture
async def auth(aiohttp_client: ClientSessionGenerator) -> FakeAuth:
async def auth(
aioclient_mock: AiohttpClientMocker,
create_device: CreateDevice,
device_access_project_id: str,
) -> FakeAuth:
"""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
return FakeAuth(aioclient_mock, create_device, device_access_project_id)
@pytest.fixture(autouse=True)
@ -119,20 +149,32 @@ def cleanup_media_storage(hass: HomeAssistant) -> Generator[None]:
@pytest.fixture
def subscriber() -> YieldFixture[FakeSubscriber]:
"""Set up the FakeSusbcriber."""
subscriber = FakeSubscriber()
def subscriber_side_effect() -> Any | None:
"""Fixture to inject failures into FakeSubscriber start."""
return None
@pytest.fixture(autouse=True)
def subscriber(subscriber_side_effect: Any | None) -> Generator[AsyncMock]:
"""Fixture to allow tests to emulate the pub/sub subscriber receiving messages."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber",
return_value=subscriber,
):
yield subscriber
"google_nest_sdm.google_nest_subscriber.StreamingManager", spec=StreamingManager
) as mock_manager:
# Use side_effect to capture the callback
def mock_init(**kwargs: Any) -> AsyncMock:
mock_manager.async_receive_event = kwargs["callback"]
if subscriber_side_effect is not None:
mock_manager.start.side_effect = subscriber_side_effect
return mock_manager
mock_manager.side_effect = mock_init
yield mock_manager
@pytest.fixture
def mock_subscriber() -> YieldFixture[AsyncMock]:
"""Fixture for injecting errors into the subscriber."""
mock_subscriber = AsyncMock(FakeSubscriber)
mock_subscriber = AsyncMock(GoogleNestSubscriber)
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber",
return_value=mock_subscriber,
@ -140,12 +182,6 @@ def mock_subscriber() -> YieldFixture[AsyncMock]:
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."""
@ -166,14 +202,12 @@ async def device_traits() -> dict[str, Any]:
@pytest.fixture
async def create_device(
device_manager: DeviceManager,
auth: FakeAuth,
device_id: str,
device_type: str,
device_traits: dict[str, Any],
) -> CreateDevice:
"""Fixture for creating devices."""
factory = CreateDevice(device_manager, auth)
factory = CreateDevice()
factory.data.update(
{
"name": device_id,
@ -262,6 +296,7 @@ async def setup_base_platform(
hass: HomeAssistant,
platforms: list[str],
config_entry: MockConfigEntry | None,
auth: FakeAuth,
) -> YieldFixture[PlatformSetup]:
"""Fixture to setup the integration platform."""
config_entry.add_to_hass(hass)
@ -278,7 +313,8 @@ async def setup_base_platform(
@pytest.fixture
async def setup_platform(
setup_base_platform: PlatformSetup, subscriber: FakeSubscriber
setup_base_platform: PlatformSetup,
subscriber: AsyncMock,
) -> PlatformSetup:
"""Fixture to setup the integration platform and subscriber."""
return setup_base_platform

View File

@ -31,6 +31,9 @@
}),
}),
]),
'subscriber': dict({
'start': 1,
}),
})
# ---
# name: test_device_diagnostics
@ -85,5 +88,8 @@
}),
}),
]),
'subscriber': dict({
'start': 1,
}),
})
# ---

View File

@ -9,16 +9,16 @@ The tests below exercise both cases during integration setup.
"""
import time
from unittest.mock import patch
from unittest.mock import AsyncMock, Mock, patch
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
from google.oauth2.credentials import Credentials
import pytest
from homeassistant.components.nest.const import API_URL, OAUTH2_TOKEN, SDM_SCOPES
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from .common import CLIENT_ID, CLIENT_SECRET, PROJECT_ID, FakeSubscriber, PlatformSetup
from .common import CLIENT_ID, CLIENT_SECRET, PROJECT_ID, PlatformSetup
from .conftest import FAKE_REFRESH_TOKEN, FAKE_TOKEN
from tests.test_util.aiohttp import AiohttpClientMocker
@ -27,7 +27,7 @@ FAKE_UPDATED_TOKEN = "fake-updated-token"
@pytest.fixture
def subscriber() -> FakeSubscriber | None:
def subscriber() -> Mock | None:
"""Disable default subscriber since tests use their own patch."""
return None
@ -54,16 +54,16 @@ async def test_auth(
# Prepare to capture credentials for Subscriber
captured_creds = None
async def async_new_subscriber(
creds, subscription_name, event_loop, async_callback
) -> GoogleNestSubscriber | None:
def async_new_subscriber(
credentials: Credentials,
) -> Mock:
"""Capture credentials for tests."""
nonlocal captured_creds
captured_creds = creds
return None # GoogleNestSubscriber
captured_creds = credentials
return AsyncMock()
with patch(
"google_nest_sdm.google_nest_subscriber.DefaultSubscriberFactory.async_new_subscriber",
"google_nest_sdm.subscriber_client.pubsub_v1.SubscriberAsyncClient",
side_effect=async_new_subscriber,
) as new_subscriber_mock:
await setup_platform()
@ -122,16 +122,16 @@ async def test_auth_expired_token(
# Prepare to capture credentials for Subscriber
captured_creds = None
async def async_new_subscriber(
creds, subscription_name, event_loop, async_callback
) -> GoogleNestSubscriber | None:
def async_new_subscriber(
credentials: Credentials,
) -> Mock:
"""Capture credentials for tests."""
nonlocal captured_creds
captured_creds = creds
return None # GoogleNestSubscriber
captured_creds = credentials
return AsyncMock()
with patch(
"google_nest_sdm.google_nest_subscriber.DefaultSubscriberFactory.async_new_subscriber",
"google_nest_sdm.subscriber_client.pubsub_v1.SubscriberAsyncClient",
side_effect=async_new_subscriber,
) as new_subscriber_mock:
await setup_platform()

View File

@ -24,7 +24,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from .common import DEVICE_ID, CreateDevice, FakeSubscriber, PlatformSetup
from .common import DEVICE_ID, CreateDevice, PlatformSetup
from .conftest import FakeAuth
from tests.common import async_fire_time_changed
@ -520,13 +520,10 @@ async def test_camera_removed(
hass: HomeAssistant,
auth: FakeAuth,
camera_device: None,
subscriber: FakeSubscriber,
setup_platform: PlatformSetup,
) -> None:
"""Test case where entities are removed and stream tokens revoked."""
await setup_platform()
# Simplify test setup
subscriber.cache_policy.fetch = False
assert len(hass.states.async_all()) == 1
cam = hass.states.get("camera.my_camera")
@ -780,7 +777,7 @@ async def test_camera_web_rtc_offer_failure(
assert response["event"] == {
"type": "error",
"code": "webrtc_offer_failed",
"message": "Nest API error: Bad Request response from API (400)",
"message": "Nest API error: response from API (400)",
}

View File

@ -7,10 +7,9 @@ pubsub subscriber.
from collections.abc import Awaitable, Callable
from http import HTTPStatus
from typing import Any
from unittest.mock import AsyncMock
import aiohttp
from google_nest_sdm.auth import AbstractAuth
from google_nest_sdm.event import EventMessage
import pytest
from homeassistant.components.climate import (
@ -45,8 +44,8 @@ from .common import (
DEVICE_COMMAND,
DEVICE_ID,
CreateDevice,
FakeSubscriber,
PlatformSetup,
create_nest_event,
)
from .conftest import FakeAuth
@ -72,14 +71,13 @@ def device_traits() -> dict[str, Any]:
@pytest.fixture
async def create_event(
hass: HomeAssistant,
auth: AbstractAuth,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
) -> CreateEvent:
"""Fixture to send a pub/sub event."""
async def create_event(traits: dict[str, Any]) -> None:
await subscriber.async_receive_event(
EventMessage.create_event(
create_nest_event(
{
"eventId": EVENT_ID,
"timestamp": "2019-01-01T00:00:01Z",
@ -88,7 +86,6 @@ async def create_event(
"traits": traits,
},
},
auth=auth,
)
)
await hass.async_block_till_done()

View File

@ -7,7 +7,6 @@ from typing import Any
from unittest.mock import patch
from google_nest_sdm.exceptions import AuthException
from google_nest_sdm.structure import Structure
import pytest
from homeassistant import config_entries
@ -25,9 +24,9 @@ from .common import (
SUBSCRIBER_ID,
TEST_CONFIG_APP_CREDS,
TEST_CONFIGFLOW_APP_CREDS,
FakeSubscriber,
NestTestConfig,
)
from .conftest import FakeAuth, PlatformSetup
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
@ -58,6 +57,11 @@ def mock_rand_topic_name_fixture() -> None:
yield
@pytest.fixture(autouse=True)
def mock_request_setup(auth: FakeAuth) -> None:
"""Fixture to ensure fake requests are setup."""
class OAuthFixture:
"""Simulate the oauth flow used by the config flow."""
@ -257,12 +261,6 @@ def mock_subscriptions() -> list[tuple[str, str]]:
return []
@pytest.fixture(name="device_access_project_id")
def mock_device_access_project_id() -> str:
"""Fixture to configure the device access console project id used in tests."""
return PROJECT_ID
@pytest.fixture(name="cloud_project_id")
def mock_cloud_project_id() -> str:
"""Fixture to configure the cloud console project id used in tests."""
@ -350,8 +348,7 @@ def mock_pubsub_api_responses(
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_app_credentials(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Check full flow."""
result = await hass.config_entries.flow.async_init(
@ -388,7 +385,7 @@ async def test_app_credentials(
("sdm_managed_topic", "device_access_project_id", "cloud_project_id"),
[(True, "new-project-id", "new-cloud-project-id")],
)
async def test_config_flow_restart(hass: HomeAssistant, oauth, subscriber) -> None:
async def test_config_flow_restart(hass: HomeAssistant, oauth: OAuthFixture) -> None:
"""Check with auth implementation is re-initialized when aborting the flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -443,8 +440,7 @@ async def test_config_flow_restart(hass: HomeAssistant, oauth, subscriber) -> No
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_config_flow_wrong_project_id(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Check the case where the wrong project ids are entered."""
result = await hass.config_entries.flow.async_init(
@ -500,8 +496,7 @@ async def test_config_flow_wrong_project_id(
)
async def test_config_flow_pubsub_configuration_error(
hass: HomeAssistant,
oauth,
mock_subscriber,
oauth: OAuthFixture,
) -> None:
"""Check full flow fails with configuration error."""
result = await hass.config_entries.flow.async_init(
@ -546,7 +541,8 @@ async def test_config_flow_pubsub_configuration_error(
[(True, HTTPStatus.INTERNAL_SERVER_ERROR)],
)
async def test_config_flow_pubsub_subscriber_error(
hass: HomeAssistant, oauth, mock_subscriber
hass: HomeAssistant,
oauth: OAuthFixture,
) -> None:
"""Check full flow with a subscriber error."""
result = await hass.config_entries.flow.async_init(
@ -697,7 +693,8 @@ async def test_reauth_multiple_config_entries(
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_pubsub_subscription_strip_whitespace(
hass: HomeAssistant, oauth, subscriber
hass: HomeAssistant,
oauth: OAuthFixture,
) -> None:
"""Check that project id has whitespace stripped on entry."""
result = await hass.config_entries.flow.async_init(
@ -776,10 +773,9 @@ async def test_pubsub_subscription_auth_failure(
)
async def test_pubsub_subscriber_config_entry_reauth(
hass: HomeAssistant,
oauth,
setup_platform,
subscriber,
config_entry,
oauth: OAuthFixture,
setup_platform: PlatformSetup,
config_entry: MockConfigEntry,
) -> None:
"""Test the pubsub subscriber id is preserved during reauth."""
await setup_platform()
@ -805,22 +801,21 @@ async def test_pubsub_subscriber_config_entry_reauth(
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_config_entry_title_from_home(
hass: HomeAssistant, oauth, subscriber
hass: HomeAssistant,
oauth: OAuthFixture,
auth: FakeAuth,
) -> None:
"""Test that the Google Home name is used for the config entry title."""
device_manager = await subscriber.async_get_device_manager()
device_manager.add_structure(
Structure.MakeStructure(
{
"name": f"enterprise/{PROJECT_ID}/structures/some-structure-id",
"traits": {
"sdm.structures.traits.Info": {
"customName": "Example Home",
},
auth.structures.append(
{
"name": f"enterprise/{PROJECT_ID}/structures/some-structure-id",
"traits": {
"sdm.structures.traits.Info": {
"customName": "Example Home",
},
}
)
},
}
)
result = await hass.config_entries.flow.async_init(
@ -848,13 +843,13 @@ async def test_config_entry_title_from_home(
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_config_entry_title_multiple_homes(
hass: HomeAssistant, oauth, subscriber
hass: HomeAssistant,
oauth: OAuthFixture,
auth: FakeAuth,
) -> None:
"""Test handling of multiple Google Homes authorized."""
device_manager = await subscriber.async_get_device_manager()
device_manager.add_structure(
Structure.MakeStructure(
auth.structures.extend(
[
{
"name": f"enterprise/{PROJECT_ID}/structures/id-1",
"traits": {
@ -862,11 +857,7 @@ async def test_config_entry_title_multiple_homes(
"customName": "Example Home #1",
},
},
}
)
)
device_manager.add_structure(
Structure.MakeStructure(
},
{
"name": f"enterprise/{PROJECT_ID}/structures/id-2",
"traits": {
@ -874,8 +865,8 @@ async def test_config_entry_title_multiple_homes(
"customName": "Example Home #2",
},
},
}
)
},
]
)
result = await hass.config_entries.flow.async_init(
@ -923,18 +914,17 @@ async def test_title_failure_fallback(
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_structure_missing_trait(hass: HomeAssistant, oauth, subscriber) -> None:
async def test_structure_missing_trait(
hass: HomeAssistant, oauth: OAuthFixture, auth: FakeAuth
) -> None:
"""Test handling the case where a structure has no name set."""
device_manager = await subscriber.async_get_device_manager()
device_manager.add_structure(
Structure.MakeStructure(
{
"name": f"enterprise/{PROJECT_ID}/structures/id-1",
# Missing Info trait
"traits": {},
}
)
auth.structures.append(
{
"name": f"enterprise/{PROJECT_ID}/structures/id-1",
# Missing Info trait
"traits": {},
}
)
result = await hass.config_entries.flow.async_init(
@ -973,8 +963,7 @@ async def test_dhcp_discovery(
@pytest.mark.parametrize(("sdm_managed_topic"), [(True)])
async def test_dhcp_discovery_with_creds(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Exercise discovery dhcp with no config present (can't run)."""
result = await hass.config_entries.flow.async_init(
@ -1029,7 +1018,6 @@ async def test_dhcp_discovery_with_creds(
async def test_token_error(
hass: HomeAssistant,
oauth: OAuthFixture,
subscriber: FakeSubscriber,
status_code: HTTPStatus,
error_reason: str,
) -> None:
@ -1064,8 +1052,7 @@ async def test_token_error(
)
async def test_existing_topic_and_subscription(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Test selecting existing user managed topic and subscription."""
result = await hass.config_entries.flow.async_init(
@ -1103,8 +1090,7 @@ async def test_existing_topic_and_subscription(
async def test_no_eligible_topics(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Test the case where there are no eligible pub/sub topics."""
result = await hass.config_entries.flow.async_init(
@ -1127,8 +1113,7 @@ async def test_no_eligible_topics(
)
async def test_list_topics_failure(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Test selecting existing user managed topic and subscription."""
result = await hass.config_entries.flow.async_init(
@ -1151,8 +1136,7 @@ async def test_list_topics_failure(
)
async def test_list_subscriptions_failure(
hass: HomeAssistant,
oauth,
subscriber,
oauth: OAuthFixture,
) -> None:
"""Test selecting existing user managed topic and subscription."""
result = await hass.config_entries.flow.async_init(

View File

@ -1,8 +1,8 @@
"""The tests for Nest device triggers."""
from typing import Any
from unittest.mock import AsyncMock
from google_nest_sdm.event import EventMessage
import pytest
from pytest_unordered import unordered
@ -18,7 +18,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from .common import DEVICE_ID, CreateDevice, FakeSubscriber, PlatformSetup
from .common import DEVICE_ID, CreateDevice, PlatformSetup, create_nest_event
from tests.common import async_get_device_automations
@ -447,7 +447,7 @@ async def test_subscriber_automation(
service_calls: list[ServiceCall],
create_device: CreateDevice,
setup_platform: PlatformSetup,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
) -> None:
"""Test end to end subscriber triggers automation."""
create_device.create(
@ -465,7 +465,7 @@ async def test_subscriber_automation(
assert await setup_automation(hass, device_entry.id, "camera_motion")
# Simulate a pubsub message received by the subscriber with a motion event
event = EventMessage.create_event(
event = create_nest_event(
{
"eventId": "some-event-id",
"timestamp": "2019-01-01T00:00:01Z",
@ -479,7 +479,6 @@ async def test_subscriber_automation(
},
},
},
auth=None,
)
await subscriber.async_receive_event(event)
await hass.async_block_till_done()

View File

@ -2,7 +2,7 @@
import datetime
from typing import Any
from unittest.mock import patch
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from google_nest_sdm.event import EventMessage, EventType
@ -13,7 +13,7 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utcnow
from .common import DEVICE_ID, CreateDevice, FakeSubscriber
from .common import DEVICE_ID, TEST_CLIP_URL, CreateDevice, create_nest_event
from .conftest import PlatformSetup
EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..."
@ -31,14 +31,6 @@ def platforms() -> list[Platform]:
return [Platform.EVENT]
@pytest.fixture(autouse=True)
def enable_prefetch(subscriber: FakeSubscriber) -> None:
"""Fixture to enable media fetching for tests to exercise."""
subscriber.cache_policy.fetch = True
with patch("homeassistant.components.nest.EVENT_MEDIA_CACHE_SIZE", new=5):
yield
@pytest.fixture
def device_type() -> str:
"""Fixture for the type of device under test."""
@ -80,7 +72,7 @@ def create_event_messages(
events: dict[str, Any], parameters: dict[str, Any] | None = None
) -> EventMessage:
"""Create an EventMessage for events."""
return EventMessage.create_event(
return create_nest_event(
{
"eventId": "some-event-id",
"timestamp": utcnow().isoformat(timespec="seconds"),
@ -90,7 +82,6 @@ def create_event_messages(
},
**(parameters if parameters else {}),
},
auth=None,
)
@ -152,7 +143,7 @@ def create_event_messages(
)
async def test_receive_events(
hass: HomeAssistant,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
setup_platform: PlatformSetup,
create_device: CreateDevice,
trait_types: list[TraitType],
@ -192,7 +183,7 @@ async def test_receive_events(
@pytest.mark.parametrize(("trait_type"), [(TraitType.DOORBELL_CHIME)])
async def test_ignore_unrelated_event(
hass: HomeAssistant,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
setup_platform: PlatformSetup,
create_device: CreateDevice,
trait_type: TraitType,
@ -222,7 +213,7 @@ async def test_ignore_unrelated_event(
@pytest.mark.freeze_time("2024-08-24T12:00:00Z")
async def test_event_threads(
hass: HomeAssistant,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
setup_platform: PlatformSetup,
create_device: CreateDevice,
freezer: FrozenDateTimeFactory,
@ -275,7 +266,7 @@ async def test_event_threads(
},
EventType.CAMERA_CLIP_PREVIEW: {
"eventSessionId": EVENT_SESSION_ID,
"previewUrl": "http://example",
"previewUrl": TEST_CLIP_URL,
},
},
parameters={"eventThreadState": "ENDED"},
@ -306,7 +297,7 @@ async def test_event_threads(
},
EventType.CAMERA_CLIP_PREVIEW: {
"eventSessionId": EVENT_SESSION_ID2,
"previewUrl": "http://example",
"previewUrl": TEST_CLIP_URL,
},
},
parameters={"eventThreadState": "ENDED"},

View File

@ -9,26 +9,38 @@ from __future__ import annotations
from collections.abc import Mapping
import datetime
from typing import Any
from unittest.mock import patch
from unittest.mock import AsyncMock
from google_nest_sdm.device import Device
from google_nest_sdm.event import EventMessage
import aiohttp
import pytest
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util.dt import utcnow
from .common import CreateDevice
from .common import (
DEVICE_ID,
TEST_CLIP_URL,
TEST_IMAGE_URL,
CreateDevice,
PlatformSetup,
create_nest_event,
)
from tests.common import async_capture_events
DOMAIN = "nest"
DEVICE_ID = "some-device-id"
PLATFORM = "camera"
NEST_EVENT = "nest_event"
EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..."
EVENT_ID = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
GENERATE_IMAGE_URL_RESPONSE = {
"results": {
"url": TEST_IMAGE_URL,
"token": "g.0.eventToken",
},
}
IMAGE_BYTES_FROM_EVENT = b"test url image bytes"
EVENT_KEYS = {"device_id", "type", "timestamp", "zones"}
@ -104,7 +116,7 @@ def create_events(events, device_id=DEVICE_ID, timestamp=None):
"""Create an EventMessage for events."""
if not timestamp:
timestamp = utcnow()
return EventMessage.create_event(
return create_nest_event(
{
"eventId": "some-event-id",
"timestamp": timestamp.isoformat(timespec="seconds"),
@ -113,7 +125,6 @@ def create_events(events, device_id=DEVICE_ID, timestamp=None):
"events": events,
},
},
auth=None,
)
@ -167,7 +178,7 @@ async def test_event(
entry = entity_registry.async_get("camera.front")
assert entry is not None
assert entry.unique_id == "some-device-id-camera"
assert entry.unique_id == f"{DEVICE_ID}-camera"
assert entry.domain == "camera"
device = device_registry.async_get(entry.device_id)
@ -175,6 +186,11 @@ async def test_event(
assert device.model == expected_model
assert device.identifiers == {("nest", DEVICE_ID)}
auth.responses = [
aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE),
aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT),
]
timestamp = utcnow()
await subscriber.async_receive_event(create_event(event_trait, timestamp=timestamp))
await hass.async_block_till_done()
@ -300,12 +316,11 @@ async def test_event_message_without_device_event(
events = async_capture_events(hass, NEST_EVENT)
await setup_platform()
timestamp = utcnow()
event = EventMessage.create_event(
event = create_nest_event(
{
"eventId": "some-event-id",
"timestamp": timestamp.isoformat(timespec="seconds"),
},
auth=None,
)
await subscriber.async_receive_event(event)
await hass.async_block_till_done()
@ -339,7 +354,7 @@ async def test_doorbell_event_thread(
},
"sdm.devices.events.CameraClipPreview.ClipPreview": {
"eventSessionId": EVENT_SESSION_ID,
"previewUrl": "image-url-1",
"previewUrl": TEST_CLIP_URL,
},
},
},
@ -356,9 +371,7 @@ async def test_doorbell_event_thread(
"eventThreadState": "STARTED",
}
)
await subscriber.async_receive_event(
EventMessage.create_event(message_data_1, auth=None)
)
await subscriber.async_receive_event(create_nest_event(message_data_1))
# Publish message #2 that sends a no-op update to end the event thread
timestamp2 = timestamp1 + datetime.timedelta(seconds=1)
@ -369,9 +382,7 @@ async def test_doorbell_event_thread(
"eventThreadState": "ENDED",
}
)
await subscriber.async_receive_event(
EventMessage.create_event(message_data_2, auth=None)
)
await subscriber.async_receive_event(create_nest_event(message_data_2))
await hass.async_block_till_done()
# The event is only published once
@ -415,7 +426,7 @@ async def test_doorbell_event_session_update(
},
"sdm.devices.events.CameraClipPreview.ClipPreview": {
"eventSessionId": EVENT_SESSION_ID,
"previewUrl": "image-url-1",
"previewUrl": TEST_CLIP_URL,
},
},
timestamp=timestamp1,
@ -437,7 +448,7 @@ async def test_doorbell_event_session_update(
},
"sdm.devices.events.CameraClipPreview.ClipPreview": {
"eventSessionId": EVENT_SESSION_ID,
"previewUrl": "image-url-1",
"previewUrl": TEST_CLIP_URL,
},
},
timestamp=timestamp2,
@ -459,7 +470,11 @@ async def test_doorbell_event_session_update(
async def test_structure_update_event(
hass: HomeAssistant, entity_registry: er.EntityRegistry, subscriber, setup_platform
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
subscriber: AsyncMock,
setup_platform: PlatformSetup,
create_device: CreateDevice,
) -> None:
"""Test a pubsub message for a new device being added."""
events = async_capture_events(hass, NEST_EVENT)
@ -468,8 +483,8 @@ async def test_structure_update_event(
# Entity for first device is registered
assert entity_registry.async_get("camera.front")
new_device = Device.MakeDevice(
{
create_device.create(
raw_data={
"name": "device-id-2",
"type": "sdm.devices.types.CAMERA",
"traits": {
@ -479,16 +494,13 @@ async def test_structure_update_event(
"sdm.devices.traits.CameraLiveStream": {},
},
},
auth=None,
)
device_manager = await subscriber.async_get_device_manager()
device_manager.add_device(new_device)
# Entity for new devie has not yet been loaded
assert not entity_registry.async_get("camera.back")
# Send a message that triggers the device to be loaded
message = EventMessage.create_event(
message = create_nest_event(
{
"eventId": "some-event-id",
"timestamp": utcnow().isoformat(timespec="seconds"),
@ -498,17 +510,10 @@ async def test_structure_update_event(
"object": "enterprise/example/devices/some-device-id2",
},
},
auth=None,
)
with (
patch("homeassistant.components.nest.PLATFORMS", [PLATFORM]),
patch(
"homeassistant.components.nest.api.GoogleNestSubscriber",
return_value=subscriber,
),
):
await subscriber.async_receive_event(message)
await hass.async_block_till_done()
await subscriber.async_receive_event(message)
await hass.async_block_till_done()
# No home assistant events published
assert not events

View File

@ -9,9 +9,9 @@ relevant modes.
"""
from collections.abc import Generator
from http import HTTPStatus
import logging
from typing import Any
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from google_nest_sdm.exceptions import (
ApiException,
@ -29,11 +29,11 @@ from .common import (
PROJECT_ID,
SUBSCRIBER_ID,
TEST_CONFIG_NEW_SUBSCRIPTION,
FakeSubscriber,
PlatformSetup,
YieldFixture,
)
from tests.test_util.aiohttp import AiohttpClientMocker
PLATFORM = "sensor"
@ -61,25 +61,6 @@ def warning_caplog(
yield caplog
@pytest.fixture
def subscriber_side_effect() -> Any | None:
"""Fixture to inject failures into FakeSubscriber start."""
return None
@pytest.fixture
def failing_subscriber(
subscriber_side_effect: Any | None,
) -> YieldFixture[FakeSubscriber]:
"""Fixture overriding default subscriber behavior to allow failure injection."""
subscriber = FakeSubscriber()
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.start_async",
side_effect=subscriber_side_effect,
):
yield subscriber
async def test_setup_success(
hass: HomeAssistant, error_caplog: pytest.LogCaptureFixture, setup_platform
) -> None:
@ -125,10 +106,9 @@ async def test_setup_configuration_failure(
@pytest.mark.parametrize("subscriber_side_effect", [SubscriberException()])
async def test_setup_susbcriber_failure(
async def test_setup_subscriber_failure(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
failing_subscriber,
setup_base_platform,
) -> None:
"""Test configuration error."""
@ -145,7 +125,6 @@ async def test_setup_device_manager_failure(
) -> None:
"""Test device manager api failure."""
with (
patch("homeassistant.components.nest.api.GoogleNestSubscriber.start_async"),
patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.async_get_device_manager",
side_effect=ApiException(),
@ -165,7 +144,6 @@ async def test_subscriber_auth_failure(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
setup_base_platform,
failing_subscriber,
) -> None:
"""Test subscriber throws an authentication error."""
await setup_base_platform()
@ -184,7 +162,6 @@ async def test_subscriber_configuration_failure(
hass: HomeAssistant,
error_caplog: pytest.LogCaptureFixture,
setup_base_platform,
failing_subscriber,
) -> None:
"""Test configuration error."""
await setup_base_platform()
@ -210,14 +187,12 @@ async def test_unload_entry(hass: HomeAssistant, setup_platform) -> None:
async def test_remove_entry(
hass: HomeAssistant,
setup_base_platform,
setup_base_platform: PlatformSetup,
aioclient_mock: AiohttpClientMocker,
subscriber: AsyncMock,
) -> None:
"""Test successful unload of a ConfigEntry."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber",
return_value=FakeSubscriber(),
):
await setup_base_platform()
await setup_base_platform()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
@ -227,14 +202,18 @@ async def test_remove_entry(
assert entry.data.get("subscriber_id") == SUBSCRIBER_ID
assert entry.data.get("project_id") == PROJECT_ID
with (
patch("homeassistant.components.nest.api.GoogleNestSubscriber.subscriber_id"),
patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription",
) as delete,
):
assert await hass.config_entries.async_remove(entry.entry_id)
assert delete.called
aioclient_mock.clear_requests()
aioclient_mock.delete(
f"https://pubsub.googleapis.com/v1/{SUBSCRIBER_ID}",
json={},
)
assert not subscriber.stop.called
assert await hass.config_entries.async_remove(entry.entry_id)
assert aioclient_mock.call_count == 1
assert subscriber.stop.called
entries = hass.config_entries.async_entries(DOMAIN)
assert not entries
@ -243,7 +222,7 @@ async def test_remove_entry(
async def test_home_assistant_stop(
hass: HomeAssistant,
setup_platform: PlatformSetup,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
) -> None:
"""Test successful subscriber shutdown when HomeAssistant stops."""
await setup_platform()
@ -253,31 +232,37 @@ async def test_home_assistant_stop(
entry = entries[0]
assert entry.state is ConfigEntryState.LOADED
assert not subscriber.stop.called
await hass.async_stop()
assert subscriber.stop_calls == 1
assert subscriber.stop.called
async def test_remove_entry_delete_subscriber_failure(
hass: HomeAssistant, setup_base_platform
hass: HomeAssistant,
setup_base_platform: PlatformSetup,
aioclient_mock: AiohttpClientMocker,
subscriber: AsyncMock,
) -> None:
"""Test a failure when deleting the subscription."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber",
return_value=FakeSubscriber(),
):
await setup_base_platform()
await setup_base_platform()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.state is ConfigEntryState.LOADED
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription",
side_effect=SubscriberException(),
) as delete:
assert await hass.config_entries.async_remove(entry.entry_id)
assert delete.called
aioclient_mock.clear_requests()
aioclient_mock.delete(
f"https://pubsub.googleapis.com/v1/{SUBSCRIBER_ID}",
status=HTTPStatus.NOT_FOUND,
)
assert not subscriber.stop.called
assert await hass.config_entries.async_remove(entry.entry_id)
assert aioclient_mock.call_count == 1
assert subscriber.stop.called
entries = hass.config_entries.async_entries(DOMAIN)
assert not entries

View File

@ -13,7 +13,6 @@ from unittest.mock import patch
import aiohttp
import av
from google_nest_sdm.event import EventMessage
import numpy as np
import pytest
@ -31,7 +30,14 @@ from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from .common import DEVICE_ID, CreateDevice, FakeSubscriber
from .common import (
DEVICE_ID,
TEST_CLIP_URL,
TEST_IMAGE_URL,
CreateDevice,
create_nest_event,
)
from .conftest import FakeAuth
from tests.common import MockUser, async_capture_events
from tests.typing import ClientSessionGenerator
@ -70,7 +76,6 @@ BATTERY_CAMERA_TRAITS = {
PERSON_EVENT = "sdm.devices.events.CameraPerson.Person"
MOTION_EVENT = "sdm.devices.events.CameraMotion.Motion"
TEST_IMAGE_URL = "https://domain/sdm_event_snapshot/dGTZwR3o4Y1..."
GENERATE_IMAGE_URL_RESPONSE = {
"results": {
"url": TEST_IMAGE_URL,
@ -162,12 +167,6 @@ def mp4() -> io.BytesIO:
return output
@pytest.fixture(autouse=True)
def enable_prefetch(subscriber: FakeSubscriber) -> None:
"""Fixture to enable media fetching for tests to exercise."""
subscriber.cache_policy.fetch = True
@pytest.fixture
def cache_size() -> int:
"""Fixture for overrideing cache size."""
@ -200,7 +199,7 @@ def create_event_message(event_data, timestamp, device_id=None):
"""Create an EventMessage for a single event type."""
if device_id is None:
device_id = DEVICE_ID
return EventMessage.create_event(
return create_nest_event(
{
"eventId": f"{EVENT_ID}-{timestamp}",
"timestamp": timestamp.isoformat(timespec="seconds"),
@ -209,7 +208,6 @@ def create_event_message(event_data, timestamp, device_id=None):
"events": event_data,
},
},
auth=None,
)
@ -224,7 +222,7 @@ def create_battery_event_data(
},
"sdm.devices.events.CameraClipPreview.ClipPreview": {
"eventSessionId": event_session_id,
"previewUrl": "https://127.0.0.1/example",
"previewUrl": TEST_CLIP_URL,
},
}
@ -284,7 +282,9 @@ async def test_supported_device(
assert len(browse.children) == 0
async def test_integration_unloaded(hass: HomeAssistant, auth, setup_platform) -> None:
async def test_integration_unloaded(
hass: HomeAssistant, auth: FakeAuth, setup_platform
) -> None:
"""Test the media player loads, but has no devices, when config unloaded."""
await setup_platform()
@ -1257,6 +1257,7 @@ async def test_media_store_save_filesystem_error(
assert response.status == HTTPStatus.NOT_FOUND, f"Response not matched: {response}"
@pytest.mark.parametrize("device_traits", [BATTERY_CAMERA_TRAITS])
async def test_media_store_load_filesystem_error(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,

View File

@ -5,8 +5,8 @@ pubsub subscriber.
"""
from typing import Any
from unittest.mock import AsyncMock
from google_nest_sdm.event import EventMessage
import pytest
from homeassistant.components.sensor import (
@ -25,7 +25,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from .common import DEVICE_ID, CreateDevice, FakeSubscriber, PlatformSetup
from .common import DEVICE_ID, CreateDevice, PlatformSetup, create_nest_event
@pytest.fixture
@ -198,7 +198,7 @@ async def test_device_name_from_structure(
async def test_event_updates_sensor(
hass: HomeAssistant,
subscriber: FakeSubscriber,
subscriber: AsyncMock,
create_device: CreateDevice,
setup_platform: PlatformSetup,
) -> None:
@ -217,7 +217,7 @@ async def test_event_updates_sensor(
assert temperature.state == "25.1"
# Simulate a pubsub message received by the subscriber with a trait update
event = EventMessage.create_event(
event = create_nest_event(
{
"eventId": "some-event-id",
"timestamp": "2019-01-01T00:00:01Z",
@ -230,7 +230,6 @@ async def test_event_updates_sensor(
},
},
},
auth=None,
)
await subscriber.async_receive_event(event)
await hass.async_block_till_done() # Process dispatch/update signal