Add tests for fitbit integration (#100765)
* Add tests for fitbit integration * Update coveragerc * Update test requirementspull/100772/head
parent
71aef4e95a
commit
781bc5b3bc
|
@ -389,7 +389,6 @@ omit =
|
|||
homeassistant/components/firmata/pin.py
|
||||
homeassistant/components/firmata/sensor.py
|
||||
homeassistant/components/firmata/switch.py
|
||||
homeassistant/components/fitbit/*
|
||||
homeassistant/components/fivem/__init__.py
|
||||
homeassistant/components/fivem/binary_sensor.py
|
||||
homeassistant/components/fivem/coordinator.py
|
||||
|
|
|
@ -12,6 +12,8 @@ from homeassistant.const import (
|
|||
UnitOfVolume,
|
||||
)
|
||||
|
||||
DOMAIN: Final = "fitbit"
|
||||
|
||||
ATTR_ACCESS_TOKEN: Final = "access_token"
|
||||
ATTR_REFRESH_TOKEN: Final = "refresh_token"
|
||||
ATTR_LAST_SAVED_AT: Final = "last_saved_at"
|
||||
|
|
|
@ -629,6 +629,9 @@ feedparser==6.0.10
|
|||
# homeassistant.components.file
|
||||
file-read-backwards==2.0.0
|
||||
|
||||
# homeassistant.components.fitbit
|
||||
fitbit==0.3.1
|
||||
|
||||
# homeassistant.components.fivem
|
||||
fivem-api==0.1.2
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for fitbit component."""
|
|
@ -0,0 +1,167 @@
|
|||
"""Test fixtures for fitbit."""
|
||||
|
||||
from collections.abc import Awaitable, Callable, Generator
|
||||
import datetime
|
||||
from http import HTTPStatus
|
||||
import time
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from requests_mock.mocker import Mocker
|
||||
|
||||
from homeassistant.components.fitbit.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
CLIENT_ID = "1234"
|
||||
CLIENT_SECRET = "5678"
|
||||
PROFILE_USER_ID = "fitbit-api-user-id-1"
|
||||
FAKE_TOKEN = "some-token"
|
||||
FAKE_REFRESH_TOKEN = "some-refresh-token"
|
||||
|
||||
PROFILE_API_URL = "https://api.fitbit.com/1/user/-/profile.json"
|
||||
DEVICES_API_URL = "https://api.fitbit.com/1/user/-/devices.json"
|
||||
TIMESERIES_API_URL_FORMAT = (
|
||||
"https://api.fitbit.com/1/user/-/{resource}/date/today/7d.json"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="token_expiration_time")
|
||||
def mcok_token_expiration_time() -> float:
|
||||
"""Fixture for expiration time of the config entry auth token."""
|
||||
return time.time() + 86400
|
||||
|
||||
|
||||
@pytest.fixture(name="fitbit_config_yaml")
|
||||
def mock_fitbit_config_yaml(token_expiration_time: float) -> dict[str, Any]:
|
||||
"""Fixture for the yaml fitbit.conf file contents."""
|
||||
return {
|
||||
"access_token": FAKE_TOKEN,
|
||||
"refresh_token": FAKE_REFRESH_TOKEN,
|
||||
"last_saved_at": token_expiration_time,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="fitbit_config_setup", autouse=True)
|
||||
def mock_fitbit_config_setup(
|
||||
fitbit_config_yaml: dict[str, Any],
|
||||
) -> Generator[None, None, None]:
|
||||
"""Fixture to mock out fitbit.conf file data loading and persistence."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fitbit.sensor.os.path.isfile", return_value=True
|
||||
), patch(
|
||||
"homeassistant.components.fitbit.sensor.load_json_object",
|
||||
return_value=fitbit_config_yaml,
|
||||
), patch(
|
||||
"homeassistant.components.fitbit.sensor.save_json",
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="monitored_resources")
|
||||
def mock_monitored_resources() -> list[str] | None:
|
||||
"""Fixture for the fitbit yaml config monitored_resources field."""
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture(name="sensor_platform_config")
|
||||
def mock_sensor_platform_config(
|
||||
monitored_resources: list[str] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Fixture for the fitbit sensor platform configuration data in configuration.yaml."""
|
||||
config = {}
|
||||
if monitored_resources is not None:
|
||||
config["monitored_resources"] = monitored_resources
|
||||
return config
|
||||
|
||||
|
||||
@pytest.fixture(name="sensor_platform_setup")
|
||||
async def mock_sensor_platform_setup(
|
||||
hass: HomeAssistant,
|
||||
sensor_platform_config: dict[str, Any],
|
||||
) -> Callable[[], Awaitable[bool]]:
|
||||
"""Fixture to set up the integration."""
|
||||
|
||||
async def run() -> bool:
|
||||
result = await async_setup_component(
|
||||
hass,
|
||||
"sensor",
|
||||
{
|
||||
"sensor": [
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
**sensor_platform_config,
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
return result
|
||||
|
||||
return run
|
||||
|
||||
|
||||
@pytest.fixture(name="profile_id")
|
||||
async def mock_profile_id() -> str:
|
||||
"""Fixture for the profile id returned from the API response."""
|
||||
return PROFILE_USER_ID
|
||||
|
||||
|
||||
@pytest.fixture(name="profile", autouse=True)
|
||||
async def mock_profile(requests_mock: Mocker, profile_id: str) -> None:
|
||||
"""Fixture to setup fake requests made to Fitbit API during config flow."""
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
PROFILE_API_URL,
|
||||
status_code=HTTPStatus.OK,
|
||||
json={
|
||||
"user": {
|
||||
"encodedId": profile_id,
|
||||
"fullName": "My name",
|
||||
"locale": "en_US",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="devices_response")
|
||||
async def mock_device_response() -> list[dict[str, Any]]:
|
||||
"""Return the list of devices."""
|
||||
return []
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def mock_devices(requests_mock: Mocker, devices_response: dict[str, Any]) -> None:
|
||||
"""Fixture to setup fake device responses."""
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
DEVICES_API_URL,
|
||||
status_code=HTTPStatus.OK,
|
||||
json=devices_response,
|
||||
)
|
||||
|
||||
|
||||
def timeseries_response(resource: str, value: str) -> dict[str, Any]:
|
||||
"""Create a timeseries response value."""
|
||||
return {
|
||||
resource: [{"dateTime": datetime.datetime.today().isoformat(), "value": value}]
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="register_timeseries")
|
||||
async def mock_register_timeseries(
|
||||
requests_mock: Mocker,
|
||||
) -> Callable[[str, dict[str, Any]], None]:
|
||||
"""Fixture to setup fake timeseries API responses."""
|
||||
|
||||
def register(resource: str, response: dict[str, Any]) -> None:
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
TIMESERIES_API_URL_FORMAT.format(resource=resource),
|
||||
status_code=HTTPStatus.OK,
|
||||
json=response,
|
||||
)
|
||||
|
||||
return register
|
|
@ -0,0 +1,92 @@
|
|||
"""Tests for the fitbit sensor platform."""
|
||||
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import timeseries_response
|
||||
|
||||
DEVICE_RESPONSE_CHARGE_2 = {
|
||||
"battery": "Medium",
|
||||
"batteryLevel": 60,
|
||||
"deviceVersion": "Charge 2",
|
||||
"id": "816713257",
|
||||
"lastSyncTime": "2019-11-07T12:00:58.000",
|
||||
"mac": "16ADD56D54GD",
|
||||
"type": "TRACKER",
|
||||
}
|
||||
DEVICE_RESPONSE_ARIA_AIR = {
|
||||
"battery": "High",
|
||||
"batteryLevel": 95,
|
||||
"deviceVersion": "Aria Air",
|
||||
"id": "016713257",
|
||||
"lastSyncTime": "2019-11-07T12:00:58.000",
|
||||
"mac": "06ADD56D54GD",
|
||||
"type": "SCALE",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"monitored_resources",
|
||||
[["activities/steps"]],
|
||||
)
|
||||
async def test_step_sensor(
|
||||
hass: HomeAssistant,
|
||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
||||
register_timeseries: Callable[[str, dict[str, Any]], None],
|
||||
) -> None:
|
||||
"""Test battery level sensor."""
|
||||
|
||||
register_timeseries(
|
||||
"activities/steps", timeseries_response("activities-steps", "5600")
|
||||
)
|
||||
await sensor_platform_setup()
|
||||
|
||||
state = hass.states.get("sensor.steps")
|
||||
assert state
|
||||
assert state.state == "5600"
|
||||
assert state.attributes == {
|
||||
"attribution": "Data provided by Fitbit.com",
|
||||
"friendly_name": "Steps",
|
||||
"icon": "mdi:walk",
|
||||
"unit_of_measurement": "steps",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("devices_response", "monitored_resources"),
|
||||
[([DEVICE_RESPONSE_CHARGE_2, DEVICE_RESPONSE_ARIA_AIR], ["devices/battery"])],
|
||||
)
|
||||
async def test_device_battery_level(
|
||||
hass: HomeAssistant,
|
||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
||||
) -> None:
|
||||
"""Test battery level sensor for devices."""
|
||||
|
||||
await sensor_platform_setup()
|
||||
|
||||
state = hass.states.get("sensor.charge_2_battery")
|
||||
assert state
|
||||
assert state.state == "Medium"
|
||||
assert state.attributes == {
|
||||
"attribution": "Data provided by Fitbit.com",
|
||||
"friendly_name": "Charge 2 Battery",
|
||||
"icon": "mdi:battery-50",
|
||||
"model": "Charge 2",
|
||||
"type": "tracker",
|
||||
}
|
||||
|
||||
state = hass.states.get("sensor.aria_air_battery")
|
||||
assert state
|
||||
assert state.state == "High"
|
||||
assert state.attributes == {
|
||||
"attribution": "Data provided by Fitbit.com",
|
||||
"friendly_name": "Aria Air Battery",
|
||||
"icon": "mdi:battery",
|
||||
"model": "Aria Air",
|
||||
"type": "scale",
|
||||
}
|
Loading…
Reference in New Issue