Fix bug in Fitbit config flow, and switch to prefer display name (#103869)

pull/103873/head
Allen Porter 2023-11-12 10:27:02 -08:00 committed by GitHub
parent 51b599e1f6
commit 96a19d61ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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