Address Google mail late review (#86847)
This commit is contained in:
parent
3b5fd4bd06
commit
032a37b121
14 changed files with 88 additions and 33 deletions
|
@ -13,14 +13,22 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
|||
OAuth2Session,
|
||||
async_get_config_entry_implementation,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import DATA_AUTH, DOMAIN
|
||||
from .const import DATA_AUTH, DATA_HASS_CONFIG, DOMAIN
|
||||
from .services import async_setup_services
|
||||
|
||||
PLATFORMS = [Platform.NOTIFY, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Google Mail platform."""
|
||||
hass.data.setdefault(DOMAIN, {})[DATA_HASS_CONFIG] = config
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Google Mail from a config entry."""
|
||||
implementation = await async_get_config_entry_implementation(hass, entry)
|
||||
|
@ -36,7 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
raise ConfigEntryNotReady from err
|
||||
except ClientError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = auth
|
||||
hass.data[DOMAIN][entry.entry_id] = auth
|
||||
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
|
@ -44,7 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
Platform.NOTIFY,
|
||||
DOMAIN,
|
||||
{DATA_AUTH: auth, CONF_NAME: entry.title},
|
||||
{},
|
||||
hass.data[DOMAIN][DATA_HASS_CONFIG],
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from google.oauth2.credentials import Credentials
|
||||
from googleapiclient.discovery import build
|
||||
|
@ -57,23 +57,29 @@ class OAuth2FlowHandler(
|
|||
|
||||
async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
|
||||
"""Create an entry for the flow, or update existing entry."""
|
||||
if self.reauth_entry:
|
||||
self.hass.config_entries.async_update_entry(self.reauth_entry, data=data)
|
||||
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
credentials = Credentials(data[CONF_TOKEN][CONF_ACCESS_TOKEN])
|
||||
|
||||
def _get_profile() -> dict[str, Any]:
|
||||
def _get_profile() -> str:
|
||||
"""Get profile from inside the executor."""
|
||||
users = build( # pylint: disable=no-member
|
||||
"gmail", "v1", credentials=credentials
|
||||
).users()
|
||||
return users.getProfile(userId="me").execute()
|
||||
return users.getProfile(userId="me").execute()["emailAddress"]
|
||||
|
||||
email = (await self.hass.async_add_executor_job(_get_profile))["emailAddress"]
|
||||
credentials = Credentials(data[CONF_TOKEN][CONF_ACCESS_TOKEN])
|
||||
email = await self.hass.async_add_executor_job(_get_profile)
|
||||
|
||||
await self.async_set_unique_id(email)
|
||||
self._abort_if_unique_id_configured()
|
||||
if not self.reauth_entry:
|
||||
await self.async_set_unique_id(email)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(title=email, data=data)
|
||||
return self.async_create_entry(title=email, data=data)
|
||||
|
||||
if self.reauth_entry.unique_id == email:
|
||||
self.hass.config_entries.async_update_entry(self.reauth_entry, data=data)
|
||||
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
return self.async_abort(
|
||||
reason="wrong_account",
|
||||
description_placeholders={"email": cast(str, self.reauth_entry.unique_id)},
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@ ATTR_START = "start"
|
|||
ATTR_TITLE = "title"
|
||||
|
||||
DATA_AUTH = "auth"
|
||||
DATA_HASS_CONFIG = "hass_config"
|
||||
DEFAULT_ACCESS = [
|
||||
"https://www.googleapis.com/auth/gmail.compose",
|
||||
"https://www.googleapis.com/auth/gmail.settings.basic",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Entity representing a Google Mail account."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
|
@ -24,6 +25,7 @@ class GoogleMailEntity(Entity):
|
|||
f"{auth.oauth_session.config_entry.entry_id}_{description.key}"
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, auth.oauth_session.config_entry.entry_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=auth.oauth_session.config_entry.unique_id,
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"requirements": ["google-api-python-client==2.71.0"],
|
||||
"codeowners": ["@tkdrob"],
|
||||
"iot_class": "cloud_polling",
|
||||
"integration_type": "device"
|
||||
"integration_type": "service"
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@ from __future__ import annotations
|
|||
|
||||
import base64
|
||||
from email.message import EmailMessage
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
from googleapiclient.http import HttpRequest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_DATA,
|
||||
|
@ -27,9 +26,9 @@ async def async_get_service(
|
|||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> GMailNotificationService:
|
||||
) -> GMailNotificationService | None:
|
||||
"""Get the notification service."""
|
||||
return GMailNotificationService(cast(DiscoveryInfoType, discovery_info))
|
||||
return GMailNotificationService(discovery_info) if discovery_info else None
|
||||
|
||||
|
||||
class GMailNotificationService(BaseNotificationService):
|
||||
|
@ -61,6 +60,6 @@ class GMailNotificationService(BaseNotificationService):
|
|||
msg = users.drafts().create(userId=email["From"], body={ATTR_MESSAGE: body})
|
||||
else:
|
||||
if not to_addrs:
|
||||
raise vol.Invalid("recipient address required")
|
||||
raise ValueError("recipient address required")
|
||||
msg = users.messages().send(userId=email["From"], body=body)
|
||||
await self.hass.async_add_executor_job(msg.execute)
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||
"wrong_account": "Wrong account: Please authenticate with {email}."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"oauth_error": "Received invalid token data.",
|
||||
"reauth_successful": "Re-authentication was successful",
|
||||
"timeout_connect": "Timeout establishing connection",
|
||||
"unknown": "Unexpected error"
|
||||
"unknown": "Unexpected error",
|
||||
"wrong_account": "Wrong account: Please authenticate with {email}."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Successfully authenticated"
|
||||
|
|
|
@ -2007,7 +2007,7 @@
|
|||
"name": "Google Domains"
|
||||
},
|
||||
"google_mail": {
|
||||
"integration_type": "device",
|
||||
"integration_type": "service",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling",
|
||||
"name": "Google Mail"
|
||||
|
|
|
@ -115,6 +115,6 @@ async def mock_setup_integration(
|
|||
),
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return func
|
||||
|
|
6
tests/components/google_mail/fixtures/get_profile_2.json
Normal file
6
tests/components/google_mail/fixtures/get_profile_2.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"emailAddress": "example2@gmail.com",
|
||||
"messagesTotal": 35308,
|
||||
"threadsTotal": 33901,
|
||||
"historyId": "4178212"
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from httplib2 import Response
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.google_mail.const import DOMAIN
|
||||
|
@ -68,14 +69,36 @@ async def test_full_flow(
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture,abort_reason,placeholders,calls,access_token",
|
||||
[
|
||||
("get_profile", "reauth_successful", None, 1, "updated-access-token"),
|
||||
(
|
||||
"get_profile_2",
|
||||
"wrong_account",
|
||||
{"email": "example@gmail.com"},
|
||||
0,
|
||||
"mock-access-token",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_reauth(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
current_request_with_host,
|
||||
config_entry: MockConfigEntry,
|
||||
fixture: str,
|
||||
abort_reason: str,
|
||||
placeholders: dict[str, str],
|
||||
calls: int,
|
||||
access_token: str,
|
||||
) -> None:
|
||||
"""Test the reauthentication case updates the existing config entry."""
|
||||
"""Test the re-authentication case updates the correct config entry.
|
||||
|
||||
Make sure we abort if the user selects the
|
||||
wrong account on the consent screen.
|
||||
"""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
config_entry.async_start_reauth(hass)
|
||||
|
@ -118,19 +141,26 @@ async def test_reauth(
|
|||
|
||||
with patch(
|
||||
"homeassistant.components.google_mail.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
) as mock_setup, patch(
|
||||
"httplib2.Http.request",
|
||||
return_value=(
|
||||
Response({}),
|
||||
bytes(load_fixture(f"google_mail/{fixture}.json"), encoding="UTF-8"),
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
|
||||
assert result.get("type") == "abort"
|
||||
assert result.get("reason") == "reauth_successful"
|
||||
assert result["reason"] == abort_reason
|
||||
assert result["description_placeholders"] == placeholders
|
||||
assert len(mock_setup.mock_calls) == calls
|
||||
|
||||
assert config_entry.unique_id == TITLE
|
||||
assert "token" in config_entry.data
|
||||
# Verify access token is refreshed
|
||||
assert config_entry.data["token"].get("access_token") == "updated-access-token"
|
||||
assert config_entry.data["token"].get("access_token") == access_token
|
||||
assert config_entry.data["token"].get("refresh_token") == "mock-refresh-token"
|
||||
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ async def test_setup_success(
|
|||
await hass.config_entries.async_unload(entries[0].entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not len(hass.services.async_services().get(DOMAIN, {}))
|
||||
assert not hass.services.async_services().get(DOMAIN)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expires_at", [time.time() - 3600], ids=["expired"])
|
||||
|
@ -125,6 +125,7 @@ async def test_device_info(
|
|||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
device = device_registry.async_get_device({(DOMAIN, entry.entry_id)})
|
||||
|
||||
assert device.entry_type is dr.DeviceEntryType.SERVICE
|
||||
assert device.identifiers == {(DOMAIN, entry.entry_id)}
|
||||
assert device.manufacturer == "Google, Inc."
|
||||
assert device.name == "example@gmail.com"
|
||||
|
|
|
@ -52,7 +52,7 @@ async def test_notify_voluptuous_error(
|
|||
"""Test voluptuous error thrown when drafting email."""
|
||||
await setup_integration()
|
||||
|
||||
with pytest.raises(Invalid) as ex:
|
||||
with pytest.raises(ValueError) as ex:
|
||||
await hass.services.async_call(
|
||||
NOTIFY_DOMAIN,
|
||||
"example_gmail_com",
|
||||
|
|
Loading…
Add table
Reference in a new issue