Enable config flow for html5 (#112806)
* html5: Enable config flow * Add tests * attempt check create_issue * replace len with call_count * fix config flow tests * test user config * more tests * remove whitespace * Update homeassistant/components/html5/issues.py Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> * Update homeassistant/components/html5/issues.py Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> * fix config * Adjust issues log * lint * lint * rename create issue * fix typing * update codeowners * fix test * fix tests * Update issues.py * Update tests/components/html5/test_config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update tests/components/html5/test_config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update tests/components/html5/test_config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * update from review * remove ternary * fix * fix missing service * fix tests * updates * adress review comments * fix indent * fix * fix format * cleanup from review * Restore config schema and use HA issue * Restore config schema and use HA issue --------- Co-authored-by: alexyao2015 <alexyao2015@users.noreply.github.com> Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Joostlek <joostlek@outlook.com>pull/124749/head^2
parent
ac39bf991f
commit
26281662b5
|
@ -633,6 +633,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/homewizard/ @DCSBL
|
||||
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
||||
/tests/components/honeywell/ @rdfurman @mkmer
|
||||
/homeassistant/components/html5/ @alexyao2015
|
||||
/tests/components/html5/ @alexyao2015
|
||||
/homeassistant/components/http/ @home-assistant/core
|
||||
/tests/components/http/ @home-assistant/core
|
||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||
|
|
|
@ -1 +1,16 @@
|
|||
"""The html5 component."""
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import discovery
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up HTML5 from a config entry."""
|
||||
await discovery.async_load_platform(
|
||||
hass, Platform.NOTIFY, DOMAIN, dict(entry.data), {}
|
||||
)
|
||||
return True
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
"""Config flow for the html5 component."""
|
||||
|
||||
import binascii
|
||||
from typing import Any, cast
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from py_vapid import Vapid
|
||||
from py_vapid.utils import b64urlencode
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import ATTR_VAPID_EMAIL, ATTR_VAPID_PRV_KEY, ATTR_VAPID_PUB_KEY, DOMAIN
|
||||
from .issues import async_create_html5_issue
|
||||
|
||||
|
||||
def vapid_generate_private_key() -> str:
|
||||
"""Generate a VAPID private key."""
|
||||
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
|
||||
return b64urlencode(
|
||||
binascii.unhexlify(f"{private_key.private_numbers().private_value:x}".zfill(64))
|
||||
)
|
||||
|
||||
|
||||
def vapid_get_public_key(private_key: str) -> str:
|
||||
"""Get the VAPID public key from a private key."""
|
||||
vapid = Vapid.from_string(private_key)
|
||||
public_key = cast(ec.EllipticCurvePublicKey, vapid.public_key)
|
||||
return b64urlencode(
|
||||
public_key.public_bytes(
|
||||
serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for HTML5."""
|
||||
|
||||
@callback
|
||||
def _async_create_html5_entry(
|
||||
self: "HTML5ConfigFlow", data: dict[str, str]
|
||||
) -> tuple[dict[str, str], ConfigFlowResult | None]:
|
||||
"""Create an HTML5 entry."""
|
||||
errors = {}
|
||||
flow_result = None
|
||||
|
||||
if not data.get(ATTR_VAPID_PRV_KEY):
|
||||
data[ATTR_VAPID_PRV_KEY] = vapid_generate_private_key()
|
||||
|
||||
# we will always generate the corresponding public key
|
||||
try:
|
||||
data[ATTR_VAPID_PUB_KEY] = vapid_get_public_key(data[ATTR_VAPID_PRV_KEY])
|
||||
except (ValueError, binascii.Error):
|
||||
errors[ATTR_VAPID_PRV_KEY] = "invalid_prv_key"
|
||||
|
||||
if not errors:
|
||||
config = {
|
||||
ATTR_VAPID_EMAIL: data[ATTR_VAPID_EMAIL],
|
||||
ATTR_VAPID_PRV_KEY: data[ATTR_VAPID_PRV_KEY],
|
||||
ATTR_VAPID_PUB_KEY: data[ATTR_VAPID_PUB_KEY],
|
||||
CONF_NAME: DOMAIN,
|
||||
}
|
||||
flow_result = self.async_create_entry(title="HTML5", data=config)
|
||||
return errors, flow_result
|
||||
|
||||
async def async_step_user(
|
||||
self: "HTML5ConfigFlow", user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input:
|
||||
errors, flow_result = self._async_create_html5_entry(user_input)
|
||||
if flow_result:
|
||||
return flow_result
|
||||
else:
|
||||
user_input = {}
|
||||
|
||||
return self.async_show_form(
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
ATTR_VAPID_EMAIL, default=user_input.get(ATTR_VAPID_EMAIL, "")
|
||||
): str,
|
||||
vol.Optional(ATTR_VAPID_PRV_KEY): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(
|
||||
self: "HTML5ConfigFlow", import_config: dict
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle config import from yaml."""
|
||||
_, flow_result = self._async_create_html5_entry(import_config)
|
||||
if not flow_result:
|
||||
async_create_html5_issue(self.hass, False)
|
||||
return self.async_abort(reason="invalid_config")
|
||||
async_create_html5_issue(self.hass, True)
|
||||
return flow_result
|
|
@ -1,4 +1,9 @@
|
|||
"""Constants for the HTML5 component."""
|
||||
|
||||
DOMAIN = "html5"
|
||||
DATA_HASS_CONFIG = "html5_hass_config"
|
||||
SERVICE_DISMISS = "dismiss"
|
||||
|
||||
ATTR_VAPID_PUB_KEY = "vapid_pub_key"
|
||||
ATTR_VAPID_PRV_KEY = "vapid_prv_key"
|
||||
ATTR_VAPID_EMAIL = "vapid_email"
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"""Issues utility for HTML5."""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUCCESSFUL_IMPORT_TRANSLATION_KEY = "deprecated_yaml"
|
||||
FAILED_IMPORT_TRANSLATION_KEY = "deprecated_yaml_import_issue"
|
||||
|
||||
INTEGRATION_TITLE = "HTML5 Push Notifications"
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_html5_issue(hass: HomeAssistant, import_success: bool) -> None:
|
||||
"""Create issues for HTML5."""
|
||||
if import_success:
|
||||
async_create_issue(
|
||||
hass,
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
f"deprecated_yaml_{DOMAIN}",
|
||||
breaks_in_ha_version="2025.4.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": INTEGRATION_TITLE,
|
||||
},
|
||||
)
|
||||
else:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_yaml_{DOMAIN}",
|
||||
breaks_in_ha_version="2025.4.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_import_issue",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": INTEGRATION_TITLE,
|
||||
},
|
||||
)
|
|
@ -1,10 +1,12 @@
|
|||
{
|
||||
"domain": "html5",
|
||||
"name": "HTML5 Push Notifications",
|
||||
"codeowners": [],
|
||||
"codeowners": ["@alexyao2015"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/html5",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["http_ece", "py_vapid", "pywebpush"],
|
||||
"requirements": ["pywebpush==1.14.1"]
|
||||
"requirements": ["pywebpush==1.14.1"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ from homeassistant.components.notify import (
|
|||
PLATFORM_SCHEMA as NOTIFY_PLATFORM_SCHEMA,
|
||||
BaseNotificationService,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import ATTR_NAME, URL_ROOT
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
@ -38,32 +39,23 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|||
from homeassistant.util import ensure_unique_string
|
||||
from homeassistant.util.json import JsonObjectType, load_json_object
|
||||
|
||||
from .const import DOMAIN, SERVICE_DISMISS
|
||||
from .const import (
|
||||
ATTR_VAPID_EMAIL,
|
||||
ATTR_VAPID_PRV_KEY,
|
||||
ATTR_VAPID_PUB_KEY,
|
||||
DOMAIN,
|
||||
SERVICE_DISMISS,
|
||||
)
|
||||
from .issues import async_create_html5_issue
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REGISTRATIONS_FILE = "html5_push_registrations.conf"
|
||||
|
||||
ATTR_VAPID_PUB_KEY = "vapid_pub_key"
|
||||
ATTR_VAPID_PRV_KEY = "vapid_prv_key"
|
||||
ATTR_VAPID_EMAIL = "vapid_email"
|
||||
|
||||
|
||||
def gcm_api_deprecated(value):
|
||||
"""Warn user that GCM API config is deprecated."""
|
||||
if value:
|
||||
_LOGGER.warning(
|
||||
"Configuring html5_push_notifications via the GCM api"
|
||||
" has been deprecated and stopped working since May 29,"
|
||||
" 2019. Use the VAPID configuration instead. For instructions,"
|
||||
" see https://www.home-assistant.io/integrations/html5/"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = NOTIFY_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional("gcm_sender_id"): vol.All(cv.string, gcm_api_deprecated),
|
||||
vol.Optional("gcm_sender_id"): cv.string,
|
||||
vol.Optional("gcm_api_key"): cv.string,
|
||||
vol.Required(ATTR_VAPID_PUB_KEY): cv.string,
|
||||
vol.Required(ATTR_VAPID_PRV_KEY): cv.string,
|
||||
|
@ -171,15 +163,30 @@ async def async_get_service(
|
|||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> HTML5NotificationService | None:
|
||||
"""Get the HTML5 push notification service."""
|
||||
if config:
|
||||
existing_config_entry = hass.config_entries.async_entries(DOMAIN)
|
||||
if existing_config_entry:
|
||||
async_create_html5_issue(hass, True)
|
||||
return None
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
if discovery_info is None:
|
||||
return None
|
||||
|
||||
json_path = hass.config.path(REGISTRATIONS_FILE)
|
||||
|
||||
registrations = await hass.async_add_executor_job(_load_config, json_path)
|
||||
|
||||
vapid_pub_key = config[ATTR_VAPID_PUB_KEY]
|
||||
vapid_prv_key = config[ATTR_VAPID_PRV_KEY]
|
||||
vapid_email = config[ATTR_VAPID_EMAIL]
|
||||
vapid_pub_key = discovery_info[ATTR_VAPID_PUB_KEY]
|
||||
vapid_prv_key = discovery_info[ATTR_VAPID_PRV_KEY]
|
||||
vapid_email = discovery_info[ATTR_VAPID_EMAIL]
|
||||
|
||||
def websocket_appkey(hass, connection, msg):
|
||||
def websocket_appkey(_hass, connection, msg):
|
||||
connection.send_message(websocket_api.result_message(msg["id"], vapid_pub_key))
|
||||
|
||||
websocket_api.async_register_command(
|
||||
|
|
|
@ -1,4 +1,31 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"vapid_email": "[%key:common::config_flow::data::email%]",
|
||||
"vapid_prv_key": "VAPID private key"
|
||||
},
|
||||
"data_description": {
|
||||
"vapid_email": "Email to use for html5 push notifications.",
|
||||
"vapid_prv_key": "If not specified, one will be automatically generated."
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"unknown": "Unknown error",
|
||||
"invalid_prv_key": "Invalid private key"
|
||||
},
|
||||
"abort": {
|
||||
"invalid_config": "Invalid configuration"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue": {
|
||||
"title": "HTML5 YAML configuration import failed",
|
||||
"description": "Configuring HTML5 push notification using YAML has been deprecated. An automatic import of your existing configuration was attempted, but it failed.\n\nPlease remove the HTML5 push notification YAML configuration from your configuration.yaml file and reconfigure HTML5 push notification again manually."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"dismiss": {
|
||||
"name": "Dismiss",
|
||||
|
|
|
@ -253,6 +253,7 @@ FLOWS = {
|
|||
"homewizard",
|
||||
"homeworks",
|
||||
"honeywell",
|
||||
"html5",
|
||||
"huawei_lte",
|
||||
"hue",
|
||||
"huisbaasje",
|
||||
|
|
|
@ -2633,8 +2633,9 @@
|
|||
"html5": {
|
||||
"name": "HTML5 Push Notifications",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"iot_class": "cloud_push"
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_push",
|
||||
"single_config_entry": true
|
||||
},
|
||||
"huawei_lte": {
|
||||
"name": "Huawei LTE",
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
"""Test the HTML5 config flow."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.html5.const import (
|
||||
ATTR_VAPID_EMAIL,
|
||||
ATTR_VAPID_PRV_KEY,
|
||||
ATTR_VAPID_PUB_KEY,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.html5.issues import (
|
||||
FAILED_IMPORT_TRANSLATION_KEY,
|
||||
SUCCESSFUL_IMPORT_TRANSLATION_KEY,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
|
||||
MOCK_CONF = {
|
||||
ATTR_VAPID_EMAIL: "test@example.com",
|
||||
ATTR_VAPID_PRV_KEY: "h6acSRds8_KR8hT9djD8WucTL06Gfe29XXyZ1KcUjN8",
|
||||
}
|
||||
MOCK_CONF_PUB_KEY = "BIUtPN7Rq_8U7RBEqClZrfZ5dR9zPCfvxYPtLpWtRVZTJEc7lzv2dhzDU6Aw1m29Ao0-UA1Uq6XO9Df8KALBKqA"
|
||||
|
||||
|
||||
async def test_step_user_success(hass: HomeAssistant) -> None:
|
||||
"""Test a successful user config flow."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.html5.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=MOCK_CONF.copy(),
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
ATTR_VAPID_PRV_KEY: MOCK_CONF[ATTR_VAPID_PRV_KEY],
|
||||
ATTR_VAPID_PUB_KEY: MOCK_CONF_PUB_KEY,
|
||||
ATTR_VAPID_EMAIL: MOCK_CONF[ATTR_VAPID_EMAIL],
|
||||
CONF_NAME: DOMAIN,
|
||||
}
|
||||
|
||||
assert mock_setup_entry.call_count == 1
|
||||
|
||||
|
||||
async def test_step_user_success_generate(hass: HomeAssistant) -> None:
|
||||
"""Test a successful user config flow, generating a key pair."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.html5.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
conf = {ATTR_VAPID_EMAIL: MOCK_CONF[ATTR_VAPID_EMAIL]}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][ATTR_VAPID_EMAIL] == MOCK_CONF[ATTR_VAPID_EMAIL]
|
||||
|
||||
assert mock_setup_entry.call_count == 1
|
||||
|
||||
|
||||
async def test_step_user_new_form(hass: HomeAssistant) -> None:
|
||||
"""Test new user input."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.html5.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||
assert mock_setup_entry.call_count == 0
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], MOCK_CONF
|
||||
)
|
||||
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert mock_setup_entry.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key", "value"),
|
||||
[
|
||||
(ATTR_VAPID_PRV_KEY, "invalid"),
|
||||
],
|
||||
)
|
||||
async def test_step_user_form_invalid_key(
|
||||
hass: HomeAssistant, key: str, value: str
|
||||
) -> None:
|
||||
"""Test invalid user input."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.html5.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
bad_conf = MOCK_CONF.copy()
|
||||
bad_conf[key] = value
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=bad_conf
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
||||
assert mock_setup_entry.call_count == 0
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], MOCK_CONF
|
||||
)
|
||||
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert mock_setup_entry.call_count == 1
|
||||
|
||||
|
||||
async def test_step_import_good(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test valid import input."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.html5.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry,
|
||||
):
|
||||
conf = MOCK_CONF.copy()
|
||||
conf[ATTR_VAPID_PUB_KEY] = MOCK_CONF_PUB_KEY
|
||||
conf["random_key"] = "random_value"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
ATTR_VAPID_PRV_KEY: conf[ATTR_VAPID_PRV_KEY],
|
||||
ATTR_VAPID_PUB_KEY: MOCK_CONF_PUB_KEY,
|
||||
ATTR_VAPID_EMAIL: conf[ATTR_VAPID_EMAIL],
|
||||
CONF_NAME: DOMAIN,
|
||||
}
|
||||
|
||||
assert mock_setup_entry.call_count == 1
|
||||
assert len(issue_registry.issues) == 1
|
||||
issue = issue_registry.async_get_issue(
|
||||
HOMEASSISTANT_DOMAIN, f"deprecated_yaml_{DOMAIN}"
|
||||
)
|
||||
assert issue
|
||||
assert issue.translation_key == SUCCESSFUL_IMPORT_TRANSLATION_KEY
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key", "value"),
|
||||
[
|
||||
(ATTR_VAPID_PRV_KEY, "invalid"),
|
||||
],
|
||||
)
|
||||
async def test_step_import_bad(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry, key: str, value: str
|
||||
) -> None:
|
||||
"""Test invalid import input."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.html5.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry,
|
||||
):
|
||||
bad_conf = MOCK_CONF.copy()
|
||||
bad_conf[key] = value
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=bad_conf
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||
assert mock_setup_entry.call_count == 0
|
||||
|
||||
assert len(issue_registry.issues) == 1
|
||||
issue = issue_registry.async_get_issue(DOMAIN, f"deprecated_yaml_{DOMAIN}")
|
||||
assert issue
|
||||
assert issue.translation_key == FAILED_IMPORT_TRANSLATION_KEY
|
|
@ -0,0 +1,44 @@
|
|||
"""Test the HTML5 setup."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
NOTIFY_CONF = {
|
||||
"notify": [
|
||||
{
|
||||
"platform": "html5",
|
||||
"name": "html5",
|
||||
"vapid_pub_key": "BIUtPN7Rq_8U7RBEqClZrfZ5dR9zPCfvxYPtLpWtRVZTJEc7lzv2dhzDU6Aw1m29Ao0-UA1Uq6XO9Df8KALBKqA",
|
||||
"vapid_prv_key": "h6acSRds8_KR8hT9djD8WucTL06Gfe29XXyZ1KcUjN8",
|
||||
"vapid_email": "test@example.com",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
async def test_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test setup of a good config entry."""
|
||||
config_entry = MockConfigEntry(domain="html5", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, "html5", {})
|
||||
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
|
||||
async def test_setup_entry_issue(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test setup of an imported config entry with deprecated YAML."""
|
||||
config_entry = MockConfigEntry(domain="html5", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, "notify", NOTIFY_CONF)
|
||||
assert await async_setup_component(hass, "html5", NOTIFY_CONF)
|
||||
|
||||
assert len(issue_registry.issues) == 1
|
|
@ -94,7 +94,7 @@ async def test_get_service_with_no_json(hass: HomeAssistant) -> None:
|
|||
await async_setup_component(hass, "http", {})
|
||||
m = mock_open()
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
|
||||
assert service is not None
|
||||
|
||||
|
@ -109,7 +109,7 @@ async def test_dismissing_message(mock_wp, hass: HomeAssistant) -> None:
|
|||
|
||||
m = mock_open(read_data=json.dumps(data))
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
service.hass = hass
|
||||
|
||||
assert service is not None
|
||||
|
@ -138,7 +138,7 @@ async def test_sending_message(mock_wp, hass: HomeAssistant) -> None:
|
|||
|
||||
m = mock_open(read_data=json.dumps(data))
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
service.hass = hass
|
||||
|
||||
assert service is not None
|
||||
|
@ -169,7 +169,7 @@ async def test_fcm_key_include(mock_wp, hass: HomeAssistant) -> None:
|
|||
|
||||
m = mock_open(read_data=json.dumps(data))
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
service.hass = hass
|
||||
|
||||
assert service is not None
|
||||
|
@ -194,7 +194,7 @@ async def test_fcm_send_with_unknown_priority(mock_wp, hass: HomeAssistant) -> N
|
|||
|
||||
m = mock_open(read_data=json.dumps(data))
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
service.hass = hass
|
||||
|
||||
assert service is not None
|
||||
|
@ -219,7 +219,7 @@ async def test_fcm_no_targets(mock_wp, hass: HomeAssistant) -> None:
|
|||
|
||||
m = mock_open(read_data=json.dumps(data))
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
service.hass = hass
|
||||
|
||||
assert service is not None
|
||||
|
@ -244,7 +244,7 @@ async def test_fcm_additional_data(mock_wp, hass: HomeAssistant) -> None:
|
|||
|
||||
m = mock_open(read_data=json.dumps(data))
|
||||
with patch("homeassistant.util.json.open", m, create=True):
|
||||
service = await html5.async_get_service(hass, VAPID_CONF)
|
||||
service = await html5.async_get_service(hass, {}, VAPID_CONF)
|
||||
service.hass = hass
|
||||
|
||||
assert service is not None
|
||||
|
@ -479,7 +479,7 @@ async def test_callback_view_with_jwt(
|
|||
mock_wp().send().status_code = 201
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -516,7 +516,7 @@ async def test_send_fcm_without_targets(
|
|||
mock_wp().send().status_code = 201
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -541,7 +541,7 @@ async def test_send_fcm_expired(
|
|||
mock_wp().send().status_code = 410
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -566,7 +566,7 @@ async def test_send_fcm_expired_save_fails(
|
|||
mock_wp().send().status_code = 410
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue