From f5cfd0329cd2b1336560f6a7d2fa26e88e35915c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 7 Dec 2022 20:02:22 +0100 Subject: [PATCH] Use the async_migrate_paypal_agreement function to get the migration URL (#83469) * Use the async_migrate_paypal_agreement function to get the migration URL * Update URL * Handle timeout error --- homeassistant/components/cloud/repairs.py | 9 +-- .../components/cloud/subscription.py | 22 +++++++ tests/components/cloud/test_repairs.py | 13 +++- tests/components/cloud/test_subscription.py | 61 +++++++++++++++++++ 4 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 tests/components/cloud/test_subscription.py diff --git a/homeassistant/components/cloud/repairs.py b/homeassistant/components/cloud/repairs.py index 0d217521c21..bf2df23aca9 100644 --- a/homeassistant/components/cloud/repairs.py +++ b/homeassistant/components/cloud/repairs.py @@ -13,7 +13,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import issue_registry as ir from .const import DOMAIN -from .subscription import async_subscription_info +from .subscription import async_migrate_paypal_agreement, async_subscription_info BACKOFF_TIME = 5 MAX_RETRIES = 60 # This allows for 10 minutes of retries @@ -68,13 +68,13 @@ class LegacySubscriptionRepairFlow(RepairsFlow): async def async_step_change_plan(self, _: None = None) -> FlowResult: """Wait for the user to authorize the app installation.""" + cloud: Cloud = self.hass.data[DOMAIN] + async def _async_wait_for_plan_change() -> None: flow_manager = repairs_flow_manager(self.hass) # We can not get here without a flow manager assert flow_manager is not None - cloud: Cloud = self.hass.data[DOMAIN] - retries = 0 while retries < MAX_RETRIES: self._data = await async_subscription_info(cloud) @@ -90,9 +90,10 @@ class LegacySubscriptionRepairFlow(RepairsFlow): if not self.wait_task: self.wait_task = self.hass.async_create_task(_async_wait_for_plan_change()) + migration = await async_migrate_paypal_agreement(cloud) return self.async_external_step( step_id="change_plan", - url="https://account.nabucasa.com/", + url=migration["url"] if migration else "https://account.nabucasa.com/", ) await self.wait_task diff --git a/homeassistant/components/cloud/subscription.py b/homeassistant/components/cloud/subscription.py index 9a2e5bd87cf..4c18b4f0253 100644 --- a/homeassistant/components/cloud/subscription.py +++ b/homeassistant/components/cloud/subscription.py @@ -1,6 +1,7 @@ """Subscription information.""" from __future__ import annotations +import asyncio import logging from typing import Any @@ -18,7 +19,28 @@ async def async_subscription_info(cloud: Cloud) -> dict[str, Any] | None: try: async with async_timeout.timeout(REQUEST_TIMEOUT): return await cloud_api.async_subscription_info(cloud) + except asyncio.TimeoutError: + _LOGGER.error( + "A timeout of %s was reached while trying to fetch subscription information", + REQUEST_TIMEOUT, + ) except ClientError: _LOGGER.error("Failed to fetch subscription information") return None + + +async def async_migrate_paypal_agreement(cloud: Cloud) -> dict[str, Any] | None: + """Migrate a paypal agreement from legacy.""" + try: + async with async_timeout.timeout(REQUEST_TIMEOUT): + return await cloud_api.async_migrate_paypal_agreement(cloud) + except asyncio.TimeoutError: + _LOGGER.error( + "A timeout of %s was reached while trying to start agreement migration", + REQUEST_TIMEOUT, + ) + except ClientError as exception: + _LOGGER.error("Failed to start agreement migration - %s", exception) + + return None diff --git a/tests/components/cloud/test_repairs.py b/tests/components/cloud/test_repairs.py index 052cdde0d0d..a7f8b2332d7 100644 --- a/tests/components/cloud/test_repairs.py +++ b/tests/components/cloud/test_repairs.py @@ -88,6 +88,10 @@ async def test_legacy_subscription_repair_flow( "https://accounts.nabucasa.com/payments/subscription_info", json={"provider": None}, ) + aioclient_mock.post( + "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", + json={"url": "https://paypal.com"}, + ) cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"}) repair_issue = issue_registry.async_get_issue( @@ -133,7 +137,7 @@ async def test_legacy_subscription_repair_flow( "flow_id": flow_id, "handler": DOMAIN, "step_id": "change_plan", - "url": "https://account.nabucasa.com/", + "url": "https://paypal.com", "description_placeholders": None, } @@ -161,8 +165,15 @@ async def test_legacy_subscription_repair_flow( async def test_legacy_subscription_repair_flow_timeout( hass: HomeAssistant, hass_client: Callable[..., Awaitable[ClientSession]], + mock_auth: Generator[None, AsyncMock, None], + aioclient_mock: AiohttpClientMocker, ): """Test timeout flow of the fix flow for legacy subscription.""" + aioclient_mock.post( + "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", + status=403, + ) + issue_registry: ir.IssueRegistry = ir.async_get(hass) cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"}) diff --git a/tests/components/cloud/test_subscription.py b/tests/components/cloud/test_subscription.py new file mode 100644 index 00000000000..4dac93e92a3 --- /dev/null +++ b/tests/components/cloud/test_subscription.py @@ -0,0 +1,61 @@ +"""Test cloud subscription functions.""" +import asyncio +from unittest.mock import AsyncMock, Mock + +from hass_nabucasa import Cloud +import pytest + +from homeassistant.components.cloud.subscription import ( + async_migrate_paypal_agreement, + async_subscription_info, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from tests.test_util.aiohttp import AiohttpClientMocker + + +@pytest.fixture(name="mocked_cloud") +def mocked_cloud_object(hass: HomeAssistant) -> Cloud: + """Mock cloud object.""" + return Mock( + accounts_server="accounts.nabucasa.com", + auth=Mock(async_check_token=AsyncMock()), + websession=async_get_clientsession(hass), + ) + + +async def test_fetching_subscription_with_timeout_error( + aioclient_mock: AiohttpClientMocker, + caplog: pytest.LogCaptureFixture, + mocked_cloud: Cloud, +): + """Test that we handle timeout error.""" + aioclient_mock.get( + "https://accounts.nabucasa.com/payments/subscription_info", + exc=asyncio.TimeoutError(), + ) + + assert await async_subscription_info(mocked_cloud) is None + assert ( + "A timeout of 10 was reached while trying to fetch subscription information" + in caplog.text + ) + + +async def test_migrate_paypal_agreement_with_timeout_error( + aioclient_mock: AiohttpClientMocker, + caplog: pytest.LogCaptureFixture, + mocked_cloud: Cloud, +): + """Test that we handle timeout error.""" + aioclient_mock.post( + "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", + exc=asyncio.TimeoutError(), + ) + + assert await async_migrate_paypal_agreement(mocked_cloud) is None + assert ( + "A timeout of 10 was reached while trying to start agreement migration" + in caplog.text + )