Fix bug in Fitbit config flow, and switch to prefer display name (#103869)
parent
51b599e1f6
commit
96a19d61ab
|
@ -69,7 +69,7 @@ class FitbitApi(ABC):
|
|||
profile = response["user"]
|
||||
self._profile = FitbitProfile(
|
||||
encoded_id=profile["encodedId"],
|
||||
full_name=profile["fullName"],
|
||||
display_name=profile["displayName"],
|
||||
locale=profile.get("locale"),
|
||||
)
|
||||
return self._profile
|
||||
|
|
|
@ -90,7 +90,7 @@ class OAuth2FlowHandler(
|
|||
|
||||
await self.async_set_unique_id(profile.encoded_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=profile.full_name, data=data)
|
||||
return self.async_create_entry(title=profile.display_name, data=data)
|
||||
|
||||
async def async_step_import(self, data: dict[str, Any]) -> FlowResult:
|
||||
"""Handle import from YAML."""
|
||||
|
|
|
@ -14,8 +14,8 @@ class FitbitProfile:
|
|||
encoded_id: str
|
||||
"""The ID representing the Fitbit user."""
|
||||
|
||||
full_name: str
|
||||
"""The first name value specified in the user's account settings."""
|
||||
display_name: str
|
||||
"""The name shown when the user's friends look at their Fitbit profile."""
|
||||
|
||||
locale: str | None
|
||||
"""The locale defined in the user's Fitbit account settings."""
|
||||
|
|
|
@ -32,6 +32,15 @@ PROFILE_USER_ID = "fitbit-api-user-id-1"
|
|||
FAKE_ACCESS_TOKEN = "some-access-token"
|
||||
FAKE_REFRESH_TOKEN = "some-refresh-token"
|
||||
FAKE_AUTH_IMPL = "conftest-imported-cred"
|
||||
FULL_NAME = "First Last"
|
||||
DISPLAY_NAME = "First L."
|
||||
PROFILE_DATA = {
|
||||
"fullName": FULL_NAME,
|
||||
"displayName": DISPLAY_NAME,
|
||||
"displayNameSetting": "name",
|
||||
"firstName": "First",
|
||||
"lastName": "Last",
|
||||
}
|
||||
|
||||
PROFILE_API_URL = "https://api.fitbit.com/1/user/-/profile.json"
|
||||
DEVICES_API_URL = "https://api.fitbit.com/1/user/-/devices.json"
|
||||
|
@ -214,20 +223,34 @@ def mock_profile_locale() -> str:
|
|||
return "en_US"
|
||||
|
||||
|
||||
@pytest.fixture(name="profile_data")
|
||||
def mock_profile_data() -> dict[str, Any]:
|
||||
"""Fixture to return other profile data fields."""
|
||||
return PROFILE_DATA
|
||||
|
||||
|
||||
@pytest.fixture(name="profile_response")
|
||||
def mock_profile_response(
|
||||
profile_id: str, profile_locale: str, profile_data: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Fixture to construct the fake profile API response."""
|
||||
return {
|
||||
"user": {
|
||||
"encodedId": profile_id,
|
||||
"locale": profile_locale,
|
||||
**profile_data,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="profile", autouse=True)
|
||||
def mock_profile(requests_mock: Mocker, profile_id: str, profile_locale: str) -> None:
|
||||
def mock_profile(requests_mock: Mocker, profile_response: dict[str, Any]) -> 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": profile_locale,
|
||||
},
|
||||
},
|
||||
json=profile_response,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ from homeassistant.helpers import config_entry_oauth2_flow, issue_registry as ir
|
|||
|
||||
from .conftest import (
|
||||
CLIENT_ID,
|
||||
DISPLAY_NAME,
|
||||
FAKE_AUTH_IMPL,
|
||||
PROFILE_API_URL,
|
||||
PROFILE_DATA,
|
||||
PROFILE_USER_ID,
|
||||
SERVER_ACCESS_TOKEN,
|
||||
)
|
||||
|
@ -76,7 +78,7 @@ async def test_full_flow(
|
|||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
config_entry = entries[0]
|
||||
assert config_entry.title == "My name"
|
||||
assert config_entry.title == DISPLAY_NAME
|
||||
assert config_entry.unique_id == PROFILE_USER_ID
|
||||
|
||||
data = dict(config_entry.data)
|
||||
|
@ -286,7 +288,7 @@ async def test_import_fitbit_config(
|
|||
|
||||
# Verify valid profile can be fetched from the API
|
||||
config_entry = entries[0]
|
||||
assert config_entry.title == "My name"
|
||||
assert config_entry.title == DISPLAY_NAME
|
||||
assert config_entry.unique_id == PROFILE_USER_ID
|
||||
|
||||
data = dict(config_entry.data)
|
||||
|
@ -598,3 +600,60 @@ async def test_reauth_wrong_user_id(
|
|||
assert result.get("reason") == "wrong_account"
|
||||
|
||||
assert len(mock_setup.mock_calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("profile_data", "expected_title"),
|
||||
[
|
||||
(PROFILE_DATA, DISPLAY_NAME),
|
||||
({"displayName": DISPLAY_NAME}, DISPLAY_NAME),
|
||||
],
|
||||
ids=("full_profile_data", "display_name_only"),
|
||||
)
|
||||
async def test_partial_profile_data(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
current_request_with_host: None,
|
||||
profile: None,
|
||||
setup_credentials: None,
|
||||
expected_title: str,
|
||||
) -> None:
|
||||
"""Check full flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
state = config_entry_oauth2_flow._encode_jwt(
|
||||
hass,
|
||||
{
|
||||
"flow_id": result["flow_id"],
|
||||
"redirect_uri": REDIRECT_URL,
|
||||
},
|
||||
)
|
||||
assert result["type"] == FlowResultType.EXTERNAL_STEP
|
||||
assert result["url"] == (
|
||||
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
|
||||
f"&redirect_uri={REDIRECT_URL}"
|
||||
f"&state={state}"
|
||||
"&scope=activity+heartrate+nutrition+profile+settings+sleep+weight&prompt=consent"
|
||||
)
|
||||
|
||||
client = await hass_client_no_auth()
|
||||
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
||||
assert resp.status == 200
|
||||
|
||||
aioclient_mock.post(
|
||||
OAUTH2_TOKEN,
|
||||
json=SERVER_ACCESS_TOKEN,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fitbit.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
config_entry = entries[0]
|
||||
assert config_entry.title == expected_title
|
||||
|
|
Loading…
Reference in New Issue