Remove code for old fitbit config import (#130783)
* Remove code for old fitbit config import * Remove translations related to issuespull/130719/head
parent
f58b5418ea
commit
96299b16e2
|
@ -6,30 +6,16 @@ from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from typing import Any, Final, cast
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
from fitbit import Fitbit
|
|
||||||
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components.application_credentials import (
|
|
||||||
ClientCredential,
|
|
||||||
async_import_client_credential,
|
|
||||||
)
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_CLIENT_ID,
|
|
||||||
CONF_CLIENT_SECRET,
|
|
||||||
CONF_TOKEN,
|
|
||||||
CONF_UNIT_SYSTEM,
|
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
UnitOfLength,
|
UnitOfLength,
|
||||||
|
@ -38,33 +24,13 @@ from homeassistant.const import (
|
||||||
UnitOfVolume,
|
UnitOfVolume,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.icon import icon_for_battery_level
|
from homeassistant.helpers.icon import icon_for_battery_level
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util.json import load_json_object
|
|
||||||
|
|
||||||
from .api import FitbitApi
|
from .api import FitbitApi
|
||||||
from .const import (
|
from .const import ATTRIBUTION, BATTERY_LEVELS, DOMAIN, FitbitScope, FitbitUnitSystem
|
||||||
ATTR_ACCESS_TOKEN,
|
|
||||||
ATTR_LAST_SAVED_AT,
|
|
||||||
ATTR_REFRESH_TOKEN,
|
|
||||||
ATTRIBUTION,
|
|
||||||
BATTERY_LEVELS,
|
|
||||||
CONF_CLOCK_FORMAT,
|
|
||||||
CONF_MONITORED_RESOURCES,
|
|
||||||
DEFAULT_CLOCK_FORMAT,
|
|
||||||
DEFAULT_CONFIG,
|
|
||||||
DOMAIN,
|
|
||||||
FITBIT_CONFIG_FILE,
|
|
||||||
FITBIT_DEFAULT_RESOURCES,
|
|
||||||
FitbitScope,
|
|
||||||
FitbitUnitSystem,
|
|
||||||
)
|
|
||||||
from .coordinator import FitbitData, FitbitDeviceCoordinator
|
from .coordinator import FitbitData, FitbitDeviceCoordinator
|
||||||
from .exceptions import FitbitApiException, FitbitAuthException
|
from .exceptions import FitbitApiException, FitbitAuthException
|
||||||
from .model import FitbitDevice, config_from_entry_data
|
from .model import FitbitDevice, config_from_entry_data
|
||||||
|
@ -533,126 +499,6 @@ FITBIT_RESOURCE_BATTERY_LEVEL = FitbitSensorEntityDescription(
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
FITBIT_RESOURCES_KEYS: Final[list[str]] = [
|
|
||||||
desc.key
|
|
||||||
for desc in (*FITBIT_RESOURCES_LIST, FITBIT_RESOURCE_BATTERY, SLEEP_START_TIME)
|
|
||||||
]
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA: Final = SENSOR_PLATFORM_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
vol.Optional(
|
|
||||||
CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES
|
|
||||||
): vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_KEYS)]),
|
|
||||||
vol.Optional(CONF_CLOCK_FORMAT, default=DEFAULT_CLOCK_FORMAT): vol.In(
|
|
||||||
["12H", "24H"]
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_UNIT_SYSTEM, default=FitbitUnitSystem.LEGACY_DEFAULT): vol.In(
|
|
||||||
[
|
|
||||||
FitbitUnitSystem.EN_GB,
|
|
||||||
FitbitUnitSystem.EN_US,
|
|
||||||
FitbitUnitSystem.METRIC,
|
|
||||||
FitbitUnitSystem.LEGACY_DEFAULT,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Only import configuration if it was previously created successfully with all
|
|
||||||
# of the following fields.
|
|
||||||
FITBIT_CONF_KEYS = [
|
|
||||||
CONF_CLIENT_ID,
|
|
||||||
CONF_CLIENT_SECRET,
|
|
||||||
ATTR_ACCESS_TOKEN,
|
|
||||||
ATTR_REFRESH_TOKEN,
|
|
||||||
ATTR_LAST_SAVED_AT,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def load_config_file(config_path: str) -> dict[str, Any] | None:
|
|
||||||
"""Load existing valid fitbit.conf from disk for import."""
|
|
||||||
if os.path.isfile(config_path):
|
|
||||||
config_file = load_json_object(config_path)
|
|
||||||
if config_file != DEFAULT_CONFIG and all(
|
|
||||||
key in config_file for key in FITBIT_CONF_KEYS
|
|
||||||
):
|
|
||||||
return config_file
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config: ConfigType,
|
|
||||||
add_entities: AddEntitiesCallback,
|
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Set up the Fitbit sensor."""
|
|
||||||
config_path = hass.config.path(FITBIT_CONFIG_FILE)
|
|
||||||
config_file = await hass.async_add_executor_job(load_config_file, config_path)
|
|
||||||
_LOGGER.debug("loaded config file: %s", config_file)
|
|
||||||
|
|
||||||
if config_file is not None:
|
|
||||||
_LOGGER.debug("Importing existing fitbit.conf application credentials")
|
|
||||||
|
|
||||||
# Refresh the token before importing to ensure it is working and not
|
|
||||||
# expired on first initialization.
|
|
||||||
authd_client = Fitbit(
|
|
||||||
config_file[CONF_CLIENT_ID],
|
|
||||||
config_file[CONF_CLIENT_SECRET],
|
|
||||||
access_token=config_file[ATTR_ACCESS_TOKEN],
|
|
||||||
refresh_token=config_file[ATTR_REFRESH_TOKEN],
|
|
||||||
expires_at=config_file[ATTR_LAST_SAVED_AT],
|
|
||||||
refresh_cb=lambda x: None,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
updated_token = await hass.async_add_executor_job(
|
|
||||||
authd_client.client.refresh_token
|
|
||||||
)
|
|
||||||
except OAuth2Error as err:
|
|
||||||
_LOGGER.debug("Unable to import fitbit OAuth2 credentials: %s", err)
|
|
||||||
translation_key = "deprecated_yaml_import_issue_cannot_connect"
|
|
||||||
else:
|
|
||||||
await async_import_client_credential(
|
|
||||||
hass,
|
|
||||||
DOMAIN,
|
|
||||||
ClientCredential(
|
|
||||||
config_file[CONF_CLIENT_ID], config_file[CONF_CLIENT_SECRET]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN,
|
|
||||||
context={"source": SOURCE_IMPORT},
|
|
||||||
data={
|
|
||||||
"auth_implementation": DOMAIN,
|
|
||||||
CONF_TOKEN: {
|
|
||||||
ATTR_ACCESS_TOKEN: updated_token[ATTR_ACCESS_TOKEN],
|
|
||||||
ATTR_REFRESH_TOKEN: updated_token[ATTR_REFRESH_TOKEN],
|
|
||||||
"expires_at": updated_token["expires_at"],
|
|
||||||
"scope": " ".join(updated_token.get("scope", [])),
|
|
||||||
},
|
|
||||||
CONF_CLOCK_FORMAT: config[CONF_CLOCK_FORMAT],
|
|
||||||
CONF_UNIT_SYSTEM: config[CONF_UNIT_SYSTEM],
|
|
||||||
CONF_MONITORED_RESOURCES: config[CONF_MONITORED_RESOURCES],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
translation_key = "deprecated_yaml_import"
|
|
||||||
if (
|
|
||||||
result.get("type") == FlowResultType.ABORT
|
|
||||||
and result.get("reason") == "cannot_connect"
|
|
||||||
):
|
|
||||||
translation_key = "deprecated_yaml_import_issue_cannot_connect"
|
|
||||||
else:
|
|
||||||
translation_key = "deprecated_yaml_no_import"
|
|
||||||
|
|
||||||
async_create_issue(
|
|
||||||
hass,
|
|
||||||
DOMAIN,
|
|
||||||
"deprecated_yaml",
|
|
||||||
breaks_in_ha_version="2024.5.0",
|
|
||||||
is_fixable=False,
|
|
||||||
severity=IssueSeverity.WARNING,
|
|
||||||
translation_key=translation_key,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|
|
@ -40,19 +40,5 @@
|
||||||
"name": "Battery level"
|
"name": "Battery level"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"issues": {
|
|
||||||
"deprecated_yaml_no_import": {
|
|
||||||
"title": "Fitbit YAML configuration is being removed",
|
|
||||||
"description": "Configuring Fitbit using YAML is being removed.\n\nRemove the `fitbit` configuration from your configuration.yaml file and remove fitbit.conf if it exists and restart Home Assistant and [set up the integration](/config/integrations/dashboard/add?domain=fitbit) manually."
|
|
||||||
},
|
|
||||||
"deprecated_yaml_import": {
|
|
||||||
"title": "Fitbit YAML configuration is being removed",
|
|
||||||
"description": "Configuring Fitbit using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically, including OAuth Application Credentials.\n\nRemove the `fitbit` configuration from your configuration.yaml file and remove fitbit.conf and restart Home Assistant to fix this issue."
|
|
||||||
},
|
|
||||||
"deprecated_yaml_import_issue_cannot_connect": {
|
|
||||||
"title": "The Fitbit YAML configuration import failed",
|
|
||||||
"description": "Configuring Fitbit using YAML is being removed but there was a connection error importing your YAML configuration.\n\nRestart Home Assistant to try again or remove the Fitbit YAML configuration from your configuration.yaml file and remove the fitbit.conf and continue to [set up the integration](/config/integrations/dashboard/add?domain=fitbit) manually."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Test fixtures for fitbit."""
|
"""Test fixtures for fitbit."""
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable, Generator
|
from collections.abc import Awaitable, Callable
|
||||||
import datetime
|
import datetime
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import time
|
import time
|
||||||
|
@ -14,12 +14,7 @@ from homeassistant.components.application_credentials import (
|
||||||
ClientCredential,
|
ClientCredential,
|
||||||
async_import_client_credential,
|
async_import_client_credential,
|
||||||
)
|
)
|
||||||
from homeassistant.components.fitbit.const import (
|
from homeassistant.components.fitbit.const import DOMAIN, OAUTH_SCOPES
|
||||||
CONF_CLIENT_ID,
|
|
||||||
CONF_CLIENT_SECRET,
|
|
||||||
DOMAIN,
|
|
||||||
OAUTH_SCOPES,
|
|
||||||
)
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
@ -83,13 +78,16 @@ def mock_token_entry(token_expiration_time: float, scopes: list[str]) -> dict[st
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="config_entry")
|
@pytest.fixture(name="config_entry")
|
||||||
def mock_config_entry(token_entry: dict[str, Any]) -> MockConfigEntry:
|
def mock_config_entry(
|
||||||
|
token_entry: dict[str, Any], imported_config_data: dict[str, Any]
|
||||||
|
) -> MockConfigEntry:
|
||||||
"""Fixture for a config entry."""
|
"""Fixture for a config entry."""
|
||||||
return MockConfigEntry(
|
return MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={
|
data={
|
||||||
"auth_implementation": FAKE_AUTH_IMPL,
|
"auth_implementation": FAKE_AUTH_IMPL,
|
||||||
"token": token_entry,
|
"token": token_entry,
|
||||||
|
**imported_config_data,
|
||||||
},
|
},
|
||||||
unique_id=PROFILE_USER_ID,
|
unique_id=PROFILE_USER_ID,
|
||||||
)
|
)
|
||||||
|
@ -107,37 +105,6 @@ async def setup_credentials(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="fitbit_config_yaml")
|
|
||||||
def mock_fitbit_config_yaml(token_expiration_time: float) -> dict[str, Any] | None:
|
|
||||||
"""Fixture for the yaml fitbit.conf file contents."""
|
|
||||||
return {
|
|
||||||
CONF_CLIENT_ID: CLIENT_ID,
|
|
||||||
CONF_CLIENT_SECRET: CLIENT_SECRET,
|
|
||||||
"access_token": FAKE_ACCESS_TOKEN,
|
|
||||||
"refresh_token": FAKE_REFRESH_TOKEN,
|
|
||||||
"last_saved_at": token_expiration_time,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="fitbit_config_setup")
|
|
||||||
def mock_fitbit_config_setup(
|
|
||||||
fitbit_config_yaml: dict[str, Any] | None,
|
|
||||||
) -> Generator[None]:
|
|
||||||
"""Fixture to mock out fitbit.conf file data loading and persistence."""
|
|
||||||
has_config = fitbit_config_yaml is not None
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.fitbit.sensor.os.path.isfile",
|
|
||||||
return_value=has_config,
|
|
||||||
),
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.fitbit.sensor.load_json_object",
|
|
||||||
return_value=fitbit_config_yaml,
|
|
||||||
),
|
|
||||||
):
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="monitored_resources")
|
@pytest.fixture(name="monitored_resources")
|
||||||
def mock_monitored_resources() -> list[str] | None:
|
def mock_monitored_resources() -> list[str] | None:
|
||||||
"""Fixture for the fitbit yaml config monitored_resources field."""
|
"""Fixture for the fitbit yaml config monitored_resources field."""
|
||||||
|
@ -150,8 +117,8 @@ def mock_configured_unit_syststem() -> str | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="sensor_platform_config")
|
@pytest.fixture(name="imported_config_data")
|
||||||
def mock_sensor_platform_config(
|
def mock_imported_config_data(
|
||||||
monitored_resources: list[str] | None,
|
monitored_resources: list[str] | None,
|
||||||
configured_unit_system: str | None,
|
configured_unit_system: str | None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
@ -164,32 +131,6 @@ def mock_sensor_platform_config(
|
||||||
return config
|
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
|
@pytest.fixture
|
||||||
def platforms() -> list[Platform]:
|
def platforms() -> list[Platform]:
|
||||||
"""Fixture to specify platforms to test."""
|
"""Fixture to specify platforms to test."""
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import time
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ from homeassistant import config_entries
|
||||||
from homeassistant.components.fitbit.const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
|
from homeassistant.components.fitbit.const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
from homeassistant.helpers import config_entry_oauth2_flow, issue_registry as ir
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
from .conftest import (
|
from .conftest import (
|
||||||
CLIENT_ID,
|
CLIENT_ID,
|
||||||
|
@ -255,207 +254,6 @@ async def test_config_entry_already_exists(
|
||||||
assert result.get("reason") == "already_configured"
|
assert result.get("reason") == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"token_expiration_time",
|
|
||||||
[time.time() + 86400, time.time() - 86400],
|
|
||||||
ids=("token_active", "token_expired"),
|
|
||||||
)
|
|
||||||
async def test_import_fitbit_config(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
fitbit_config_setup: None,
|
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
requests_mock: Mocker,
|
|
||||||
) -> None:
|
|
||||||
"""Test that platform configuration is imported successfully."""
|
|
||||||
|
|
||||||
requests_mock.register_uri(
|
|
||||||
"POST",
|
|
||||||
OAUTH2_TOKEN,
|
|
||||||
status_code=HTTPStatus.OK,
|
|
||||||
json=SERVER_ACCESS_TOKEN,
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
|
||||||
) as mock_setup:
|
|
||||||
await sensor_platform_setup()
|
|
||||||
|
|
||||||
assert len(mock_setup.mock_calls) == 1
|
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(entries) == 1
|
|
||||||
|
|
||||||
# Verify valid profile can be fetched from the API
|
|
||||||
config_entry = entries[0]
|
|
||||||
assert config_entry.title == DISPLAY_NAME
|
|
||||||
assert config_entry.unique_id == PROFILE_USER_ID
|
|
||||||
|
|
||||||
data = dict(config_entry.data)
|
|
||||||
# Verify imported values from fitbit.conf and configuration.yaml and
|
|
||||||
# that the token is updated.
|
|
||||||
assert "token" in data
|
|
||||||
expires_at = data["token"]["expires_at"]
|
|
||||||
assert expires_at > time.time()
|
|
||||||
del data["token"]["expires_at"]
|
|
||||||
assert dict(config_entry.data) == {
|
|
||||||
"auth_implementation": DOMAIN,
|
|
||||||
"clock_format": "24H",
|
|
||||||
"monitored_resources": ["activities/steps"],
|
|
||||||
"token": {
|
|
||||||
"access_token": "server-access-token",
|
|
||||||
"refresh_token": "server-refresh-token",
|
|
||||||
"scope": "activity heartrate nutrition profile settings sleep weight",
|
|
||||||
},
|
|
||||||
"unit_system": "default",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify an issue is raised for deprecated configuration.yaml
|
|
||||||
issue = issue_registry.issues.get((DOMAIN, "deprecated_yaml"))
|
|
||||||
assert issue
|
|
||||||
assert issue.translation_key == "deprecated_yaml_import"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_import_fitbit_config_failure_cannot_connect(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
fitbit_config_setup: None,
|
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
requests_mock: Mocker,
|
|
||||||
) -> None:
|
|
||||||
"""Test platform configuration fails to import successfully."""
|
|
||||||
|
|
||||||
requests_mock.register_uri(
|
|
||||||
"POST",
|
|
||||||
OAUTH2_TOKEN,
|
|
||||||
status_code=HTTPStatus.OK,
|
|
||||||
json=SERVER_ACCESS_TOKEN,
|
|
||||||
)
|
|
||||||
requests_mock.register_uri(
|
|
||||||
"GET", PROFILE_API_URL, status_code=HTTPStatus.INTERNAL_SERVER_ERROR
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
|
||||||
) as mock_setup:
|
|
||||||
await sensor_platform_setup()
|
|
||||||
|
|
||||||
assert len(mock_setup.mock_calls) == 0
|
|
||||||
|
|
||||||
# Verify an issue is raised that we were unable to import configuration
|
|
||||||
issue = issue_registry.issues.get((DOMAIN, "deprecated_yaml"))
|
|
||||||
assert issue
|
|
||||||
assert issue.translation_key == "deprecated_yaml_import_issue_cannot_connect"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"status_code",
|
|
||||||
[
|
|
||||||
(HTTPStatus.UNAUTHORIZED),
|
|
||||||
(HTTPStatus.INTERNAL_SERVER_ERROR),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_import_fitbit_config_cannot_refresh(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
fitbit_config_setup: None,
|
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
requests_mock: Mocker,
|
|
||||||
status_code: HTTPStatus,
|
|
||||||
) -> None:
|
|
||||||
"""Test platform configuration import fails when refreshing the token."""
|
|
||||||
|
|
||||||
requests_mock.register_uri(
|
|
||||||
"POST",
|
|
||||||
OAUTH2_TOKEN,
|
|
||||||
status_code=status_code,
|
|
||||||
json="",
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
|
||||||
) as mock_setup:
|
|
||||||
await sensor_platform_setup()
|
|
||||||
|
|
||||||
assert len(mock_setup.mock_calls) == 0
|
|
||||||
|
|
||||||
# Verify an issue is raised that we were unable to import configuration
|
|
||||||
issue = issue_registry.issues.get((DOMAIN, "deprecated_yaml"))
|
|
||||||
assert issue
|
|
||||||
assert issue.translation_key == "deprecated_yaml_import_issue_cannot_connect"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_import_fitbit_config_already_exists(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config_entry: MockConfigEntry,
|
|
||||||
setup_credentials: None,
|
|
||||||
integration_setup: Callable[[], Awaitable[bool]],
|
|
||||||
fitbit_config_setup: None,
|
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
requests_mock: Mocker,
|
|
||||||
) -> None:
|
|
||||||
"""Test that platform configuration is not imported if it already exists."""
|
|
||||||
|
|
||||||
requests_mock.register_uri(
|
|
||||||
"POST",
|
|
||||||
OAUTH2_TOKEN,
|
|
||||||
status_code=HTTPStatus.OK,
|
|
||||||
json=SERVER_ACCESS_TOKEN,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify existing config entry
|
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(entries) == 1
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
|
||||||
) as mock_config_entry_setup:
|
|
||||||
await integration_setup()
|
|
||||||
|
|
||||||
assert len(mock_config_entry_setup.mock_calls) == 1
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
|
||||||
) as mock_import_setup:
|
|
||||||
await sensor_platform_setup()
|
|
||||||
|
|
||||||
assert len(mock_import_setup.mock_calls) == 0
|
|
||||||
|
|
||||||
# Still one config entry
|
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(entries) == 1
|
|
||||||
|
|
||||||
# Verify an issue is raised for deprecated configuration.yaml
|
|
||||||
issue = issue_registry.issues.get((DOMAIN, "deprecated_yaml"))
|
|
||||||
assert issue
|
|
||||||
assert issue.translation_key == "deprecated_yaml_import"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_platform_setup_without_import(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
) -> None:
|
|
||||||
"""Test platform configuration.yaml but no existing fitbit.conf credentials."""
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
|
||||||
) as mock_setup:
|
|
||||||
await sensor_platform_setup()
|
|
||||||
|
|
||||||
# Verify no configuration entry is imported since the integration is not
|
|
||||||
# fully setup properly
|
|
||||||
assert len(mock_setup.mock_calls) == 0
|
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(entries) == 0
|
|
||||||
|
|
||||||
# Verify an issue is raised for deprecated configuration.yaml
|
|
||||||
assert len(issue_registry.issues) == 1
|
|
||||||
issue = issue_registry.issues.get((DOMAIN, "deprecated_yaml"))
|
|
||||||
assert issue
|
|
||||||
assert issue.translation_key == "deprecated_yaml_no_import"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("current_request_with_host")
|
@pytest.mark.usefixtures("current_request_with_host")
|
||||||
async def test_reauth_flow(
|
async def test_reauth_flow(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|
|
@ -212,8 +212,8 @@ def mock_token_refresh(requests_mock: Mocker) -> None:
|
||||||
)
|
)
|
||||||
async def test_sensors(
|
async def test_sensors(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
fitbit_config_setup: None,
|
setup_credentials: None,
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
register_timeseries: Callable[[str, dict[str, Any]], None],
|
register_timeseries: Callable[[str, dict[str, Any]], None],
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
entity_id: str,
|
entity_id: str,
|
||||||
|
@ -226,7 +226,7 @@ async def test_sensors(
|
||||||
register_timeseries(
|
register_timeseries(
|
||||||
api_resource, timeseries_response(api_resource.replace("/", "-"), api_value)
|
api_resource, timeseries_response(api_resource.replace("/", "-"), api_value)
|
||||||
)
|
)
|
||||||
await sensor_platform_setup()
|
await integration_setup()
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
assert len(entries) == 1
|
assert len(entries) == 1
|
||||||
|
|
||||||
|
@ -243,13 +243,13 @@ async def test_sensors(
|
||||||
)
|
)
|
||||||
async def test_device_battery(
|
async def test_device_battery(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
fitbit_config_setup: None,
|
setup_credentials: None,
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test battery level sensor for devices."""
|
"""Test battery level sensor for devices."""
|
||||||
|
|
||||||
assert await sensor_platform_setup()
|
assert await integration_setup()
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
assert len(entries) == 1
|
assert len(entries) == 1
|
||||||
|
|
||||||
|
@ -290,13 +290,13 @@ async def test_device_battery(
|
||||||
)
|
)
|
||||||
async def test_device_battery_level(
|
async def test_device_battery_level(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
fitbit_config_setup: None,
|
setup_credentials: None,
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test battery level sensor for devices."""
|
"""Test battery level sensor for devices."""
|
||||||
|
|
||||||
assert await sensor_platform_setup()
|
assert await integration_setup()
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
assert len(entries) == 1
|
assert len(entries) == 1
|
||||||
|
|
||||||
|
@ -347,15 +347,15 @@ async def test_device_battery_level(
|
||||||
)
|
)
|
||||||
async def test_profile_local(
|
async def test_profile_local(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
fitbit_config_setup: None,
|
setup_credentials: None,
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
register_timeseries: Callable[[str, dict[str, Any]], None],
|
register_timeseries: Callable[[str, dict[str, Any]], None],
|
||||||
expected_unit: str,
|
expected_unit: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the fitbit profile locale impact on unit of measure."""
|
"""Test the fitbit profile locale impact on unit of measure."""
|
||||||
|
|
||||||
register_timeseries("body/weight", timeseries_response("body-weight", "175"))
|
register_timeseries("body/weight", timeseries_response("body-weight", "175"))
|
||||||
await sensor_platform_setup()
|
await integration_setup()
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
assert len(entries) == 1
|
assert len(entries) == 1
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ async def test_profile_local(
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("sensor_platform_config", "api_response", "expected_state"),
|
("imported_config_data", "api_response", "expected_state"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
|
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
|
||||||
|
@ -396,8 +396,8 @@ async def test_profile_local(
|
||||||
)
|
)
|
||||||
async def test_sleep_time_clock_format(
|
async def test_sleep_time_clock_format(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
fitbit_config_setup: None,
|
setup_credentials: None,
|
||||||
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
register_timeseries: Callable[[str, dict[str, Any]], None],
|
register_timeseries: Callable[[str, dict[str, Any]], None],
|
||||||
api_response: str,
|
api_response: str,
|
||||||
expected_state: str,
|
expected_state: str,
|
||||||
|
@ -407,7 +407,7 @@ async def test_sleep_time_clock_format(
|
||||||
register_timeseries(
|
register_timeseries(
|
||||||
"sleep/startTime", timeseries_response("sleep-startTime", api_response)
|
"sleep/startTime", timeseries_response("sleep-startTime", api_response)
|
||||||
)
|
)
|
||||||
await sensor_platform_setup()
|
assert await integration_setup()
|
||||||
|
|
||||||
state = hass.states.get("sensor.sleep_start_time")
|
state = hass.states.get("sensor.sleep_start_time")
|
||||||
assert state
|
assert state
|
||||||
|
|
Loading…
Reference in New Issue