"""Test the Coinbase config flow.""" import logging from unittest.mock import patch from coinbase.rest.rest_base import HTTPError import pytest from homeassistant import config_entries from homeassistant.components.coinbase.const import ( CONF_CURRENCIES, CONF_EXCHANGE_PRECISION, CONF_EXCHANGE_RATES, DOMAIN, ) from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from .common import ( init_mock_coinbase, mock_get_exchange_rates, mock_get_portfolios, mocked_get_accounts_v3, ) from .const import BAD_CURRENCY, BAD_EXCHANGE_RATE, GOOD_CURRENCY, GOOD_EXCHANGE_RATE async def test_form(hass: HomeAssistant) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] is FlowResultType.FORM assert result["errors"] == {} with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), patch( "homeassistant.components.coinbase.async_setup_entry", return_value=True, ) as mock_setup_entry, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF"}, ) await hass.async_block_till_done() assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == "Default" assert result2["data"] == { CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF", } assert len(mock_setup_entry.mock_calls) == 1 async def test_form_invalid_auth( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) caplog.set_level(logging.DEBUG) api_auth_error_unknown = HTTPError("unknown error") with patch( "coinbase.rest.RESTClient.get_portfolios", side_effect=api_auth_error_unknown, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF", }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "invalid_auth"} assert "Coinbase rejected API credentials due to an unknown error" in caplog.text api_auth_error_key = HTTPError("invalid api key") with patch( "coinbase.rest.RESTClient.get_portfolios", side_effect=api_auth_error_key, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF", }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "invalid_auth_key"} assert "Coinbase rejected API credentials due to an invalid API key" in caplog.text api_auth_error_secret = HTTPError("invalid signature") with patch( "coinbase.rest.RESTClient.get_portfolios", side_effect=api_auth_error_secret, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF", }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "invalid_auth_secret"} assert ( "Coinbase rejected API credentials due to an invalid API secret" in caplog.text ) async def test_form_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) with patch( "coinbase.rest.RESTClient.get_portfolios", side_effect=ConnectionError, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF", }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "cannot_connect"} async def test_form_catch_all_exception(hass: HomeAssistant) -> None: """Test we handle unknown exceptions.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) with patch( "coinbase.rest.RESTClient.get_portfolios", side_effect=Exception, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF", }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "unknown"} async def test_option_form(hass: HomeAssistant) -> None: """Test we handle a good wallet currency option.""" with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): config_entry = await init_mock_coinbase(hass) await hass.async_block_till_done() result = await hass.config_entries.options.async_init(config_entry.entry_id) await hass.async_block_till_done() result2 = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ CONF_CURRENCIES: [GOOD_CURRENCY], CONF_EXCHANGE_RATES: [GOOD_EXCHANGE_RATE], CONF_EXCHANGE_PRECISION: 5, }, ) assert result2["type"] is FlowResultType.CREATE_ENTRY await hass.async_block_till_done() async def test_form_bad_account_currency(hass: HomeAssistant) -> None: """Test we handle a bad currency option.""" with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): config_entry = await init_mock_coinbase(hass) result = await hass.config_entries.options.async_init(config_entry.entry_id) await hass.async_block_till_done() result2 = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ CONF_CURRENCIES: [BAD_CURRENCY], CONF_EXCHANGE_RATES: [], CONF_EXCHANGE_PRECISION: 5, }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "currency_unavailable"} async def test_form_bad_exchange_rate(hass: HomeAssistant) -> None: """Test we handle a bad exchange rate.""" with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): config_entry = await init_mock_coinbase(hass) result = await hass.config_entries.options.async_init(config_entry.entry_id) await hass.async_block_till_done() result2 = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ CONF_CURRENCIES: [], CONF_EXCHANGE_RATES: [BAD_EXCHANGE_RATE], CONF_EXCHANGE_PRECISION: 5, }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "exchange_rate_unavailable"} async def test_option_catch_all_exception(hass: HomeAssistant) -> None: """Test we handle an unknown exception in the option flow.""" with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): config_entry = await init_mock_coinbase(hass) result = await hass.config_entries.options.async_init(config_entry.entry_id) await hass.async_block_till_done() with patch( "coinbase.rest.RESTClient.get_accounts", side_effect=Exception, ): result2 = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ CONF_CURRENCIES: [], CONF_EXCHANGE_RATES: ["ETH"], CONF_EXCHANGE_PRECISION: 5, }, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "unknown"} async def test_reauth_flow(hass: HomeAssistant) -> None: """Test reauth flow.""" with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): config_entry = await init_mock_coinbase(hass) # Start reauth flow result = await hass.config_entries.flow.async_init( DOMAIN, context={ "source": config_entries.SOURCE_REAUTH, "entry_id": config_entry.entry_id, }, data=config_entry.data, ) assert result["type"] is FlowResultType.FORM assert result["step_id"] == "reauth_confirm" # Test successful reauth with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "new_key", CONF_API_TOKEN: "new_secret", }, ) assert result2["type"] is FlowResultType.ABORT assert result2["reason"] == "reauth_successful" assert config_entry.data[CONF_API_KEY] == "new_key" assert config_entry.data[CONF_API_TOKEN] == "new_secret" async def test_reauth_flow_invalid_auth(hass: HomeAssistant) -> None: """Test reauth flow with invalid credentials.""" with ( patch( "coinbase.rest.RESTClient.get_portfolios", return_value=mock_get_portfolios(), ), patch("coinbase.rest.RESTClient.get_accounts", new=mocked_get_accounts_v3), patch( "coinbase.rest.RESTClient.get", return_value={"data": mock_get_exchange_rates()}, ), ): config_entry = await init_mock_coinbase(hass) # Start reauth flow result = await hass.config_entries.flow.async_init( DOMAIN, context={ "source": config_entries.SOURCE_REAUTH, "entry_id": config_entry.entry_id, }, data=config_entry.data, ) # Test invalid auth during reauth api_auth_error_key = HTTPError("invalid api key") with patch( "coinbase.rest.RESTClient.get_portfolios", side_effect=api_auth_error_key, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_API_KEY: "bad_key", CONF_API_TOKEN: "bad_secret", }, ) assert result2["type"] is FlowResultType.FORM assert result2["step_id"] == "reauth_confirm" assert result2["errors"] == {"base": "invalid_auth_key"}