mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Remove profile from Withings config flow (#100202)
* Remove profile from Withings config flow * Add config flow migration * Add config flow migration * Remove datamanager profile * Remove datamanager profile * Add manufacturer * Remove migration * Remove migration * Fix feedback
This commit is contained in:
parent
80aa19263b
commit
8498cdfb3c
@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from aiohttp.web import Request, Response
|
from aiohttp.web import Request, Response
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -17,12 +16,14 @@ from homeassistant.components.application_credentials import (
|
|||||||
async_import_client_credential,
|
async_import_client_credential,
|
||||||
)
|
)
|
||||||
from homeassistant.components.webhook import (
|
from homeassistant.components.webhook import (
|
||||||
|
async_generate_id,
|
||||||
async_unregister as async_unregister_webhook,
|
async_unregister as async_unregister_webhook,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_CLIENT_ID,
|
CONF_CLIENT_ID,
|
||||||
CONF_CLIENT_SECRET,
|
CONF_CLIENT_SECRET,
|
||||||
|
CONF_TOKEN,
|
||||||
CONF_WEBHOOK_ID,
|
CONF_WEBHOOK_ID,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
@ -39,6 +40,7 @@ from .common import (
|
|||||||
get_data_manager_by_webhook_id,
|
get_data_manager_by_webhook_id,
|
||||||
json_message_response,
|
json_message_response,
|
||||||
)
|
)
|
||||||
|
from .const import CONF_USE_WEBHOOK, CONFIG
|
||||||
|
|
||||||
DOMAIN = const.DOMAIN
|
DOMAIN = const.DOMAIN
|
||||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||||
@ -103,33 +105,27 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Withings from a config entry."""
|
"""Set up Withings from a config entry."""
|
||||||
config_updates: dict[str, Any] = {}
|
if CONF_USE_WEBHOOK not in entry.options:
|
||||||
|
new_data = entry.data.copy()
|
||||||
# Add a unique id if it's an older config entry.
|
new_options = {
|
||||||
if entry.unique_id != entry.data["token"]["userid"] or not isinstance(
|
CONF_USE_WEBHOOK: new_data.get(CONF_USE_WEBHOOK, False),
|
||||||
entry.unique_id, str
|
|
||||||
):
|
|
||||||
config_updates["unique_id"] = str(entry.data["token"]["userid"])
|
|
||||||
|
|
||||||
# Add the webhook configuration.
|
|
||||||
if CONF_WEBHOOK_ID not in entry.data:
|
|
||||||
webhook_id = webhook.async_generate_id()
|
|
||||||
config_updates["data"] = {
|
|
||||||
**entry.data,
|
|
||||||
**{
|
|
||||||
const.CONF_USE_WEBHOOK: hass.data[DOMAIN][const.CONFIG][
|
|
||||||
const.CONF_USE_WEBHOOK
|
|
||||||
],
|
|
||||||
CONF_WEBHOOK_ID: webhook_id,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
unique_id = str(entry.data[CONF_TOKEN]["userid"])
|
||||||
|
if CONF_WEBHOOK_ID not in new_data:
|
||||||
|
new_data[CONF_WEBHOOK_ID] = async_generate_id()
|
||||||
|
|
||||||
if config_updates:
|
hass.config_entries.async_update_entry(
|
||||||
hass.config_entries.async_update_entry(entry, **config_updates)
|
entry, data=new_data, options=new_options, unique_id=unique_id
|
||||||
|
)
|
||||||
|
use_webhook = hass.data[DOMAIN][CONFIG][CONF_USE_WEBHOOK]
|
||||||
|
if use_webhook is not None and use_webhook != entry.options[CONF_USE_WEBHOOK]:
|
||||||
|
new_options = entry.options.copy()
|
||||||
|
new_options |= {CONF_USE_WEBHOOK: use_webhook}
|
||||||
|
hass.config_entries.async_update_entry(entry, options=new_options)
|
||||||
|
|
||||||
data_manager = await async_get_data_manager(hass, entry)
|
data_manager = await async_get_data_manager(hass, entry)
|
||||||
|
|
||||||
_LOGGER.debug("Confirming %s is authenticated to withings", data_manager.profile)
|
_LOGGER.debug("Confirming %s is authenticated to withings", entry.title)
|
||||||
await data_manager.poll_data_update_coordinator.async_config_entry_first_refresh()
|
await data_manager.poll_data_update_coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
webhook.async_register(
|
webhook.async_register(
|
||||||
|
@ -203,7 +203,6 @@ class DataManager:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
profile: str,
|
|
||||||
api: ConfigEntryWithingsApi,
|
api: ConfigEntryWithingsApi,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
webhook_config: WebhookConfig,
|
webhook_config: WebhookConfig,
|
||||||
@ -212,7 +211,6 @@ class DataManager:
|
|||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._api = api
|
self._api = api
|
||||||
self._user_id = user_id
|
self._user_id = user_id
|
||||||
self._profile = profile
|
|
||||||
self._webhook_config = webhook_config
|
self._webhook_config = webhook_config
|
||||||
self._notify_subscribe_delay = SUBSCRIBE_DELAY
|
self._notify_subscribe_delay = SUBSCRIBE_DELAY
|
||||||
self._notify_unsubscribe_delay = UNSUBSCRIBE_DELAY
|
self._notify_unsubscribe_delay = UNSUBSCRIBE_DELAY
|
||||||
@ -256,11 +254,6 @@ class DataManager:
|
|||||||
"""Get the user_id of the authenticated user."""
|
"""Get the user_id of the authenticated user."""
|
||||||
return self._user_id
|
return self._user_id
|
||||||
|
|
||||||
@property
|
|
||||||
def profile(self) -> str:
|
|
||||||
"""Get the profile."""
|
|
||||||
return self._profile
|
|
||||||
|
|
||||||
def async_start_polling_webhook_subscriptions(self) -> None:
|
def async_start_polling_webhook_subscriptions(self) -> None:
|
||||||
"""Start polling webhook subscriptions (if enabled) to reconcile their setup."""
|
"""Start polling webhook subscriptions (if enabled) to reconcile their setup."""
|
||||||
self.async_stop_polling_webhook_subscriptions()
|
self.async_stop_polling_webhook_subscriptions()
|
||||||
@ -530,12 +523,11 @@ async def async_get_data_manager(
|
|||||||
config_entry_data = hass.data[const.DOMAIN][config_entry.entry_id]
|
config_entry_data = hass.data[const.DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
if const.DATA_MANAGER not in config_entry_data:
|
if const.DATA_MANAGER not in config_entry_data:
|
||||||
profile: str = config_entry.data[const.PROFILE]
|
_LOGGER.debug(
|
||||||
|
"Creating withings data manager for profile: %s", config_entry.title
|
||||||
_LOGGER.debug("Creating withings data manager for profile: %s", profile)
|
)
|
||||||
config_entry_data[const.DATA_MANAGER] = DataManager(
|
config_entry_data[const.DATA_MANAGER] = DataManager(
|
||||||
hass,
|
hass,
|
||||||
profile,
|
|
||||||
ConfigEntryWithingsApi(
|
ConfigEntryWithingsApi(
|
||||||
hass=hass,
|
hass=hass,
|
||||||
config_entry=config_entry,
|
config_entry=config_entry,
|
||||||
@ -549,7 +541,7 @@ async def async_get_data_manager(
|
|||||||
url=webhook.async_generate_url(
|
url=webhook.async_generate_url(
|
||||||
hass, config_entry.data[CONF_WEBHOOK_ID]
|
hass, config_entry.data[CONF_WEBHOOK_ID]
|
||||||
),
|
),
|
||||||
enabled=config_entry.data[const.CONF_USE_WEBHOOK],
|
enabled=config_entry.options[const.CONF_USE_WEBHOOK],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,26 +5,24 @@ from collections.abc import Mapping
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
from withings_api.common import AuthScope
|
from withings_api.common import AuthScope
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_TOKEN
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers import config_entry_oauth2_flow
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
from homeassistant.util import slugify
|
|
||||||
|
|
||||||
from . import const
|
from .const import CONF_USE_WEBHOOK, DEFAULT_TITLE, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
class WithingsFlowHandler(
|
class WithingsFlowHandler(
|
||||||
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=const.DOMAIN
|
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||||
):
|
):
|
||||||
"""Handle a config flow."""
|
"""Handle a config flow."""
|
||||||
|
|
||||||
DOMAIN = const.DOMAIN
|
DOMAIN = DOMAIN
|
||||||
|
|
||||||
# Temporarily holds authorization data during the profile step.
|
reauth_entry: ConfigEntry | None = None
|
||||||
_current_data: dict[str, None | str | int] = {}
|
|
||||||
_reauth_profile: str | None = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def logger(self) -> logging.Logger:
|
def logger(self) -> logging.Logger:
|
||||||
@ -45,64 +43,37 @@ class WithingsFlowHandler(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
|
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||||
"""Override the create entry so user can select a profile."""
|
"""Perform reauth upon an API authentication error."""
|
||||||
self._current_data = data
|
self.reauth_entry = self.hass.config_entries.async_get_entry(
|
||||||
return await self.async_step_profile(data)
|
self.context["entry_id"]
|
||||||
|
|
||||||
async def async_step_profile(self, data: dict[str, Any]) -> FlowResult:
|
|
||||||
"""Prompt the user to select a user profile."""
|
|
||||||
errors = {}
|
|
||||||
profile = data.get(const.PROFILE) or self._reauth_profile
|
|
||||||
|
|
||||||
if profile:
|
|
||||||
existing_entries = [
|
|
||||||
config_entry
|
|
||||||
for config_entry in self._async_current_entries()
|
|
||||||
if slugify(config_entry.data.get(const.PROFILE)) == slugify(profile)
|
|
||||||
]
|
|
||||||
|
|
||||||
if self._reauth_profile or not existing_entries:
|
|
||||||
new_data = {**self._current_data, **data, const.PROFILE: profile}
|
|
||||||
self._current_data = {}
|
|
||||||
return await self.async_step_finish(new_data)
|
|
||||||
|
|
||||||
errors["base"] = "already_configured"
|
|
||||||
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id="profile",
|
|
||||||
data_schema=vol.Schema({vol.Required(const.PROFILE): str}),
|
|
||||||
errors=errors,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult:
|
|
||||||
"""Prompt user to re-authenticate."""
|
|
||||||
self._reauth_profile = data.get(const.PROFILE)
|
|
||||||
return await self.async_step_reauth_confirm()
|
return await self.async_step_reauth_confirm()
|
||||||
|
|
||||||
async def async_step_reauth_confirm(
|
async def async_step_reauth_confirm(
|
||||||
self, data: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Prompt user to re-authenticate."""
|
"""Confirm reauth dialog."""
|
||||||
if data is not None:
|
if user_input is None:
|
||||||
return await self.async_step_user()
|
return self.async_show_form(step_id="reauth_confirm")
|
||||||
|
return await self.async_step_user()
|
||||||
|
|
||||||
placeholders = {const.PROFILE: self._reauth_profile}
|
async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
|
||||||
|
"""Create an entry for the flow, or update existing entry."""
|
||||||
|
user_id = str(data[CONF_TOKEN]["userid"])
|
||||||
|
if not self.reauth_entry:
|
||||||
|
await self.async_set_unique_id(user_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
self.context.update({"title_placeholders": placeholders})
|
return self.async_create_entry(
|
||||||
|
title=DEFAULT_TITLE,
|
||||||
|
data=data,
|
||||||
|
options={CONF_USE_WEBHOOK: False},
|
||||||
|
)
|
||||||
|
|
||||||
return self.async_show_form(
|
if self.reauth_entry.unique_id == user_id:
|
||||||
step_id="reauth_confirm",
|
self.hass.config_entries.async_update_entry(self.reauth_entry, data=data)
|
||||||
description_placeholders=placeholders,
|
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
||||||
)
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
async def async_step_finish(self, data: dict[str, Any]) -> FlowResult:
|
return self.async_abort(reason="wrong_account")
|
||||||
"""Finish the flow."""
|
|
||||||
self._current_data = {}
|
|
||||||
|
|
||||||
await self.async_set_unique_id(
|
|
||||||
str(data["token"]["userid"]), raise_on_progress=False
|
|
||||||
)
|
|
||||||
self._abort_if_unique_id_configured(data)
|
|
||||||
|
|
||||||
return self.async_create_entry(title=data[const.PROFILE], data=data)
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Constants used by the Withings component."""
|
"""Constants used by the Withings component."""
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
|
||||||
|
DEFAULT_TITLE = "Withings"
|
||||||
CONF_PROFILES = "profiles"
|
CONF_PROFILES = "profiles"
|
||||||
CONF_USE_WEBHOOK = "use_webhook"
|
CONF_USE_WEBHOOK = "use_webhook"
|
||||||
|
|
||||||
|
@ -46,8 +46,7 @@ class BaseWithingsSensor(Entity):
|
|||||||
)
|
)
|
||||||
self._state_data: Any | None = None
|
self._state_data: Any | None = None
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, str(data_manager.user_id))},
|
identifiers={(DOMAIN, str(data_manager.user_id))}, manufacturer="Withings"
|
||||||
name=data_manager.profile,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -4,8 +4,10 @@ from typing import Any
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from homeassistant.components.webhook import async_generate_url
|
from homeassistant.components.webhook import async_generate_url
|
||||||
|
from homeassistant.components.withings.const import CONF_USE_WEBHOOK, DOMAIN
|
||||||
from homeassistant.config import async_process_ha_core_config
|
from homeassistant.config import async_process_ha_core_config
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -48,3 +50,16 @@ async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry)
|
|||||||
)
|
)
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def enable_webhooks(hass: HomeAssistant) -> None:
|
||||||
|
"""Enable webhooks."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: {
|
||||||
|
CONF_USE_WEBHOOK: True,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@ -96,9 +96,11 @@ def mock_config_entry(expires_at: int, scopes: list[str]) -> MockConfigEntry:
|
|||||||
"scope": ",".join(scopes),
|
"scope": ",".join(scopes),
|
||||||
},
|
},
|
||||||
"profile": TITLE,
|
"profile": TITLE,
|
||||||
"use_webhook": True,
|
|
||||||
"webhook_id": WEBHOOK_ID,
|
"webhook_id": WEBHOOK_ID,
|
||||||
},
|
},
|
||||||
|
options={
|
||||||
|
"use_webhook": True,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from withings_api.common import NotifyAppli
|
|||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import call_webhook, setup_integration
|
from . import call_webhook, enable_webhooks, setup_integration
|
||||||
from .conftest import USER_ID, WEBHOOK_ID
|
from .conftest import USER_ID, WEBHOOK_ID
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -21,6 +21,7 @@ async def test_binary_sensor(
|
|||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test binary sensor."""
|
"""Test binary sensor."""
|
||||||
|
await enable_webhooks(hass)
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
|
|
||||||
client = await hass_client_no_auth()
|
client = await hass_client_no_auth()
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
"""Tests for the Withings component."""
|
|
||||||
from http import HTTPStatus
|
|
||||||
import re
|
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
|
||||||
import pytest
|
|
||||||
import requests_mock
|
|
||||||
from withings_api.common import NotifyAppli
|
|
||||||
|
|
||||||
from homeassistant.components.withings.common import ConfigEntryWithingsApi
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2Implementation
|
|
||||||
|
|
||||||
from .common import ComponentFactory, get_data_manager_by_user_id, new_profile_config
|
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
|
||||||
from tests.typing import ClientSessionGenerator
|
|
||||||
|
|
||||||
|
|
||||||
async def test_config_entry_withings_api(hass: HomeAssistant) -> None:
|
|
||||||
"""Test ConfigEntryWithingsApi."""
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
data={"token": {"access_token": "mock_access_token", "expires_at": 1111111}}
|
|
||||||
)
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
implementation_mock = MagicMock(spec=AbstractOAuth2Implementation)
|
|
||||||
implementation_mock.async_refresh_token.return_value = {
|
|
||||||
"expires_at": 1111111,
|
|
||||||
"access_token": "mock_access_token",
|
|
||||||
}
|
|
||||||
|
|
||||||
with requests_mock.mock() as rqmck:
|
|
||||||
rqmck.get(
|
|
||||||
re.compile(".*"),
|
|
||||||
status_code=HTTPStatus.OK,
|
|
||||||
json={"status": 0, "body": {"message": "success"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
api = ConfigEntryWithingsApi(hass, config_entry, implementation_mock)
|
|
||||||
response = await hass.async_add_executor_job(
|
|
||||||
api.request, "test", {"arg1": "val1", "arg2": "val2"}
|
|
||||||
)
|
|
||||||
assert response == {"message": "success"}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("user_id", "arg_user_id", "arg_appli", "expected_code"),
|
|
||||||
[
|
|
||||||
[0, 0, NotifyAppli.WEIGHT.value, 0], # Success
|
|
||||||
[0, None, 1, 0], # Success, we ignore the user_id.
|
|
||||||
[0, None, None, 12], # No request body.
|
|
||||||
[0, "GG", None, 20], # appli not provided.
|
|
||||||
[0, 0, None, 20], # appli not provided.
|
|
||||||
[0, 0, 99, 21], # Invalid appli.
|
|
||||||
[0, 11, NotifyAppli.WEIGHT.value, 0], # Success, we ignore the user_id
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_webhook_post(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
component_factory: ComponentFactory,
|
|
||||||
aiohttp_client: ClientSessionGenerator,
|
|
||||||
user_id: int,
|
|
||||||
arg_user_id: Any,
|
|
||||||
arg_appli: Any,
|
|
||||||
expected_code: int,
|
|
||||||
current_request_with_host: None,
|
|
||||||
) -> None:
|
|
||||||
"""Test webhook callback."""
|
|
||||||
person0 = new_profile_config("person0", user_id)
|
|
||||||
|
|
||||||
await component_factory.configure_component(profile_configs=(person0,))
|
|
||||||
await component_factory.setup_profile(person0.user_id)
|
|
||||||
data_manager = get_data_manager_by_user_id(hass, user_id)
|
|
||||||
|
|
||||||
client: TestClient = await aiohttp_client(hass.http.app)
|
|
||||||
|
|
||||||
post_data = {}
|
|
||||||
if arg_user_id is not None:
|
|
||||||
post_data["userid"] = arg_user_id
|
|
||||||
if arg_appli is not None:
|
|
||||||
post_data["appli"] = arg_appli
|
|
||||||
|
|
||||||
resp = await client.post(
|
|
||||||
urlparse(data_manager.webhook_config.url).path, data=post_data
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wait for remaining tasks to complete.
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
data = await resp.json()
|
|
||||||
resp.close()
|
|
||||||
|
|
||||||
assert data["code"] == expected_code
|
|
@ -1,13 +1,14 @@
|
|||||||
"""Tests for config flow."""
|
"""Tests for config flow."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from homeassistant.components.withings.const import DOMAIN, PROFILE
|
from homeassistant.components.withings.const import DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
|
||||||
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
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
from .conftest import CLIENT_ID
|
from . import setup_integration
|
||||||
|
from .conftest import CLIENT_ID, USER_ID
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
@ -63,18 +64,12 @@ async def test_full_flow(
|
|||||||
"homeassistant.components.withings.async_setup_entry", return_value=True
|
"homeassistant.components.withings.async_setup_entry", return_value=True
|
||||||
) as mock_setup:
|
) as mock_setup:
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
assert result["type"] == FlowResultType.FORM
|
|
||||||
assert result["step_id"] == "profile"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
|
||||||
result["flow_id"], user_input={PROFILE: "Henk"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == "Henk"
|
assert result["title"] == "Withings"
|
||||||
assert "result" in result
|
assert "result" in result
|
||||||
assert result["result"].unique_id == "600"
|
assert result["result"].unique_id == "600"
|
||||||
assert "token" in result["result"].data
|
assert "token" in result["result"].data
|
||||||
@ -86,12 +81,13 @@ async def test_config_non_unique_profile(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
current_request_with_host: None,
|
current_request_with_host: None,
|
||||||
|
withings: AsyncMock,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
disable_webhook_delay,
|
disable_webhook_delay,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test setup a non-unique profile."""
|
"""Test setup a non-unique profile."""
|
||||||
config_entry = MockConfigEntry(domain=DOMAIN, data={PROFILE: "Henk"}, unique_id="0")
|
await setup_integration(hass, config_entry)
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
)
|
)
|
||||||
@ -126,28 +122,13 @@ async def test_config_non_unique_profile(
|
|||||||
"access_token": "mock-access-token",
|
"access_token": "mock-access-token",
|
||||||
"type": "Bearer",
|
"type": "Bearer",
|
||||||
"expires_in": 60,
|
"expires_in": 60,
|
||||||
"userid": 10,
|
"userid": USER_ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.ABORT
|
||||||
assert result["step_id"] == "profile"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
|
||||||
result["flow_id"], user_input={PROFILE: "Henk"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result
|
|
||||||
assert result["errors"]["base"] == "already_configured"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
|
||||||
result["flow_id"], user_input={PROFILE: "Henk 2"}
|
|
||||||
)
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
|
||||||
assert result["title"] == "Henk 2"
|
|
||||||
assert "result" in result
|
|
||||||
assert result["result"].unique_id == "10"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_config_reauth_profile(
|
async def test_config_reauth_profile(
|
||||||
@ -155,18 +136,22 @@ async def test_config_reauth_profile(
|
|||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
|
withings: AsyncMock,
|
||||||
disable_webhook_delay,
|
disable_webhook_delay,
|
||||||
current_request_with_host,
|
current_request_with_host,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth an existing profile re-creates the config entry."""
|
"""Test reauth an existing profile reauthenticates the config entry."""
|
||||||
config_entry.add_to_hass(hass)
|
await setup_integration(hass, config_entry)
|
||||||
|
|
||||||
config_entry.async_start_reauth(hass)
|
result = await hass.config_entries.flow.async_init(
|
||||||
await hass.async_block_till_done()
|
DOMAIN,
|
||||||
|
context={
|
||||||
flows = hass.config_entries.flow.async_progress()
|
"source": SOURCE_REAUTH,
|
||||||
assert len(flows) == 1
|
"entry_id": config_entry.entry_id,
|
||||||
result = flows[0]
|
},
|
||||||
|
data=config_entry.data,
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
@ -198,12 +183,75 @@ async def test_config_reauth_profile(
|
|||||||
"access_token": "mock-access-token",
|
"access_token": "mock-access-token",
|
||||||
"type": "Bearer",
|
"type": "Bearer",
|
||||||
"expires_in": 60,
|
"expires_in": 60,
|
||||||
"userid": "0",
|
"userid": USER_ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
assert result
|
assert result
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.ABORT
|
||||||
assert result["data"]["token"]["refresh_token"] == "mock-refresh-token"
|
assert result["reason"] == "reauth_successful"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_reauth_wrong_account(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
withings: AsyncMock,
|
||||||
|
disable_webhook_delay,
|
||||||
|
current_request_with_host,
|
||||||
|
) -> None:
|
||||||
|
"""Test reauth with wrong account."""
|
||||||
|
await setup_integration(hass, config_entry)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={
|
||||||
|
"source": SOURCE_REAUTH,
|
||||||
|
"entry_id": config_entry.entry_id,
|
||||||
|
},
|
||||||
|
data=config_entry.data,
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"flow_id": result["flow_id"],
|
||||||
|
"redirect_uri": "https://example.com/auth/external/callback",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result["url"] == (
|
||||||
|
"https://account.withings.com/oauth2_user/authorize2?"
|
||||||
|
f"response_type=code&client_id={CLIENT_ID}&"
|
||||||
|
"redirect_uri=https://example.com/auth/external/callback&"
|
||||||
|
f"state={state}"
|
||||||
|
"&scope=user.info,user.metrics,user.activity,user.sleepevents"
|
||||||
|
)
|
||||||
|
client = await hass_client_no_auth()
|
||||||
|
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
||||||
|
assert resp.status == 200
|
||||||
|
assert resp.headers["content-type"] == "text/html; charset=utf-8"
|
||||||
|
|
||||||
|
aioclient_mock.clear_requests()
|
||||||
|
aioclient_mock.post(
|
||||||
|
"https://wbsapi.withings.net/v2/oauth2",
|
||||||
|
json={
|
||||||
|
"body": {
|
||||||
|
"refresh_token": "mock-refresh-token",
|
||||||
|
"access_token": "mock-access-token",
|
||||||
|
"type": "Bearer",
|
||||||
|
"expires_in": 60,
|
||||||
|
"userid": 12346,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
assert result
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "wrong_account"
|
||||||
|
@ -1,31 +1,20 @@
|
|||||||
"""Tests for the Withings component."""
|
"""Tests for the Withings component."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from typing import Any
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from withings_api.common import NotifyAppli, UnauthorizedException
|
from withings_api.common import NotifyAppli
|
||||||
|
|
||||||
import homeassistant.components.webhook as webhook
|
|
||||||
from homeassistant.components.webhook import async_generate_url
|
from homeassistant.components.webhook import async_generate_url
|
||||||
from homeassistant.components.withings import CONFIG_SCHEMA, DOMAIN, async_setup, const
|
from homeassistant.components.withings import CONFIG_SCHEMA, DOMAIN, async_setup, const
|
||||||
from homeassistant.components.withings.common import ConfigEntryWithingsApi, DataManager
|
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_WEBHOOK_ID
|
||||||
from homeassistant.config import async_process_ha_core_config
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.const import (
|
|
||||||
CONF_CLIENT_ID,
|
|
||||||
CONF_CLIENT_SECRET,
|
|
||||||
CONF_EXTERNAL_URL,
|
|
||||||
CONF_UNIT_SYSTEM,
|
|
||||||
CONF_UNIT_SYSTEM_METRIC,
|
|
||||||
)
|
|
||||||
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import setup_integration
|
from . import enable_webhooks, setup_integration
|
||||||
from .common import ComponentFactory, get_data_manager_by_user_id, new_profile_config
|
|
||||||
from .conftest import WEBHOOK_ID
|
from .conftest import WEBHOOK_ID
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
@ -113,126 +102,6 @@ async def test_async_setup_no_config(hass: HomeAssistant) -> None:
|
|||||||
hass.async_create_task.assert_not_called()
|
hass.async_create_task.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"exception",
|
|
||||||
[
|
|
||||||
UnauthorizedException("401"),
|
|
||||||
UnauthorizedException("401"),
|
|
||||||
Exception("401, this is the message"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
@patch("homeassistant.components.withings.common._RETRY_COEFFICIENT", 0)
|
|
||||||
async def test_auth_failure(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
component_factory: ComponentFactory,
|
|
||||||
exception: Exception,
|
|
||||||
current_request_with_host: None,
|
|
||||||
) -> None:
|
|
||||||
"""Test auth failure."""
|
|
||||||
person0 = new_profile_config(
|
|
||||||
"person0",
|
|
||||||
0,
|
|
||||||
api_response_user_get_device=exception,
|
|
||||||
api_response_measure_get_meas=exception,
|
|
||||||
api_response_sleep_get_summary=exception,
|
|
||||||
)
|
|
||||||
|
|
||||||
await component_factory.configure_component(profile_configs=(person0,))
|
|
||||||
assert not hass.config_entries.flow.async_progress()
|
|
||||||
|
|
||||||
await component_factory.setup_profile(person0.user_id)
|
|
||||||
data_manager = get_data_manager_by_user_id(hass, person0.user_id)
|
|
||||||
await data_manager.poll_data_update_coordinator.async_refresh()
|
|
||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
|
||||||
assert flows
|
|
||||||
assert len(flows) == 1
|
|
||||||
|
|
||||||
flow = flows[0]
|
|
||||||
assert flow["handler"] == const.DOMAIN
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
|
||||||
flow["flow_id"], user_input={}
|
|
||||||
)
|
|
||||||
assert result
|
|
||||||
assert result["type"] == "external"
|
|
||||||
assert result["handler"] == const.DOMAIN
|
|
||||||
assert result["step_id"] == "auth"
|
|
||||||
|
|
||||||
await component_factory.unload(person0)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_set_config_unique_id(
|
|
||||||
hass: HomeAssistant, component_factory: ComponentFactory
|
|
||||||
) -> None:
|
|
||||||
"""Test upgrading configs to use a unique id."""
|
|
||||||
person0 = new_profile_config("person0", 0)
|
|
||||||
|
|
||||||
await component_factory.configure_component(profile_configs=(person0,))
|
|
||||||
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
domain=DOMAIN,
|
|
||||||
data={
|
|
||||||
"token": {"userid": "my_user_id"},
|
|
||||||
"auth_implementation": "withings",
|
|
||||||
"profile": person0.profile,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch("homeassistant.components.withings.async_get_data_manager") as mock:
|
|
||||||
data_manager: DataManager = MagicMock(spec=DataManager)
|
|
||||||
data_manager.poll_data_update_coordinator = MagicMock(
|
|
||||||
spec=DataUpdateCoordinator
|
|
||||||
)
|
|
||||||
data_manager.poll_data_update_coordinator.last_update_success = True
|
|
||||||
data_manager.subscription_update_coordinator = MagicMock(
|
|
||||||
spec=DataUpdateCoordinator
|
|
||||||
)
|
|
||||||
data_manager.subscription_update_coordinator.last_update_success = True
|
|
||||||
mock.return_value = data_manager
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
||||||
assert config_entry.unique_id == "my_user_id"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_set_convert_unique_id_to_string(hass: HomeAssistant) -> None:
|
|
||||||
"""Test upgrading configs to use a unique id."""
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
domain=DOMAIN,
|
|
||||||
data={
|
|
||||||
"token": {"userid": 1234},
|
|
||||||
"auth_implementation": "withings",
|
|
||||||
"profile": "person0",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
hass_config = {
|
|
||||||
HA_DOMAIN: {
|
|
||||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
|
||||||
CONF_EXTERNAL_URL: "http://127.0.0.1:8080/",
|
|
||||||
},
|
|
||||||
const.DOMAIN: {
|
|
||||||
CONF_CLIENT_ID: "my_client_id",
|
|
||||||
CONF_CLIENT_SECRET: "my_client_secret",
|
|
||||||
const.CONF_USE_WEBHOOK: False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.withings.common.ConfigEntryWithingsApi",
|
|
||||||
spec=ConfigEntryWithingsApi,
|
|
||||||
):
|
|
||||||
await async_process_ha_core_config(hass, hass_config.get(HA_DOMAIN))
|
|
||||||
assert await async_setup_component(hass, HA_DOMAIN, {})
|
|
||||||
assert await async_setup_component(hass, webhook.DOMAIN, hass_config)
|
|
||||||
assert await async_setup_component(hass, const.DOMAIN, hass_config)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert config_entry.unique_id == "1234"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_data_manager_webhook_subscription(
|
async def test_data_manager_webhook_subscription(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
withings: AsyncMock,
|
withings: AsyncMock,
|
||||||
@ -241,6 +110,7 @@ async def test_data_manager_webhook_subscription(
|
|||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test data manager webhook subscriptions."""
|
"""Test data manager webhook subscriptions."""
|
||||||
|
await enable_webhooks(hass)
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
await hass_client_no_auth()
|
await hass_client_no_auth()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -285,3 +155,87 @@ async def test_requests(
|
|||||||
path=urlparse(webhook_url).path,
|
path=urlparse(webhook_url).path,
|
||||||
)
|
)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("config_entry"),
|
||||||
|
[
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="123",
|
||||||
|
data={
|
||||||
|
"token": {"userid": 123},
|
||||||
|
"profile": "henk",
|
||||||
|
"use_webhook": False,
|
||||||
|
"webhook_id": "3290798afaebd28519c4883d3d411c7197572e0cc9b8d507471f59a700a61a55",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="123",
|
||||||
|
data={
|
||||||
|
"token": {"userid": 123},
|
||||||
|
"profile": "henk",
|
||||||
|
"use_webhook": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_config_flow_upgrade(
|
||||||
|
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Test config flow upgrade."""
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entry = hass.config_entries.async_get_entry(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert entry.unique_id == "123"
|
||||||
|
assert entry.data["token"]["userid"] == 123
|
||||||
|
assert CONF_WEBHOOK_ID in entry.data
|
||||||
|
assert entry.options == {
|
||||||
|
"use_webhook": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("body", "expected_code"),
|
||||||
|
[
|
||||||
|
[{"userid": 0, "appli": NotifyAppli.WEIGHT.value}, 0], # Success
|
||||||
|
[{"userid": None, "appli": 1}, 0], # Success, we ignore the user_id.
|
||||||
|
[{}, 12], # No request body.
|
||||||
|
[{"userid": "GG"}, 20], # appli not provided.
|
||||||
|
[{"userid": 0}, 20], # appli not provided.
|
||||||
|
[{"userid": 0, "appli": 99}, 21], # Invalid appli.
|
||||||
|
[
|
||||||
|
{"userid": 11, "appli": NotifyAppli.WEIGHT.value},
|
||||||
|
0,
|
||||||
|
], # Success, we ignore the user_id
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_webhook_post(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
withings: AsyncMock,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
|
disable_webhook_delay,
|
||||||
|
body: dict[str, Any],
|
||||||
|
expected_code: int,
|
||||||
|
current_request_with_host: None,
|
||||||
|
) -> None:
|
||||||
|
"""Test webhook callback."""
|
||||||
|
await setup_integration(hass, config_entry)
|
||||||
|
client = await hass_client_no_auth()
|
||||||
|
webhook_url = async_generate_url(hass, WEBHOOK_ID)
|
||||||
|
|
||||||
|
resp = await client.post(urlparse(webhook_url).path, data=body)
|
||||||
|
|
||||||
|
# Wait for remaining tasks to complete.
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
data = await resp.json()
|
||||||
|
resp.close()
|
||||||
|
|
||||||
|
assert data["code"] == expected_code
|
||||||
|
Loading…
x
Reference in New Issue
Block a user