core/tests/components/withings/test_init.py

414 lines
13 KiB
Python

"""Tests for the Withings component."""
import re
import time
from asynctest import MagicMock
import requests_mock
import voluptuous as vol
from withings_api import AbstractWithingsApi
from withings_api.common import SleepModel, SleepState
import homeassistant.components.http as http
from homeassistant.components.withings import (
async_setup,
async_setup_entry,
const,
CONFIG_SCHEMA,
)
from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from .common import (
assert_state_equals,
configure_integration,
setup_hass,
WITHINGS_GET_DEVICE_RESPONSE,
WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
WITHINGS_SLEEP_RESPONSE,
WITHINGS_SLEEP_RESPONSE_EMPTY,
WITHINGS_SLEEP_SUMMARY_RESPONSE,
WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
WITHINGS_MEASURES_RESPONSE,
WITHINGS_MEASURES_RESPONSE_EMPTY,
)
def config_schema_validate(withings_config) -> None:
"""Assert a schema config succeeds."""
hass_config = {http.DOMAIN: {}, const.DOMAIN: withings_config}
return CONFIG_SCHEMA(hass_config)
def config_schema_assert_fail(withings_config) -> None:
"""Assert a schema config will fail."""
try:
config_schema_validate(withings_config)
assert False, "This line should not have run."
except vol.error.MultipleInvalid:
assert True
def test_config_schema_basic_config() -> None:
"""Test schema."""
config_schema_validate(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: ["Person 1", "Person 2"],
}
)
def test_config_schema_client_id() -> None:
"""Test schema."""
config_schema_assert_fail(
{
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: ["Person 1", "Person 2"],
}
)
config_schema_assert_fail(
{
const.CLIENT_SECRET: "my_client_secret",
const.CLIENT_ID: "",
const.PROFILES: ["Person 1"],
}
)
config_schema_validate(
{
const.CLIENT_SECRET: "my_client_secret",
const.CLIENT_ID: "my_client_id",
const.PROFILES: ["Person 1"],
}
)
def test_config_schema_client_secret() -> None:
"""Test schema."""
config_schema_assert_fail(
{const.CLIENT_ID: "my_client_id", const.PROFILES: ["Person 1"]}
)
config_schema_assert_fail(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "",
const.PROFILES: ["Person 1"],
}
)
config_schema_validate(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: ["Person 1"],
}
)
def test_config_schema_profiles() -> None:
"""Test schema."""
config_schema_assert_fail(
{const.CLIENT_ID: "my_client_id", const.CLIENT_SECRET: "my_client_secret"}
)
config_schema_assert_fail(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: "",
}
)
config_schema_assert_fail(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: [],
}
)
config_schema_assert_fail(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: ["Person 1", "Person 1"],
}
)
config_schema_validate(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: ["Person 1"],
}
)
config_schema_validate(
{
const.CLIENT_ID: "my_client_id",
const.CLIENT_SECRET: "my_client_secret",
const.PROFILES: ["Person 1", "Person 2"],
}
)
async def test_async_setup_no_config(hass: HomeAssistant) -> None:
"""Test method."""
hass.async_create_task = MagicMock()
await async_setup(hass, {})
hass.async_create_task.assert_not_called()
async def test_upgrade_token(
hass: HomeAssistant, aiohttp_client, aioclient_mock
) -> None:
"""Test upgrading from old config data format to new one."""
config = await setup_hass(hass)
profiles = config[const.DOMAIN][const.PROFILES]
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=0,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
getmeasures_response=WITHINGS_MEASURES_RESPONSE_EMPTY,
get_sleep_response=WITHINGS_SLEEP_RESPONSE_EMPTY,
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
)
entries = hass.config_entries.async_entries(const.DOMAIN)
assert entries
entry = entries[0]
data = entry.data
token = data.get("token")
hass.config_entries.async_update_entry(
entry,
data={
const.PROFILE: data.get(const.PROFILE),
const.CREDENTIALS: {
"access_token": token.get("access_token"),
"refresh_token": token.get("refresh_token"),
"token_expiry": token.get("expires_at"),
"token_type": token.get("type"),
"userid": token.get("userid"),
"client_id": token.get("my_client_id"),
"consumer_secret": token.get("my_consumer_secret"),
},
},
)
with requests_mock.mock() as rqmck:
rqmck.get(
re.compile(AbstractWithingsApi.URL + "/v2/user?.*action=getdevice(&.*|$)"),
status_code=200,
json=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
)
assert await async_setup_entry(hass, entry)
entries = hass.config_entries.async_entries(const.DOMAIN)
assert entries
data = entries[0].data
assert data.get("auth_implementation") == const.DOMAIN
assert data.get("implementation") == const.DOMAIN
assert data.get(const.PROFILE) == profiles[0]
token = data.get("token")
assert token
assert token.get("access_token") == "mock-access-token"
assert token.get("refresh_token") == "mock-refresh-token"
assert token.get("expires_at") > time.time()
assert token.get("type") == "Bearer"
assert token.get("userid") == "myuserid"
assert not token.get("client_id")
assert not token.get("consumer_secret")
async def test_auth_failure(
hass: HomeAssistant, aiohttp_client, aioclient_mock
) -> None:
"""Test auth failure."""
config = await setup_hass(hass)
profiles = config[const.DOMAIN][const.PROFILES]
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=0,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
getmeasures_response=WITHINGS_MEASURES_RESPONSE_EMPTY,
get_sleep_response=WITHINGS_SLEEP_RESPONSE_EMPTY,
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
)
entries = hass.config_entries.async_entries(const.DOMAIN)
assert entries
entry = entries[0]
hass.config_entries.async_update_entry(
entry, data={**entry.data, **{"new_item": 1}}
)
with requests_mock.mock() as rqmck:
rqmck.get(
re.compile(AbstractWithingsApi.URL + "/v2/user?.*action=getdevice(&.*|$)"),
status_code=200,
json={"status": 401, "body": {}},
)
assert not (await async_setup_entry(hass, entry))
async def test_full_setup(hass: HomeAssistant, aiohttp_client, aioclient_mock) -> None:
"""Test the whole component lifecycle."""
config = await setup_hass(hass)
profiles = config[const.DOMAIN][const.PROFILES]
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=0,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE,
getmeasures_response=WITHINGS_MEASURES_RESPONSE,
get_sleep_response=WITHINGS_SLEEP_RESPONSE,
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE,
)
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=1,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
getmeasures_response=WITHINGS_MEASURES_RESPONSE_EMPTY,
get_sleep_response=WITHINGS_SLEEP_RESPONSE_EMPTY,
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
)
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=2,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
getmeasures_response=WITHINGS_MEASURES_RESPONSE_EMPTY,
get_sleep_response={
"status": 0,
"body": {
"model": SleepModel.TRACKER.real,
"series": [
{
"startdate": "2019-02-01 00:00:00",
"enddate": "2019-02-01 01:00:00",
"state": SleepState.AWAKE.real,
}
],
},
},
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
)
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=3,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
getmeasures_response=WITHINGS_MEASURES_RESPONSE_EMPTY,
get_sleep_response={
"status": 0,
"body": {
"model": SleepModel.TRACKER.real,
"series": [
{
"startdate": "2019-02-01 00:00:00",
"enddate": "2019-02-01 01:00:00",
"state": SleepState.LIGHT.real,
}
],
},
},
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
)
await configure_integration(
hass=hass,
aiohttp_client=aiohttp_client,
aioclient_mock=aioclient_mock,
profiles=profiles,
profile_index=4,
get_device_response=WITHINGS_GET_DEVICE_RESPONSE_EMPTY,
getmeasures_response=WITHINGS_MEASURES_RESPONSE_EMPTY,
get_sleep_response={
"status": 0,
"body": {
"model": SleepModel.TRACKER.real,
"series": [
{
"startdate": "2019-02-01 00:00:00",
"enddate": "2019-02-01 01:00:00",
"state": SleepState.REM.real,
}
],
},
},
get_sleep_summary_response=WITHINGS_SLEEP_SUMMARY_RESPONSE_EMPTY,
)
# Test the states of the entities.
expected_states = (
(profiles[0], const.MEAS_WEIGHT_KG, 70.0),
(profiles[0], const.MEAS_FAT_MASS_KG, 5.0),
(profiles[0], const.MEAS_FAT_FREE_MASS_KG, 60.0),
(profiles[0], const.MEAS_MUSCLE_MASS_KG, 50.0),
(profiles[0], const.MEAS_BONE_MASS_KG, 10.0),
(profiles[0], const.MEAS_HEIGHT_M, 2.0),
(profiles[0], const.MEAS_FAT_RATIO_PCT, 0.07),
(profiles[0], const.MEAS_DIASTOLIC_MMHG, 70.0),
(profiles[0], const.MEAS_SYSTOLIC_MMGH, 100.0),
(profiles[0], const.MEAS_HEART_PULSE_BPM, 60.0),
(profiles[0], const.MEAS_SPO2_PCT, 0.95),
(profiles[0], const.MEAS_HYDRATION, 0.95),
(profiles[0], const.MEAS_PWV, 100.0),
(profiles[0], const.MEAS_SLEEP_WAKEUP_DURATION_SECONDS, 320),
(profiles[0], const.MEAS_SLEEP_LIGHT_DURATION_SECONDS, 520),
(profiles[0], const.MEAS_SLEEP_DEEP_DURATION_SECONDS, 720),
(profiles[0], const.MEAS_SLEEP_REM_DURATION_SECONDS, 920),
(profiles[0], const.MEAS_SLEEP_WAKEUP_COUNT, 1120),
(profiles[0], const.MEAS_SLEEP_TOSLEEP_DURATION_SECONDS, 1320),
(profiles[0], const.MEAS_SLEEP_TOWAKEUP_DURATION_SECONDS, 1520),
(profiles[0], const.MEAS_SLEEP_HEART_RATE_AVERAGE, 1720),
(profiles[0], const.MEAS_SLEEP_HEART_RATE_MIN, 1920),
(profiles[0], const.MEAS_SLEEP_HEART_RATE_MAX, 2120),
(profiles[0], const.MEAS_SLEEP_RESPIRATORY_RATE_AVERAGE, 2320),
(profiles[0], const.MEAS_SLEEP_RESPIRATORY_RATE_MIN, 2520),
(profiles[0], const.MEAS_SLEEP_RESPIRATORY_RATE_MAX, 2720),
(profiles[0], const.MEAS_SLEEP_STATE, const.STATE_DEEP),
(profiles[1], const.MEAS_SLEEP_STATE, STATE_UNKNOWN),
(profiles[1], const.MEAS_HYDRATION, STATE_UNKNOWN),
(profiles[2], const.MEAS_SLEEP_STATE, const.STATE_AWAKE),
(profiles[3], const.MEAS_SLEEP_STATE, const.STATE_LIGHT),
(profiles[3], const.MEAS_FAT_FREE_MASS_KG, STATE_UNKNOWN),
(profiles[4], const.MEAS_SLEEP_STATE, const.STATE_REM),
)
for (profile, meas, value) in expected_states:
assert_state_equals(hass, profile, meas, value)
# Tear down setup entries.
entries = hass.config_entries.async_entries(const.DOMAIN)
assert entries
for entry in entries:
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()