diff --git a/tests/components/watttime/conftest.py b/tests/components/watttime/conftest.py new file mode 100644 index 00000000000..84d8704eb1e --- /dev/null +++ b/tests/components/watttime/conftest.py @@ -0,0 +1,103 @@ +"""Define test fixtures for WattTime.""" +import json +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from homeassistant.components.watttime.config_flow import ( + CONF_LOCATION_TYPE, + LOCATION_TYPE_COORDINATES, +) +from homeassistant.components.watttime.const import DOMAIN +from homeassistant.const import ( + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_PASSWORD, + CONF_USERNAME, +) +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry, load_fixture + + +@pytest.fixture(name="client") +def client_fixture(get_grid_region): + """Define an aiowatttime client.""" + client = Mock() + client.emissions.async_get_grid_region = get_grid_region + client.emissions.async_get_realtime_emissions = AsyncMock() + return client + + +@pytest.fixture(name="config_auth") +def config_auth_fixture(hass): + """Define an auth config entry data fixture.""" + return { + CONF_USERNAME: "user", + CONF_PASSWORD: "password", + } + + +@pytest.fixture(name="config_coordinates") +def config_coordinates_fixture(hass): + """Define a coordinates config entry data fixture.""" + return { + CONF_LATITUDE: 32.87336, + CONF_LONGITUDE: -117.22743, + } + + +@pytest.fixture(name="config_location_type") +def config_location_type_fixture(hass): + """Define a location type config entry data fixture.""" + return { + CONF_LOCATION_TYPE: LOCATION_TYPE_COORDINATES, + } + + +@pytest.fixture(name="config_entry") +def config_entry_fixture(hass, config_auth, config_coordinates, unique_id): + """Define a config entry fixture.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=unique_id, + data={**config_auth, **config_coordinates}, + ) + entry.add_to_hass(hass) + return entry + + +@pytest.fixture(name="data_grid_region", scope="session") +def data_grid_region_fixture(): + """Define grid region data.""" + return json.loads(load_fixture("grid_region_data.json", "watttime")) + + +@pytest.fixture(name="get_grid_region") +def get_grid_region_fixture(data_grid_region): + """Define an aiowatttime method to get grid region data.""" + return AsyncMock(return_value=data_grid_region) + + +@pytest.fixture(name="setup_watttime") +async def setup_watttime_fixture(hass, client, config_auth, config_coordinates): + """Define a fixture to set up WattTime.""" + with patch( + "homeassistant.components.watttime.Client.async_login", return_value=client + ), patch( + "homeassistant.components.watttime.config_flow.Client.async_login", + return_value=client, + ), patch( + "homeassistant.components.watttime.PLATFORMS", [] + ): + assert await async_setup_component( + hass, DOMAIN, {**config_auth, **config_coordinates} + ) + await hass.async_block_till_done() + yield + + +@pytest.fixture(name="unique_id") +def unique_id_fixture(hass): + """Define a config entry unique ID fixture.""" + return "32.87336, -117.22743" diff --git a/tests/components/watttime/fixtures/grid_region_data.json b/tests/components/watttime/fixtures/grid_region_data.json new file mode 100644 index 00000000000..0104ebeb98c --- /dev/null +++ b/tests/components/watttime/fixtures/grid_region_data.json @@ -0,0 +1,5 @@ +{ + "id": 263, + "abbrev": "PJM_NJ", + "name": "PJM New Jersey" +} diff --git a/tests/components/watttime/test_config_flow.py b/tests/components/watttime/test_config_flow.py index de6e16a400a..ddbe4ebff4a 100644 --- a/tests/components/watttime/test_config_flow.py +++ b/tests/components/watttime/test_config_flow.py @@ -7,7 +7,6 @@ import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components.watttime.config_flow import ( CONF_LOCATION_TYPE, - LOCATION_TYPE_COORDINATES, LOCATION_TYPE_HOME, ) from homeassistant.components.watttime.const import ( @@ -29,106 +28,108 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) -from tests.common import MockConfigEntry - -@pytest.fixture(name="client") -def client_fixture(get_grid_region): - """Define a fixture for an aiowatttime client.""" - client = AsyncMock(return_value=None) - client.emissions.async_get_grid_region = get_grid_region - return client - - -@pytest.fixture(name="client_login") -def client_login_fixture(client): - """Define a fixture for patching the aiowatttime coroutine to get a client.""" +@pytest.mark.parametrize( + "exc,error", [(InvalidCredentialsError, "invalid_auth"), (Exception, "unknown")] +) +async def test_auth_errors( + hass: HomeAssistant, config_auth, config_location_type, exc, error +) -> None: + """Test that issues with auth show the correct error.""" with patch( - "homeassistant.components.watttime.config_flow.Client.async_login" - ) as mock_client: - mock_client.return_value = client - yield mock_client + "homeassistant.components.watttime.config_flow.Client.async_login", + side_effect=exc, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=config_auth + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": error} -@pytest.fixture(name="get_grid_region") -def get_grid_region_fixture(): - """Define a fixture for getting grid region data.""" - return AsyncMock(return_value={"abbrev": "AUTH_1", "id": 1, "name": "Authority 1"}) - - -async def test_duplicate_error(hass: HomeAssistant, client_login): - """Test that errors are shown when duplicate entries are added.""" - MockConfigEntry( - domain=DOMAIN, - unique_id="32.87336, -117.22743", - data={ - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 32.87336, - CONF_LONGITUDE: -117.22743, - }, - ).add_to_hass(hass) - +@pytest.mark.parametrize( + "get_grid_region,errors", + [ + ( + AsyncMock(side_effect=CoordinatesNotFoundError), + {"latitude": "unknown_coordinates"}, + ), + ( + AsyncMock(side_effect=Exception), + {"base": "unknown"}, + ), + ], +) +async def test_coordinate_errors( + hass: HomeAssistant, + config_auth, + config_coordinates, + config_location_type, + errors, + setup_watttime, +) -> None: + """Test that issues with coordinates show the correct error.""" result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=config_auth ) result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LOCATION_TYPE: LOCATION_TYPE_HOME}, + result["flow_id"], user_input=config_location_type ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_coordinates + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == errors + +@pytest.mark.parametrize( + "config_location_type", [{CONF_LOCATION_TYPE: LOCATION_TYPE_HOME}] +) +async def test_duplicate_error( + hass: HomeAssistant, config_auth, config_entry, config_location_type, setup_watttime +): + """Test that errors are shown when duplicate entries are added.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=config_auth + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_location_type + ) assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" -async def test_options_flow(hass): +async def test_options_flow(hass: HomeAssistant, config_entry): """Test config flow options.""" - entry = MockConfigEntry( - domain=DOMAIN, - unique_id="32.87336, -117.22743", - data={ - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 32.87336, - CONF_LONGITUDE: -117.22743, - }, - ) - entry.add_to_hass(hass) - with patch( "homeassistant.components.watttime.async_setup_entry", return_value=True ): - await hass.config_entries.async_setup(entry.entry_id) - result = await hass.config_entries.options.async_init(entry.entry_id) - + await hass.config_entries.async_setup(config_entry.entry_id) + result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_SHOW_ON_MAP: False} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert entry.options == {CONF_SHOW_ON_MAP: False} + assert config_entry.options == {CONF_SHOW_ON_MAP: False} -async def test_show_form_coordinates(hass: HomeAssistant, client_login) -> None: +async def test_show_form_coordinates( + hass: HomeAssistant, config_auth, config_location_type, setup_watttime +) -> None: """Test showing the form to input custom latitude/longitude.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, + result["flow_id"], user_input=config_auth ) result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LOCATION_TYPE: LOCATION_TYPE_COORDINATES}, + result["flow_id"], user_input=config_location_type ) result = await hass.config_entries.flow.async_configure(result["flow_id"]) - assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "coordinates" assert result["errors"] is None @@ -139,73 +140,15 @@ async def test_show_form_user(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] is None -@pytest.mark.parametrize( - "get_grid_region", [AsyncMock(side_effect=CoordinatesNotFoundError)] -) -async def test_step_coordinates_unknown_coordinates( - hass: HomeAssistant, client_login +async def test_step_reauth( + hass: HomeAssistant, config_auth, config_coordinates, config_entry, setup_watttime ) -> None: - """Test that providing coordinates with no data is handled.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LOCATION_TYPE: LOCATION_TYPE_COORDINATES}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LATITUDE: "0", CONF_LONGITUDE: "0"}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"latitude": "unknown_coordinates"} - - -@pytest.mark.parametrize("get_grid_region", [AsyncMock(side_effect=Exception)]) -async def test_step_coordinates_unknown_error( - hass: HomeAssistant, client_login -) -> None: - """Test that providing coordinates with no data is handled.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LOCATION_TYPE: LOCATION_TYPE_HOME}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "unknown"} - - -async def test_step_reauth(hass: HomeAssistant, client_login) -> None: """Test a full reauth flow.""" - MockConfigEntry( - domain=DOMAIN, - unique_id="51.528308, -0.3817765", - data={ - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 51.528308, - CONF_LONGITUDE: -0.3817765, - CONF_BALANCING_AUTHORITY: "Authority 1", - CONF_BALANCING_AUTHORITY_ABBREV: "AUTH_1", - }, - ).add_to_hass(hass) - with patch( "homeassistant.components.watttime.async_setup_entry", return_value=True, @@ -214,10 +157,8 @@ async def test_step_reauth(hass: HomeAssistant, client_login) -> None: DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={ - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 51.528308, - CONF_LONGITUDE: -0.3817765, + **config_auth, + **config_coordinates, CONF_BALANCING_AUTHORITY: "Authority 1", CONF_BALANCING_AUTHORITY_ABBREV: "AUTH_1", }, @@ -226,105 +167,29 @@ async def test_step_reauth(hass: HomeAssistant, client_login) -> None: result["flow_id"], user_input={CONF_PASSWORD: "password"}, ) - await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "reauth_successful" assert len(hass.config_entries.async_entries()) == 1 -async def test_step_reauth_invalid_credentials(hass: HomeAssistant) -> None: - """Test that invalid credentials during reauth are handled.""" - MockConfigEntry( - domain=DOMAIN, - unique_id="51.528308, -0.3817765", - data={ - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 51.528308, - CONF_LONGITUDE: -0.3817765, - CONF_BALANCING_AUTHORITY: "Authority 1", - CONF_BALANCING_AUTHORITY_ABBREV: "AUTH_1", - }, - ).add_to_hass(hass) - - with patch( - "homeassistant.components.watttime.config_flow.Client.async_login", - AsyncMock(side_effect=InvalidCredentialsError), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_REAUTH}, - data={ - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 51.528308, - CONF_LONGITUDE: -0.3817765, - CONF_BALANCING_AUTHORITY: "Authority 1", - CONF_BALANCING_AUTHORITY_ABBREV: "AUTH_1", - }, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_PASSWORD: "password"}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_auth"} - - -async def test_step_user_coordinates(hass: HomeAssistant, client_login) -> None: +async def test_step_user_coordinates( + hass: HomeAssistant, + config_auth, + config_location_type, + config_coordinates, + setup_watttime, +) -> None: """Test a full login flow (inputting custom coordinates).""" - - with patch( - "homeassistant.components.watttime.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LOCATION_TYPE: LOCATION_TYPE_COORDINATES}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"}, - ) - await hass.async_block_till_done() - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "51.528308, -0.3817765" - assert result["data"] == { - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_LATITUDE: 51.528308, - CONF_LONGITUDE: -0.3817765, - CONF_BALANCING_AUTHORITY: "Authority 1", - CONF_BALANCING_AUTHORITY_ABBREV: "AUTH_1", - } - - -async def test_step_user_home(hass: HomeAssistant, client_login) -> None: - """Test a full login flow (selecting the home location).""" - - with patch( - "homeassistant.components.watttime.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_LOCATION_TYPE: LOCATION_TYPE_HOME}, - ) - await hass.async_block_till_done() - + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=config_auth + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_location_type + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_coordinates + ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == "32.87336, -117.22743" assert result["data"] == { @@ -332,41 +197,31 @@ async def test_step_user_home(hass: HomeAssistant, client_login) -> None: CONF_PASSWORD: "password", CONF_LATITUDE: 32.87336, CONF_LONGITUDE: -117.22743, - CONF_BALANCING_AUTHORITY: "Authority 1", - CONF_BALANCING_AUTHORITY_ABBREV: "AUTH_1", + CONF_BALANCING_AUTHORITY: "PJM New Jersey", + CONF_BALANCING_AUTHORITY_ABBREV: "PJM_NJ", } -async def test_step_user_invalid_credentials(hass: HomeAssistant) -> None: - """Test that invalid credentials are handled.""" - - with patch( - "homeassistant.components.watttime.config_flow.Client.async_login", - AsyncMock(side_effect=InvalidCredentialsError), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_auth"} - - -@pytest.mark.parametrize("get_grid_region", [AsyncMock(side_effect=Exception)]) -async def test_step_user_unknown_error(hass: HomeAssistant, client_login) -> None: - """Test that an unknown error during the login step is handled.""" - - with patch( - "homeassistant.components.watttime.config_flow.Client.async_login", - AsyncMock(side_effect=Exception), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={CONF_USERNAME: "user", CONF_PASSWORD: "password"}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "unknown"} +@pytest.mark.parametrize( + "config_location_type", [{CONF_LOCATION_TYPE: LOCATION_TYPE_HOME}] +) +async def test_step_user_home( + hass: HomeAssistant, config_auth, config_location_type, setup_watttime +) -> None: + """Test a full login flow (selecting the home location).""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=config_auth + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_location_type + ) + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "32.87336, -117.22743" + assert result["data"] == { + CONF_USERNAME: "user", + CONF_PASSWORD: "password", + CONF_LATITUDE: 32.87336, + CONF_LONGITUDE: -117.22743, + CONF_BALANCING_AUTHORITY: "PJM New Jersey", + CONF_BALANCING_AUTHORITY_ABBREV: "PJM_NJ", + }