Add tests for fitbit integration (#100765)
* Add tests for fitbit integration * Update coveragerc * Update test requirements
This commit is contained in:
parent
71aef4e95a
commit
781bc5b3bc
6 changed files with 265 additions and 1 deletions
|
@ -389,7 +389,6 @@ omit =
|
||||||
homeassistant/components/firmata/pin.py
|
homeassistant/components/firmata/pin.py
|
||||||
homeassistant/components/firmata/sensor.py
|
homeassistant/components/firmata/sensor.py
|
||||||
homeassistant/components/firmata/switch.py
|
homeassistant/components/firmata/switch.py
|
||||||
homeassistant/components/fitbit/*
|
|
||||||
homeassistant/components/fivem/__init__.py
|
homeassistant/components/fivem/__init__.py
|
||||||
homeassistant/components/fivem/binary_sensor.py
|
homeassistant/components/fivem/binary_sensor.py
|
||||||
homeassistant/components/fivem/coordinator.py
|
homeassistant/components/fivem/coordinator.py
|
||||||
|
|
|
@ -12,6 +12,8 @@ from homeassistant.const import (
|
||||||
UnitOfVolume,
|
UnitOfVolume,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOMAIN: Final = "fitbit"
|
||||||
|
|
||||||
ATTR_ACCESS_TOKEN: Final = "access_token"
|
ATTR_ACCESS_TOKEN: Final = "access_token"
|
||||||
ATTR_REFRESH_TOKEN: Final = "refresh_token"
|
ATTR_REFRESH_TOKEN: Final = "refresh_token"
|
||||||
ATTR_LAST_SAVED_AT: Final = "last_saved_at"
|
ATTR_LAST_SAVED_AT: Final = "last_saved_at"
|
||||||
|
|
|
@ -629,6 +629,9 @@ feedparser==6.0.10
|
||||||
# homeassistant.components.file
|
# homeassistant.components.file
|
||||||
file-read-backwards==2.0.0
|
file-read-backwards==2.0.0
|
||||||
|
|
||||||
|
# homeassistant.components.fitbit
|
||||||
|
fitbit==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.fivem
|
# homeassistant.components.fivem
|
||||||
fivem-api==0.1.2
|
fivem-api==0.1.2
|
||||||
|
|
||||||
|
|
1
tests/components/fitbit/__init__.py
Normal file
1
tests/components/fitbit/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for fitbit component."""
|
167
tests/components/fitbit/conftest.py
Normal file
167
tests/components/fitbit/conftest.py
Normal file
|
@ -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
|
92
tests/components/fitbit/test_sensor.py
Normal file
92
tests/components/fitbit/test_sensor.py
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue