Address Google mail late review (#86847)

This commit is contained in:
Robert Hillis 2023-01-30 08:18:56 -05:00 committed by GitHub
parent 3b5fd4bd06
commit 032a37b121
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 88 additions and 33 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -7,5 +7,5 @@
"requirements": ["google-api-python-client==2.71.0"],
"codeowners": ["@tkdrob"],
"iot_class": "cloud_polling",
"integration_type": "device"
"integration_type": "service"
}

View file

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

View file

@ -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%]"

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
{
"emailAddress": "example2@gmail.com",
"messagesTotal": 35308,
"threadsTotal": 33901,
"historyId": "4178212"
}

View file

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

View file

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

View file

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