mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 02:03:27 +01:00
Refactor HTML5 integration to use aiohttp instead of requests (#163202)
This commit is contained in:
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
import logging
|
||||
@@ -13,7 +12,7 @@ from typing import TYPE_CHECKING, Any, NotRequired, TypedDict, cast
|
||||
from urllib.parse import urlparse
|
||||
import uuid
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp import ClientSession, web
|
||||
from aiohttp.hdrs import AUTHORIZATION
|
||||
import jwt
|
||||
from py_vapid import Vapid
|
||||
@@ -35,6 +34,7 @@ from homeassistant.const import ATTR_NAME, URL_ROOT
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import ensure_unique_string
|
||||
@@ -203,8 +203,9 @@ async def async_get_service(
|
||||
hass.http.register_view(HTML5PushRegistrationView(registrations, json_path))
|
||||
hass.http.register_view(HTML5PushCallbackView(registrations))
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
return HTML5NotificationService(
|
||||
hass, vapid_prv_key, vapid_email, registrations, json_path
|
||||
hass, session, vapid_prv_key, vapid_email, registrations, json_path
|
||||
)
|
||||
|
||||
|
||||
@@ -420,12 +421,14 @@ class HTML5NotificationService(BaseNotificationService):
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
session: ClientSession,
|
||||
vapid_prv: str,
|
||||
vapid_email: str,
|
||||
registrations: dict[str, Registration],
|
||||
json_path: str,
|
||||
) -> None:
|
||||
"""Initialize the service."""
|
||||
self.session = session
|
||||
self._vapid_prv = vapid_prv
|
||||
self._vapid_email = vapid_email
|
||||
self.registrations = registrations
|
||||
@@ -456,22 +459,18 @@ class HTML5NotificationService(BaseNotificationService):
|
||||
"""Return a dictionary of registered targets."""
|
||||
return {registration: registration for registration in self.registrations}
|
||||
|
||||
def dismiss(self, **kwargs: Any) -> None:
|
||||
"""Dismisses a notification."""
|
||||
data: dict[str, Any] | None = kwargs.get(ATTR_DATA)
|
||||
tag: str = data.get(ATTR_TAG, "") if data else ""
|
||||
payload = {ATTR_TAG: tag, ATTR_DISMISS: True, ATTR_DATA: {}}
|
||||
|
||||
self._push_message(payload, **kwargs)
|
||||
|
||||
async def async_dismiss(self, **kwargs) -> None:
|
||||
async def async_dismiss(self, **kwargs: Any) -> None:
|
||||
"""Dismisses a notification.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
await self.hass.async_add_executor_job(partial(self.dismiss, **kwargs))
|
||||
data: dict[str, Any] | None = kwargs.get(ATTR_DATA)
|
||||
tag: str = data.get(ATTR_TAG, "") if data else ""
|
||||
payload = {ATTR_TAG: tag, ATTR_DISMISS: True, ATTR_DATA: {}}
|
||||
|
||||
def send_message(self, message: str = "", **kwargs: Any) -> None:
|
||||
await self._push_message(payload, **kwargs)
|
||||
|
||||
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
|
||||
"""Send a message to a user."""
|
||||
tag = str(uuid.uuid4())
|
||||
payload: dict[str, Any] = {
|
||||
@@ -503,9 +502,9 @@ class HTML5NotificationService(BaseNotificationService):
|
||||
):
|
||||
payload[ATTR_DATA][ATTR_URL] = URL_ROOT
|
||||
|
||||
self._push_message(payload, **kwargs)
|
||||
await self._push_message(payload, **kwargs)
|
||||
|
||||
def _push_message(self, payload: dict[str, Any], **kwargs: Any) -> None:
|
||||
async def _push_message(self, payload: dict[str, Any], **kwargs: Any) -> None:
|
||||
"""Send the message."""
|
||||
|
||||
timestamp = int(time.time())
|
||||
@@ -535,7 +534,9 @@ class HTML5NotificationService(BaseNotificationService):
|
||||
subscription["keys"]["auth"],
|
||||
)
|
||||
|
||||
webpusher = WebPusher(cast(dict[str, Any], info["subscription"]))
|
||||
webpusher = WebPusher(
|
||||
cast(dict[str, Any], info["subscription"]), aiohttp_session=self.session
|
||||
)
|
||||
|
||||
endpoint = urlparse(subscription["endpoint"])
|
||||
vapid_claims = {
|
||||
@@ -545,28 +546,31 @@ class HTML5NotificationService(BaseNotificationService):
|
||||
}
|
||||
vapid_headers = Vapid.from_string(self._vapid_prv).sign(vapid_claims)
|
||||
vapid_headers.update({"urgency": priority, "priority": priority})
|
||||
response = webpusher.send(
|
||||
|
||||
response = await webpusher.send_async(
|
||||
data=json.dumps(payload), headers=vapid_headers, ttl=ttl
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert not isinstance(response, str)
|
||||
|
||||
if response.status_code == HTTPStatus.GONE:
|
||||
if response.status == HTTPStatus.GONE:
|
||||
_LOGGER.info("Notification channel has expired")
|
||||
reg = self.registrations.pop(target)
|
||||
try:
|
||||
save_json(self.registrations_json_path, self.registrations)
|
||||
await self.hass.async_add_executor_job(
|
||||
save_json, self.registrations_json_path, self.registrations
|
||||
)
|
||||
except HomeAssistantError:
|
||||
self.registrations[target] = reg
|
||||
_LOGGER.error("Error saving registration")
|
||||
else:
|
||||
_LOGGER.info("Configuration saved")
|
||||
elif response.status_code >= HTTPStatus.BAD_REQUEST:
|
||||
elif response.status >= HTTPStatus.BAD_REQUEST:
|
||||
_LOGGER.error(
|
||||
"There was an issue sending the notification %s: %s",
|
||||
response.status_code,
|
||||
response.text,
|
||||
response.status,
|
||||
await response.text(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Common fixtures for html5 integration."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohttp import ClientResponse
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.html5.const import (
|
||||
@@ -45,3 +46,58 @@ def mock_load_config() -> Generator[MagicMock]:
|
||||
"homeassistant.components.html5.notify._load_config", return_value={}
|
||||
) as mock_load_config:
|
||||
yield mock_load_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_wp() -> Generator[AsyncMock]:
|
||||
"""Mock WebPusher."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.html5.notify.WebPusher", autospec=True
|
||||
) as mock_client,
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.cls = mock_client
|
||||
client.send_async.return_value = AsyncMock(spec=ClientResponse, status=201)
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_jwt() -> Generator[MagicMock]:
|
||||
"""Mock JWT."""
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.html5.notify.jwt") as mock_client,
|
||||
):
|
||||
mock_client.encode.return_value = "JWT"
|
||||
mock_client.decode.return_value = {"target": "device"}
|
||||
yield mock_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_uuid() -> Generator[MagicMock]:
|
||||
"""Mock UUID."""
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.html5.notify.uuid") as mock_client,
|
||||
):
|
||||
mock_client.uuid4.return_value = "12345678-1234-5678-1234-567812345678"
|
||||
yield mock_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_vapid() -> Generator[MagicMock]:
|
||||
"""Mock VAPID headers."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.html5.notify.Vapid", autospec=True
|
||||
) as mock_client,
|
||||
):
|
||||
mock_client.from_string.return_value.sign.return_value = {
|
||||
"Authorization": "vapid t=signed!!!",
|
||||
"urgency": "normal",
|
||||
"priority": "normal",
|
||||
}
|
||||
yield mock_client
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
from unittest.mock import MagicMock, mock_open, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, mock_open, patch
|
||||
|
||||
from aiohttp.hdrs import AUTHORIZATION
|
||||
import pytest
|
||||
@@ -71,6 +71,12 @@ SUBSCRIPTION_5 = {
|
||||
REGISTER_URL = "/api/notify.html5"
|
||||
PUBLISH_URL = "/api/notify.html5/callback"
|
||||
|
||||
VAPID_HEADERS = {
|
||||
"Authorization": "vapid t=signed!!!",
|
||||
"urgency": "normal",
|
||||
"priority": "normal",
|
||||
}
|
||||
|
||||
|
||||
async def test_get_service_with_no_json(hass: HomeAssistant) -> None:
|
||||
"""Test empty json file."""
|
||||
@@ -82,11 +88,11 @@ async def test_get_service_with_no_json(hass: HomeAssistant) -> None:
|
||||
assert service is not None
|
||||
|
||||
|
||||
@patch("homeassistant.components.html5.notify.WebPusher")
|
||||
async def test_dismissing_message(mock_wp, hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_dismissing_message(mock_wp: AsyncMock, hass: HomeAssistant) -> None:
|
||||
"""Test dismissing message."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
mock_wp().send().status_code = 201
|
||||
|
||||
data = {"device": SUBSCRIPTION_1}
|
||||
|
||||
@@ -99,23 +105,18 @@ async def test_dismissing_message(mock_wp, hass: HomeAssistant) -> None:
|
||||
|
||||
await service.async_dismiss(target=["device", "non_existing"], data={"tag": "test"})
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"]
|
||||
|
||||
# Call to send
|
||||
payload = json.loads(mock_wp.mock_calls[3][2]["data"])
|
||||
|
||||
assert payload["dismiss"] is True
|
||||
assert payload["tag"] == "test"
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"tag": "test", "dismiss": true, "data": {"jwt": "JWT"}, "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
|
||||
|
||||
@patch("homeassistant.components.html5.notify.WebPusher")
|
||||
async def test_sending_message(mock_wp, hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_sending_message(mock_wp: AsyncMock, hass: HomeAssistant) -> None:
|
||||
"""Test sending message."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
mock_wp().send().status_code = 201
|
||||
|
||||
data = {"device": SUBSCRIPTION_1}
|
||||
|
||||
@@ -130,23 +131,21 @@ async def test_sending_message(mock_wp, hass: HomeAssistant) -> None:
|
||||
"Hello", target=["device", "non_existing"], data={"icon": "beer.png"}
|
||||
)
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"url": "/", "jwt": "JWT"}, "icon": "beer.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"]
|
||||
|
||||
# Call to send
|
||||
payload = json.loads(mock_wp.mock_calls[3][2]["data"])
|
||||
|
||||
assert payload["body"] == "Hello"
|
||||
assert payload["icon"] == "beer.png"
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_1["subscription"]
|
||||
|
||||
|
||||
@patch("homeassistant.components.html5.notify.WebPusher")
|
||||
async def test_fcm_key_include(mock_wp, hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_fcm_key_include(mock_wp: AsyncMock, hass: HomeAssistant) -> None:
|
||||
"""Test if the FCM header is included."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
mock_wp().send().status_code = 201
|
||||
|
||||
data = {"chrome": SUBSCRIPTION_5}
|
||||
|
||||
@@ -159,19 +158,23 @@ async def test_fcm_key_include(mock_wp, hass: HomeAssistant) -> None:
|
||||
|
||||
await service.async_send_message("Hello", target=["chrome"])
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"url": "/", "jwt": "JWT"}, "icon": "/static/icons/favicon-192x192.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
# Get the keys passed to the WebPusher's send method
|
||||
assert mock_wp.mock_calls[3][2]["headers"]["Authorization"] is not None
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
|
||||
@patch("homeassistant.components.html5.notify.WebPusher")
|
||||
async def test_fcm_send_with_unknown_priority(mock_wp, hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_fcm_send_with_unknown_priority(
|
||||
mock_wp: AsyncMock, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Test if the gcm_key is only included for GCM endpoints."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
mock_wp().send().status_code = 201
|
||||
|
||||
data = {"chrome": SUBSCRIPTION_5}
|
||||
|
||||
@@ -184,19 +187,20 @@ async def test_fcm_send_with_unknown_priority(mock_wp, hass: HomeAssistant) -> N
|
||||
|
||||
await service.async_send_message("Hello", target=["chrome"], priority="undefined")
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"url": "/", "jwt": "JWT"}, "icon": "/static/icons/favicon-192x192.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
# Get the keys passed to the WebPusher's send method
|
||||
assert mock_wp.mock_calls[3][2]["headers"]["priority"] == "normal"
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
|
||||
@patch("homeassistant.components.html5.notify.WebPusher")
|
||||
async def test_fcm_no_targets(mock_wp, hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_fcm_no_targets(mock_wp: AsyncMock, hass: HomeAssistant) -> None:
|
||||
"""Test if the gcm_key is only included for GCM endpoints."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
mock_wp().send().status_code = 201
|
||||
|
||||
data = {"chrome": SUBSCRIPTION_5}
|
||||
|
||||
@@ -209,19 +213,20 @@ async def test_fcm_no_targets(mock_wp, hass: HomeAssistant) -> None:
|
||||
|
||||
await service.async_send_message("Hello")
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"url": "/", "jwt": "JWT"}, "icon": "/static/icons/favicon-192x192.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
# Get the keys passed to the WebPusher's send method
|
||||
assert mock_wp.mock_calls[3][2]["headers"]["priority"] == "normal"
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
|
||||
@patch("homeassistant.components.html5.notify.WebPusher")
|
||||
async def test_fcm_additional_data(mock_wp, hass: HomeAssistant) -> None:
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_fcm_additional_data(mock_wp: AsyncMock, hass: HomeAssistant) -> None:
|
||||
"""Test if the gcm_key is only included for GCM endpoints."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
mock_wp().send().status_code = 201
|
||||
|
||||
data = {"chrome": SUBSCRIPTION_5}
|
||||
|
||||
@@ -234,12 +239,13 @@ async def test_fcm_additional_data(mock_wp, hass: HomeAssistant) -> None:
|
||||
|
||||
await service.async_send_message("Hello", data={"mykey": "myvalue"})
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"mykey": "myvalue", "url": "/", "jwt": "JWT"}, "icon": "/static/icons/favicon-192x192.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
# Get the keys passed to the WebPusher's send method
|
||||
assert mock_wp.mock_calls[3][2]["headers"]["priority"] == "normal"
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("load_config")
|
||||
@@ -581,11 +587,14 @@ async def test_callback_view_no_jwt(
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_callback_view_with_jwt(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
config_entry: MockConfigEntry,
|
||||
load_config: MagicMock,
|
||||
mock_wp: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that the notification callback view works with JWT."""
|
||||
load_config.return_value = {"device": SUBSCRIPTION_1}
|
||||
@@ -599,27 +608,22 @@ async def test_callback_view_with_jwt(
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
with patch("homeassistant.components.html5.notify.WebPusher") as mock_wp:
|
||||
mock_wp().send().status_code = 201
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"url": "/", "jwt": "JWT"}, "icon": "beer.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"]
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_1["subscription"]
|
||||
|
||||
# Call to send
|
||||
push_payload = json.loads(mock_wp.mock_calls[3][2]["data"])
|
||||
|
||||
assert push_payload["body"] == "Hello"
|
||||
assert push_payload["icon"] == "beer.png"
|
||||
|
||||
bearer_token = f"Bearer {push_payload['data']['jwt']}"
|
||||
bearer_token = "Bearer JWT"
|
||||
|
||||
resp = await client.post(
|
||||
PUBLISH_URL, json={"type": "push"}, headers={AUTHORIZATION: bearer_token}
|
||||
@@ -630,10 +634,13 @@ async def test_callback_view_with_jwt(
|
||||
assert body == {"event": "push", "status": "ok"}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_send_fcm_without_targets(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
load_config: MagicMock,
|
||||
mock_wp: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that the notification is send with FCM without targets."""
|
||||
load_config.return_value = {"device": SUBSCRIPTION_5}
|
||||
@@ -645,25 +652,29 @@ async def test_send_fcm_without_targets(
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with patch("homeassistant.components.html5.notify.WebPusher") as mock_wp:
|
||||
mock_wp().send().status_code = 201
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_wp.mock_calls) == 4
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"html5",
|
||||
{"message": "Hello", "target": ["device"], "data": {"icon": "beer.png"}},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_wp.send_async.assert_awaited_once_with(
|
||||
data='{"badge": "/static/images/notification-badge.png", "body": "Hello", "data": {"url": "/", "jwt": "JWT"}, "icon": "beer.png", "tag": "12345678-1234-5678-1234-567812345678", "title": "Home Assistant", "timestamp": 1234567890000}',
|
||||
headers=VAPID_HEADERS,
|
||||
ttl=86400,
|
||||
)
|
||||
# WebPusher constructor
|
||||
assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"]
|
||||
assert mock_wp.cls.call_args[0][0] == SUBSCRIPTION_5["subscription"]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_send_fcm_expired(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
load_config: MagicMock,
|
||||
mock_wp: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that the FCM target is removed when expired."""
|
||||
load_config.return_value = {"device": SUBSCRIPTION_5}
|
||||
@@ -674,12 +685,10 @@ async def test_send_fcm_expired(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
mock_wp.send_async.return_value.status = 410
|
||||
with (
|
||||
patch("homeassistant.components.html5.notify.WebPusher") as mock_wp,
|
||||
patch("homeassistant.components.html5.notify.save_json") as mock_save,
|
||||
):
|
||||
mock_wp().send().status_code = 410
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"html5",
|
||||
@@ -690,11 +699,14 @@ async def test_send_fcm_expired(
|
||||
mock_save.assert_called_once_with(hass.config.path(html5.REGISTRATIONS_FILE), {})
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_jwt", "mock_vapid", "mock_uuid")
|
||||
@pytest.mark.freeze_time("2009-02-13T23:31:30.000Z")
|
||||
async def test_send_fcm_expired_save_fails(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
load_config: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
mock_wp: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that the FCM target remains after expiry if save_json fails."""
|
||||
load_config.return_value = {"device": SUBSCRIPTION_5}
|
||||
@@ -705,16 +717,13 @@ async def test_send_fcm_expired_save_fails(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
mock_wp.send_async.return_value.status = 410
|
||||
with (
|
||||
patch("homeassistant.components.html5.notify.WebPusher") as mock_wp,
|
||||
patch(
|
||||
"homeassistant.components.html5.notify.save_json",
|
||||
side_effect=HomeAssistantError(),
|
||||
),
|
||||
):
|
||||
mock_wp().send().status_code = 410
|
||||
|
||||
await hass.services.async_call(
|
||||
"notify",
|
||||
"html5",
|
||||
|
||||
Reference in New Issue
Block a user