Add tests for fitbit integration (#100765)

* Add tests for fitbit integration

* Update coveragerc

* Update test requirements
pull/100772/head
Allen Porter 2023-09-23 12:04:44 -07:00 committed by GitHub
parent 71aef4e95a
commit 781bc5b3bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 265 additions and 1 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1 @@
"""Tests for fitbit component."""

View 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

View 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",
}