Add binary sensor tests to Skybell (#79568)

* Add tests to Skybell

* better way to manage cache

* uno mas

* try ci fix

* temporary

* undo temporary

* ruff

* black

* uno mas

* uno mas

* remove problematic test for now

* reduce to binary sensor tests

* coverage

* move cache to json

* Update tests/components/skybell/conftest.py

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Robert Hillis 2023-11-22 12:56:50 -05:00 committed by GitHub
parent 9278db7344
commit 200804237f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 322 additions and 27 deletions

View File

@ -1124,10 +1124,7 @@ omit =
homeassistant/components/sky_hub/* homeassistant/components/sky_hub/*
homeassistant/components/skybeacon/sensor.py homeassistant/components/skybeacon/sensor.py
homeassistant/components/skybell/__init__.py homeassistant/components/skybell/__init__.py
homeassistant/components/skybell/binary_sensor.py
homeassistant/components/skybell/camera.py homeassistant/components/skybell/camera.py
homeassistant/components/skybell/coordinator.py
homeassistant/components/skybell/entity.py
homeassistant/components/skybell/light.py homeassistant/components/skybell/light.py
homeassistant/components/skybell/sensor.py homeassistant/components/skybell/sensor.py
homeassistant/components/skybell/switch.py homeassistant/components/skybell/switch.py

View File

@ -1,12 +1 @@
"""Tests for the SkyBell integration.""" """Tests for the SkyBell integration."""
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
USERNAME = "user"
PASSWORD = "password"
USER_ID = "123456789012345678901234"
CONF_CONFIG_FLOW = {
CONF_EMAIL: USERNAME,
CONF_PASSWORD: PASSWORD,
}

View File

@ -1,11 +1,28 @@
"""Test setup for the SkyBell integration.""" """Configure pytest for Skybell tests."""
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from aioskybell import Skybell, SkybellDevice from aioskybell import Skybell, SkybellDevice
from aioskybell.helpers.const import BASE_URL, USERS_ME_URL
import orjson
import pytest import pytest
from . import USER_ID from homeassistant.components.skybell.const import DOMAIN
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
USERNAME = "user"
PASSWORD = "password"
USER_ID = "1234567890abcdef12345678"
DEVICE_ID = "012345670123456789abcdef"
CONF_DATA = {
CONF_EMAIL: USERNAME,
CONF_PASSWORD: PASSWORD,
}
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -23,3 +40,88 @@ def skybell_mock():
return_value=mocked_skybell, return_value=mocked_skybell,
), patch("homeassistant.components.skybell.Skybell", return_value=mocked_skybell): ), patch("homeassistant.components.skybell.Skybell", return_value=mocked_skybell):
yield mocked_skybell yield mocked_skybell
def create_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Create fixture for adding config entry in Home Assistant."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=CONF_DATA)
entry.add_to_hass(hass)
return entry
async def set_aioclient_responses(aioclient_mock: AiohttpClientMocker) -> None:
"""Set AioClient responses."""
aioclient_mock.get(
f"{BASE_URL}devices/{DEVICE_ID}/info/",
text=load_fixture("skybell/device_info.json"),
)
aioclient_mock.get(
f"{BASE_URL}devices/{DEVICE_ID}/settings/",
text=load_fixture("skybell/device_settings.json"),
)
aioclient_mock.get(
f"{BASE_URL}devices/{DEVICE_ID}/activities/",
text=load_fixture("skybell/activities.json"),
)
aioclient_mock.get(
f"{BASE_URL}devices/",
text=load_fixture("skybell/device.json"),
)
aioclient_mock.get(
USERS_ME_URL,
text=load_fixture("skybell/me.json"),
)
aioclient_mock.post(
f"{BASE_URL}login/",
text=load_fixture("skybell/login.json"),
)
aioclient_mock.get(
f"{BASE_URL}devices/{DEVICE_ID}/activities/1234567890ab1234567890ac/video/",
text=load_fixture("skybell/video.json"),
)
aioclient_mock.get(
f"{BASE_URL}devices/{DEVICE_ID}/avatar/",
text=load_fixture("skybell/avatar.json"),
)
aioclient_mock.get(
f"https://v3-production-devices-avatar.s3.us-west-2.amazonaws.com/{DEVICE_ID}.jpg",
)
aioclient_mock.get(
f"https://skybell-thumbnails-stage.s3.amazonaws.com/{DEVICE_ID}/1646859244793-951{DEVICE_ID}_{DEVICE_ID}.jpeg",
)
@pytest.fixture
async def connection(aioclient_mock: AiohttpClientMocker) -> None:
"""Fixture for good connection responses."""
await set_aioclient_responses(aioclient_mock)
def create_skybell(hass: HomeAssistant) -> Skybell:
"""Create Skybell object."""
skybell = Skybell(
username=USERNAME,
password=PASSWORD,
get_devices=True,
session=async_get_clientsession(hass),
)
skybell._cache = orjson.loads(load_fixture("skybell/cache.json"))
return skybell
def mock_skybell(hass: HomeAssistant):
"""Mock Skybell object."""
return patch(
"homeassistant.components.skybell.Skybell", return_value=create_skybell(hass)
)
async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry:
"""Set up the Skybell integration in Home Assistant."""
config_entry = create_entry(hass)
with mock_skybell(hass), patch("aioskybell.utils.async_save_cache"):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry

View File

@ -0,0 +1,30 @@
[
{
"videoState": "download:ready",
"_id": "1234567890ab1234567890ab",
"device": "0123456789abcdef01234567",
"callId": "1234567890123-1234567890abcd1234567890abcd",
"event": "device:sensor:motion",
"state": "ready",
"ttlStartDate": "2020-03-30T12:35:02.204Z",
"createdAt": "2020-03-30T12:35:02.204Z",
"updatedAt": "2020-03-30T12:35:02.566Z",
"id": "1234567890ab1234567890ab",
"media": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcdef.jpeg",
"mediaSmall": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcdef_small.jpeg"
},
{
"videoState": "download:ready",
"_id": "1234567890ab1234567890a9",
"device": "0123456789abcdef01234567",
"callId": "1234567890123-1234567890abcd1234567890abc9",
"event": "application:on-demand",
"state": "ready",
"ttlStartDate": "2020-03-30T11:35:02.204Z",
"createdAt": "2020-03-30T11:35:02.204Z",
"updatedAt": "2020-03-30T11:35:02.566Z",
"id": "1234567890ab1234567890a9",
"media": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcde9.jpeg",
"mediaSmall": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcde9_small.jpeg"
}
]

View File

@ -0,0 +1,4 @@
{
"createdAt": "2020-03-31T04:13:48.640Z",
"url": "https://v3-production-devices-avatar.s3.us-west-2.amazonaws.com/012345670123456789abcdef.jpg"
}

View File

@ -0,0 +1,40 @@
{
"app_id": "secret",
"client_id": "secret",
"token": "secret",
"access_token": "secret",
"devices": {
"5f8ef594362f31000833d959": {
"event": {
"device:sensor:motion": {
"videoState": "download:ready",
"_id": "1234567890ab1234567890ab",
"device": "0123456789abcdef01234567",
"callId": "1234567890123-1234567890abcd1234567890abcd",
"event": "device:sensor:motion",
"state": "ready",
"ttlStartDate": "2020-03-30T12:35:02.204Z",
"createdAt": "2020-03-30T12:35:02.204Z",
"updatedAt": "2020-03-30T12:35:02.566Z",
"id": "1234567890ab1234567890ab",
"media": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcdef.jpeg",
"mediaSmall": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcdef_small.jpeg"
},
"device:sensor:button": {
"videoState": "download:ready",
"_id": "1234567890ab1234567890a9",
"device": "0123456789abcdef01234567",
"callId": "1234567890123-1234567890abcd1234567890abc9",
"event": "application:on-demand",
"state": "ready",
"ttlStartDate": "2020-03-30T11:35:02.204Z",
"createdAt": "2020-03-30T11:35:02.204Z",
"updatedAt": "2020-03-30T11:35:02.566Z",
"id": "1234567890ab1234567890a9",
"media": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcde9.jpeg",
"mediaSmall": "https://skybell-thumbnails-stage.s3.amazonaws.com/012345670123456789abcdef/1646859244793-951012345670123456789abcdef_012345670123456789abcde9_small.jpeg"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
[
{
"user": "0123456789abcdef01234567",
"uuid": "0123456789",
"resourceId": "012345670123456789abcdef",
"deviceInviteToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"location": {
"lat": "-1.0",
"lng": "1.0"
},
"name": "Front Door",
"type": "skybell hd",
"status": "up",
"createdAt": "2020-10-20T14:35:00.745Z",
"updatedAt": "2020-10-20T14:35:00.745Z",
"id": "012345670123456789abcdef",
"acl": "owner"
}
]

View File

@ -0,0 +1,25 @@
{
"essid": "wifi",
"wifiBitrate": "39",
"proxy_port": "5683",
"wifiLinkQuality": "43",
"port": "5683",
"mac": "ff:ff:ff:ff:ff:ff",
"serialNo": "0123456789",
"wifiTxPwrEeprom": "12",
"region": "us-west-2",
"hardwareRevision": "SKYBELL_TRIMPLUS_1000030-F",
"proxy_address": "34.209.204.201",
"wifiSignalLevel": "-67",
"localHostname": "ip-10-0-0-67.us-west-2.compute.internal",
"wifiNoise": "0",
"address": "1.2.3.4",
"clientId": "1234567890abcdef1234567890abcdef1234567890abcdef",
"timestamp": "60000000000",
"deviceId": "01234567890abcdef1234567",
"firmwareVersion": "7082",
"checkedInAt": "2020-03-31T04:13:37.000Z",
"status": {
"wifiLink": "poor"
}
}

View File

@ -0,0 +1,22 @@
{
"ring_tone": "0",
"do_not_ring": "false",
"do_not_disturb": "false",
"digital_doorbell": "false",
"video_profile": "1",
"mic_volume": "63",
"speaker_volume": "96",
"chime_level": "1",
"motion_threshold": "32",
"low_lux_threshold": "50",
"med_lux_threshold": "150",
"high_lux_threshold": "400",
"low_front_led_dac": "10",
"med_front_led_dac": "10",
"high_front_led_dac": "10",
"green_r": "0",
"green_g": "0",
"green_b": "255",
"led_intensity": "0",
"motion_policy": "call"
}

View File

@ -0,0 +1,22 @@
{
"ring_tone": "0",
"do_not_ring": "false",
"do_not_disturb": "false",
"digital_doorbell": "false",
"video_profile": "1",
"mic_volume": "63",
"speaker_volume": "96",
"chime_level": "1",
"motion_threshold": "32",
"low_lux_threshold": "50",
"med_lux_threshold": "150",
"high_lux_threshold": "400",
"low_front_led_dac": "10",
"med_front_led_dac": "10",
"high_front_led_dac": "10",
"green_r": "10",
"green_g": "125",
"green_b": "255",
"led_intensity": "50",
"motion_policy": "disabled"
}

View File

@ -0,0 +1,10 @@
{
"firstName": "John",
"lastName": "Doe",
"resourceId": "0123456789abcdef01234567",
"createdAt": "2018-07-06T02:02:14.050Z",
"updatedAt": "2018-07-06T02:02:14.050Z",
"id": "0123456789abcdef01234567",
"userLinks": [],
"access_token": "superlongkey"
}

View File

@ -0,0 +1,5 @@
{
"errors": {
"message": "Invalid Login - SmartAuth"
}
}

View File

@ -0,0 +1,9 @@
{
"firstName": "First",
"lastName": "Last",
"resourceId": "123456789012345678901234",
"createdAt": "2018-10-06T02:02:14.050Z",
"updatedAt": "2018-10-06T02:02:14.050Z",
"id": "1234567890abcdef12345678",
"userLinks": []
}

View File

@ -0,0 +1,3 @@
{
"url": "https://production-video-download.s3.us-west-2.amazonaws.com/012345670123456789abcdef/1654307756676-0123456789120123456789abcdef_012345670123456789abcdef.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=01234567890123456789%2F20203030%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200330T201225Z&X-Amz-Expires=300&X-Amz-Signature=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef&X-Amz-SignedHeaders=host"
}

View File

@ -0,0 +1,18 @@
"""Binary sensor tests for the Skybell integration."""
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.const import ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from .conftest import async_init_integration
async def test_binary_sensors(hass: HomeAssistant, connection) -> None:
"""Test we get sensor data."""
await async_init_integration(hass)
state = hass.states.get("binary_sensor.front_door_button")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY
state = hass.states.get("binary_sensor.front_door_motion")
assert state.state == STATE_ON
assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.MOTION

View File

@ -11,7 +11,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_SOURCE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from . import CONF_CONFIG_FLOW, PASSWORD, USER_ID from .conftest import CONF_DATA, PASSWORD, USER_ID
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -37,12 +37,12 @@ async def test_flow_user(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input=CONF_CONFIG_FLOW, user_input=CONF_DATA,
) )
assert result["type"] == FlowResultType.CREATE_ENTRY assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "user" assert result["title"] == "user"
assert result["data"] == CONF_CONFIG_FLOW assert result["data"] == CONF_DATA
assert result["result"].unique_id == USER_ID assert result["result"].unique_id == USER_ID
@ -50,12 +50,12 @@ async def test_flow_user_already_configured(hass: HomeAssistant) -> None:
"""Test user initialized flow with duplicate server.""" """Test user initialized flow with duplicate server."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data=CONF_CONFIG_FLOW, data=CONF_DATA,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW DOMAIN, context={"source": SOURCE_USER}, data=CONF_DATA
) )
assert result["type"] == FlowResultType.ABORT assert result["type"] == FlowResultType.ABORT
@ -66,7 +66,7 @@ async def test_flow_user_cannot_connect(hass: HomeAssistant, skybell_mock) -> No
"""Test user initialized flow with unreachable server.""" """Test user initialized flow with unreachable server."""
skybell_mock.async_initialize.side_effect = exceptions.SkybellException(hass) skybell_mock.async_initialize.side_effect = exceptions.SkybellException(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW DOMAIN, context={"source": SOURCE_USER}, data=CONF_DATA
) )
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
@ -79,7 +79,7 @@ async def test_invalid_credentials(hass: HomeAssistant, skybell_mock) -> None:
exceptions.SkybellAuthenticationException(hass) exceptions.SkybellAuthenticationException(hass)
) )
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW DOMAIN, context={"source": SOURCE_USER}, data=CONF_DATA
) )
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
@ -91,7 +91,7 @@ async def test_flow_user_unknown_error(hass: HomeAssistant, skybell_mock) -> Non
"""Test user initialized flow with unreachable server.""" """Test user initialized flow with unreachable server."""
skybell_mock.async_initialize.side_effect = Exception skybell_mock.async_initialize.side_effect = Exception
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW DOMAIN, context={"source": SOURCE_USER}, data=CONF_DATA
) )
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
@ -100,7 +100,7 @@ async def test_flow_user_unknown_error(hass: HomeAssistant, skybell_mock) -> Non
async def test_step_reauth(hass: HomeAssistant) -> None: async def test_step_reauth(hass: HomeAssistant) -> None:
"""Test the reauth flow.""" """Test the reauth flow."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=CONF_CONFIG_FLOW) entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=CONF_DATA)
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -126,7 +126,7 @@ async def test_step_reauth(hass: HomeAssistant) -> None:
async def test_step_reauth_failed(hass: HomeAssistant, skybell_mock) -> None: async def test_step_reauth_failed(hass: HomeAssistant, skybell_mock) -> None:
"""Test the reauth flow fails and recovers.""" """Test the reauth flow fails and recovers."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=CONF_CONFIG_FLOW) entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=CONF_DATA)
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(