parent
77debcbe8b
commit
355e80aa56
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
|
@ -12,11 +11,17 @@ from homeassistant.helpers import config_entry_oauth2_flow
|
|||
|
||||
from . import api
|
||||
from .const import DOMAIN
|
||||
from .exceptions import GoogleTasksApiError
|
||||
from .types import GoogleTasksConfigEntry, GoogleTasksData
|
||||
|
||||
__all__ = [
|
||||
"DOMAIN",
|
||||
]
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.TODO]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: GoogleTasksConfigEntry) -> bool:
|
||||
"""Set up Google Tasks from a config entry."""
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
|
@ -36,16 +41,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
except ClientError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = auth
|
||||
try:
|
||||
task_lists = await auth.list_task_lists()
|
||||
except GoogleTasksApiError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
entry.runtime_data = GoogleTasksData(auth, task_lists)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: GoogleTasksConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
|
|
@ -11,15 +11,13 @@ from homeassistant.components.todo import (
|
|||
TodoListEntity,
|
||||
TodoListEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TaskUpdateCoordinator
|
||||
from .types import GoogleTasksConfigEntry
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=15)
|
||||
|
||||
|
@ -69,20 +67,20 @@ def _convert_api_item(item: dict[str, str]) -> TodoItem:
|
|||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: GoogleTasksConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Google Tasks todo platform."""
|
||||
api: AsyncConfigEntryAuth = hass.data[DOMAIN][entry.entry_id]
|
||||
task_lists = await api.list_task_lists()
|
||||
async_add_entities(
|
||||
(
|
||||
GoogleTaskTodoListEntity(
|
||||
TaskUpdateCoordinator(hass, api, task_list["id"]),
|
||||
TaskUpdateCoordinator(hass, entry.runtime_data.api, task_list["id"]),
|
||||
task_list["title"],
|
||||
entry.entry_id,
|
||||
task_list["id"],
|
||||
)
|
||||
for task_list in task_lists
|
||||
for task_list in entry.runtime_data.task_lists
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
"""Types for the Google Tasks integration."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
|
||||
|
||||
@dataclass
|
||||
class GoogleTasksData:
|
||||
"""Class to hold Google Tasks data."""
|
||||
|
||||
api: AsyncConfigEntryAuth
|
||||
task_lists: list[dict[str, Any]]
|
||||
|
||||
|
||||
type GoogleTasksConfigEntry = ConfigEntry[GoogleTasksData]
|
|
@ -1,10 +1,12 @@
|
|||
"""Test fixtures for Google Tasks."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
import json
|
||||
import time
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from httplib2 import Response
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.application_credentials import (
|
||||
|
@ -24,6 +26,14 @@ FAKE_ACCESS_TOKEN = "some-access-token"
|
|||
FAKE_REFRESH_TOKEN = "some-refresh-token"
|
||||
FAKE_AUTH_IMPL = "conftest-imported-cred"
|
||||
|
||||
TASK_LIST = {
|
||||
"id": "task-list-id-1",
|
||||
"title": "My tasks",
|
||||
}
|
||||
LIST_TASK_LIST_RESPONSE = {
|
||||
"items": [TASK_LIST],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
|
@ -89,3 +99,31 @@ async def mock_integration_setup(
|
|||
return result
|
||||
|
||||
return run
|
||||
|
||||
|
||||
@pytest.fixture(name="api_responses")
|
||||
def mock_api_responses() -> list[dict | list]:
|
||||
"""Fixture forcreate_response_object API responses to return during test."""
|
||||
return []
|
||||
|
||||
|
||||
def create_response_object(api_response: dict | list) -> tuple[Response, bytes]:
|
||||
"""Create an http response."""
|
||||
return (
|
||||
Response({"Content-Type": "application/json"}),
|
||||
json.dumps(api_response).encode(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="response_handler")
|
||||
def mock_response_handler(api_responses: list[dict | list]) -> list:
|
||||
"""Create a mock http2lib response handler."""
|
||||
return [create_response_object(api_response) for api_response in api_responses]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_http_response(response_handler: list | Callable) -> Mock:
|
||||
"""Fixture to fake out http2lib responses."""
|
||||
|
||||
with patch("httplib2.Http.request", side_effect=response_handler) as mock_response:
|
||||
yield mock_response
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
from collections.abc import Awaitable, Callable
|
||||
import http
|
||||
from http import HTTPStatus
|
||||
import time
|
||||
from unittest.mock import Mock
|
||||
|
||||
from httplib2 import Response
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.google_tasks import DOMAIN
|
||||
|
@ -11,15 +14,19 @@ from homeassistant.components.google_tasks.const import OAUTH2_TOKEN
|
|||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import LIST_TASK_LIST_RESPONSE
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
@pytest.mark.parametrize("api_responses", [[LIST_TASK_LIST_RESPONSE]])
|
||||
async def test_setup(
|
||||
hass: HomeAssistant,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
setup_credentials: None,
|
||||
mock_http_response: Mock,
|
||||
) -> None:
|
||||
"""Test successful setup and unload."""
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
@ -35,12 +42,14 @@ async def test_setup(
|
|||
|
||||
|
||||
@pytest.mark.parametrize("expires_at", [time.time() - 3600], ids=["expired"])
|
||||
@pytest.mark.parametrize("api_responses", [[LIST_TASK_LIST_RESPONSE]])
|
||||
async def test_expired_token_refresh_success(
|
||||
hass: HomeAssistant,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
config_entry: MockConfigEntry,
|
||||
setup_credentials: None,
|
||||
mock_http_response: Mock,
|
||||
) -> None:
|
||||
"""Test expired token is refreshed."""
|
||||
|
||||
|
@ -98,3 +107,22 @@ async def test_expired_token_refresh_failure(
|
|||
await integration_setup()
|
||||
|
||||
assert config_entry.state is expected_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"response_handler",
|
||||
[
|
||||
([(Response({"status": HTTPStatus.INTERNAL_SERVER_ERROR}), b"")]),
|
||||
],
|
||||
)
|
||||
async def test_setup_error(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
mock_http_response: Mock,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test an error returned by the server when setting up the platform."""
|
||||
|
||||
assert not await integration_setup()
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
|
|
@ -4,7 +4,7 @@ from collections.abc import Awaitable, Callable
|
|||
from http import HTTPStatus
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock
|
||||
|
||||
from httplib2 import Response
|
||||
import pytest
|
||||
|
@ -23,16 +23,11 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .conftest import LIST_TASK_LIST_RESPONSE, create_response_object
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
ENTITY_ID = "todo.my_tasks"
|
||||
ITEM = {
|
||||
"id": "task-list-id-1",
|
||||
"title": "My tasks",
|
||||
}
|
||||
LIST_TASK_LIST_RESPONSE = {
|
||||
"items": [ITEM],
|
||||
}
|
||||
EMPTY_RESPONSE = {}
|
||||
LIST_TASKS_RESPONSE = {
|
||||
"items": [],
|
||||
|
@ -149,20 +144,6 @@ async def ws_get_items(
|
|||
return get
|
||||
|
||||
|
||||
@pytest.fixture(name="api_responses")
|
||||
def mock_api_responses() -> list[dict | list]:
|
||||
"""Fixture for API responses to return during test."""
|
||||
return []
|
||||
|
||||
|
||||
def create_response_object(api_response: dict | list) -> tuple[Response, bytes]:
|
||||
"""Create an http response."""
|
||||
return (
|
||||
Response({"Content-Type": "application/json"}),
|
||||
json.dumps(api_response).encode(),
|
||||
)
|
||||
|
||||
|
||||
def create_batch_response_object(
|
||||
content_ids: list[str], api_responses: list[dict | list | Response | None]
|
||||
) -> tuple[Response, bytes]:
|
||||
|
@ -225,18 +206,10 @@ def create_batch_response_handler(
|
|||
return _handler
|
||||
|
||||
|
||||
@pytest.fixture(name="response_handler")
|
||||
def mock_response_handler(api_responses: list[dict | list]) -> list:
|
||||
"""Create a mock http2lib response handler."""
|
||||
return [create_response_object(api_response) for api_response in api_responses]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_http_response(response_handler: list | Callable) -> Mock:
|
||||
"""Fixture to fake out http2lib responses."""
|
||||
|
||||
with patch("httplib2.Http.request", side_effect=response_handler) as mock_response:
|
||||
yield mock_response
|
||||
def setup_http_response(mock_http_response: Mock) -> None:
|
||||
"""Fixture to load the http response mock."""
|
||||
return
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timezone", ["America/Regina", "UTC", "Asia/Tokyo"])
|
||||
|
@ -303,29 +276,6 @@ async def test_get_items(
|
|||
assert state.state == "1"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"response_handler",
|
||||
[
|
||||
([(Response({"status": HTTPStatus.INTERNAL_SERVER_ERROR}), b"")]),
|
||||
],
|
||||
)
|
||||
async def test_list_items_server_error(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
ws_get_items: Callable[[], Awaitable[dict[str, str]]],
|
||||
) -> None:
|
||||
"""Test an error returned by the server when setting up the platform."""
|
||||
|
||||
assert await integration_setup()
|
||||
|
||||
await hass_ws_client(hass)
|
||||
|
||||
state = hass.states.get("todo.my_tasks")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"api_responses",
|
||||
[
|
||||
|
|
Loading…
Reference in New Issue