mirror of
https://github.com/home-assistant/core.git
synced 2025-12-25 05:26:47 +00:00
Use latest withings_api module (#27817)
* Using latest winthings_api module. Drastically reduced complexity of tests. * Removing import source. * Fixing test requirements. * Using requests_mock instead of responses module. * Updating file formatting. * Removing unused method. * Adding support for new OAuth2 config flow. * Addressing PR feedback. Removing unecessary base_url from config, this is a potential breaking change. * Addressing PR feedback.
This commit is contained in:
committed by
Paulus Schoutsen
parent
fc09702cc3
commit
15bedd8f27
@@ -1,29 +1,46 @@
|
||||
"""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.api as api
|
||||
import homeassistant.components.http as http
|
||||
from homeassistant.components.withings import async_setup, const, CONFIG_SCHEMA
|
||||
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 .conftest import WithingsFactory, WithingsFactoryConfig
|
||||
|
||||
BASE_HASS_CONFIG = {
|
||||
http.DOMAIN: {},
|
||||
api.DOMAIN: {"base_url": "http://localhost/"},
|
||||
const.DOMAIN: None,
|
||||
}
|
||||
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):
|
||||
def config_schema_validate(withings_config) -> None:
|
||||
"""Assert a schema config succeeds."""
|
||||
hass_config = BASE_HASS_CONFIG.copy()
|
||||
hass_config[const.DOMAIN] = withings_config
|
||||
hass_config = {http.DOMAIN: {}, const.DOMAIN: withings_config}
|
||||
|
||||
return CONFIG_SCHEMA(hass_config)
|
||||
|
||||
|
||||
def config_schema_assert_fail(withings_config):
|
||||
def config_schema_assert_fail(withings_config) -> None:
|
||||
"""Assert a schema config will fail."""
|
||||
try:
|
||||
config_schema_validate(withings_config)
|
||||
@@ -32,7 +49,7 @@ def config_schema_assert_fail(withings_config):
|
||||
assert True
|
||||
|
||||
|
||||
def test_config_schema_basic_config():
|
||||
def test_config_schema_basic_config() -> None:
|
||||
"""Test schema."""
|
||||
config_schema_validate(
|
||||
{
|
||||
@@ -43,7 +60,7 @@ def test_config_schema_basic_config():
|
||||
)
|
||||
|
||||
|
||||
def test_config_schema_client_id():
|
||||
def test_config_schema_client_id() -> None:
|
||||
"""Test schema."""
|
||||
config_schema_assert_fail(
|
||||
{
|
||||
@@ -67,7 +84,7 @@ def test_config_schema_client_id():
|
||||
)
|
||||
|
||||
|
||||
def test_config_schema_client_secret():
|
||||
def test_config_schema_client_secret() -> None:
|
||||
"""Test schema."""
|
||||
config_schema_assert_fail(
|
||||
{const.CLIENT_ID: "my_client_id", const.PROFILES: ["Person 1"]}
|
||||
@@ -88,7 +105,7 @@ def test_config_schema_client_secret():
|
||||
)
|
||||
|
||||
|
||||
def test_config_schema_profiles():
|
||||
def test_config_schema_profiles() -> None:
|
||||
"""Test schema."""
|
||||
config_schema_assert_fail(
|
||||
{const.CLIENT_ID: "my_client_id", const.CLIENT_SECRET: "my_client_secret"}
|
||||
@@ -130,50 +147,7 @@ def test_config_schema_profiles():
|
||||
)
|
||||
|
||||
|
||||
def test_config_schema_base_url():
|
||||
"""Test schema."""
|
||||
config_schema_validate(
|
||||
{
|
||||
const.CLIENT_ID: "my_client_id",
|
||||
const.CLIENT_SECRET: "my_client_secret",
|
||||
const.PROFILES: ["Person 1"],
|
||||
}
|
||||
)
|
||||
config_schema_assert_fail(
|
||||
{
|
||||
const.CLIENT_ID: "my_client_id",
|
||||
const.CLIENT_SECRET: "my_client_secret",
|
||||
const.BASE_URL: 123,
|
||||
const.PROFILES: ["Person 1"],
|
||||
}
|
||||
)
|
||||
config_schema_assert_fail(
|
||||
{
|
||||
const.CLIENT_ID: "my_client_id",
|
||||
const.CLIENT_SECRET: "my_client_secret",
|
||||
const.BASE_URL: "",
|
||||
const.PROFILES: ["Person 1"],
|
||||
}
|
||||
)
|
||||
config_schema_assert_fail(
|
||||
{
|
||||
const.CLIENT_ID: "my_client_id",
|
||||
const.CLIENT_SECRET: "my_client_secret",
|
||||
const.BASE_URL: "blah blah",
|
||||
const.PROFILES: ["Person 1"],
|
||||
}
|
||||
)
|
||||
config_schema_validate(
|
||||
{
|
||||
const.CLIENT_ID: "my_client_id",
|
||||
const.CLIENT_SECRET: "my_client_secret",
|
||||
const.BASE_URL: "https://www.blah.blah.blah/blah/blah",
|
||||
const.PROFILES: ["Person 1"],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def test_async_setup_no_config(hass):
|
||||
async def test_async_setup_no_config(hass: HomeAssistant) -> None:
|
||||
"""Test method."""
|
||||
hass.async_create_task = MagicMock()
|
||||
|
||||
@@ -182,15 +156,258 @@ async def test_async_setup_no_config(hass):
|
||||
hass.async_create_task.assert_not_called()
|
||||
|
||||
|
||||
async def test_async_setup_teardown(withings_factory: WithingsFactory, hass):
|
||||
"""Test method."""
|
||||
data = await withings_factory(WithingsFactoryConfig(measures=[const.MEAS_TEMP_C]))
|
||||
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]
|
||||
|
||||
profile = WithingsFactoryConfig.PROFILE_1
|
||||
await data.configure_all(profile, "authorization_code")
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user