diff --git a/custom_components/weatherlink/config_flow.py b/custom_components/weatherlink/config_flow.py index 1fe4f38..88d2691 100644 --- a/custom_components/weatherlink/config_flow.py +++ b/custom_components/weatherlink/config_flow.py @@ -113,6 +113,27 @@ async def validate_input_v2( if not await hub.authenticate(): raise InvalidAuth + return {} + + +async def validate_input_v2b( + hass: HomeAssistant, data: dict[str, Any] +) -> dict[str, Any]: + """Validate the user input allows us to connect. + + Data has the keys from STEP_USER_DATA_SCHEMA_V2 with values provided by the user. + """ + websession = async_get_clientsession(hass) + hub = WLHubV2( + station_id=data.get(CONF_STATION_ID), + api_key_v2=data[CONF_API_KEY_V2], + api_secret=data[CONF_API_SECRET], + websession=websession, + ) + + # if not await hub.authenticate(): + # raise InvalidAuth + # Return info that you want to store in the config entry. data = await hub.get_station() _LOGGER.debug("Station data: %s", data) @@ -238,7 +259,7 @@ async def async_step_user_3( user_input[CONF_API_SECRET] = self.user_data_2[CONF_API_SECRET] try: - info = await validate_input_v2(self.hass, user_input) + info = await validate_input_v2b(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: diff --git a/tests/conftest.py b/tests/conftest.py index b48033e..950034f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -75,6 +75,18 @@ def bypass_get_data_fixture( yield +@pytest.fixture(name="bypass_get_data_api_1") +def bypass_get_data_api_1_fixture( + hass: HomeAssistant, +): + """Skip calls to get data from API.""" + with patch( + "custom_components.weatherlink.pyweatherlink.WLHub.get_data", + return_value=json_loads(load_fixture("fryksasm_api1.json")), + ): + yield + + @pytest.fixture(name="bypass_get_station") def bypass_get_station_fixture( hass: HomeAssistant, diff --git a/tests/fixtures/fryksasm_api1.json b/tests/fixtures/fryksasm_api1.json new file mode 100644 index 0000000..5929eca --- /dev/null +++ b/tests/fixtures/fryksasm_api1.json @@ -0,0 +1,119 @@ +{ + "credit": "Davis Instruments Corp.", + "credit_URL": "http:\/\/www.davisnet.com", + "disclaimer_url": "http:\/\/www.davisnet.com\/about\/terms.asp", + "copyright_url": "http:\/\/www.davisnet.com\/about\/terms.asp", + "privacy_policy_url": "http:\/\/www.davisnet.com\/about\/privacy.asp", + "image": { + "url": "http:\/\/www.weatherlink.com\/images\/Logo_Davis_reflxblu.jpg", + "title": "Davis WeatherLink", + "link": "http:\/\/www.weatherlink.com" + }, + "suggested_pickup": "15 minutes after the hour", + "suggested_pickup_period": "60", + "dewpoint_c": "-0.6", + "dewpoint_f": "31.0", + "dewpoint_string": "31.0 F (-0.6 C)", + "heat_index_c": "0.0", + "heat_index_f": "32.0", + "heat_index_string": "32.0 F (0.0 C)", + "location": "Fryks\u00e5s, Dalarna, Sweden", + "latitude": "61.194282356502", + "longitude": "14.5285606384277", + "observation_time": "Last Updated on Dec 29 2024, 11:14 pm CET", + "observation_time_rfc822": "Sun, 29 Dec 2024 23:14:45 +0100", + "pressure_in": "29.409", + "pressure_mb": "995.9", + "pressure_string": "995.9 mb", + "relative_humidity": "97", + "station_id": "petman", + "temp_c": "0.0", + "temp_f": "32.0", + "temperature_string": "32.0 F (0.0 C)", + "wind_degrees": "145", + "wind_dir": "Southeast", + "wind_kt": "5.2", + "wind_mph": "6.0", + "windchill_c": "-2.8", + "windchill_f": "27.0", + "windchill_string": "27.0 F (-2.8 C)", + "davis_current_observation": { + "DID": "001D0A0028EA", + "station_name": "Fryks\u00e5s", + "observation_age": 16, + "dewpoint_day_high_f": "32", + "dewpoint_day_high_time": "12:00am", + "dewpoint_day_low_f": "31", + "dewpoint_day_low_time": "12:00am", + "dewpoint_month_high_f": "43", + "dewpoint_month_low_f": "13", + "dewpoint_year_high_f": "70", + "dewpoint_year_low_f": "-4", + "heat_index_day_high_f": "32", + "heat_index_day_high_time": "12:00am", + "heat_index_month_high_f": "45", + "heat_index_year_high_f": "52", + "pressure_day_high_in": "29.409", + "pressure_day_high_time": "12:00am", + "pressure_day_low_in": "29.406", + "pressure_day_low_time": "12:04am", + "pressure_month_high_in": "30.777", + "pressure_month_low_in": "28.841", + "pressure_tendency_string": "Falling Slowly", + "pressure_year_high_in": "30.777", + "pressure_year_low_in": "28.615", + "rain_day_in": "0.0000", + "rain_month_in": "0.4882", + "rain_rate_day_high_in_per_hr": "0.0000", + "rain_rate_hour_high_in_per_hr": "0.0000", + "rain_rate_in_per_hr": "0.0000", + "rain_rate_month_high_in_per_hr": "0.1260", + "rain_rate_year_high_in_per_hr": "5.9685", + "rain_storm_in": "0.0157", + "rain_storm_start_date": "12\/13\/2024", + "rain_year_in": "28.4094", + "relative_humidity_day_high": "97", + "relative_humidity_day_high_time": "12:00am", + "relative_humidity_day_low": "97", + "relative_humidity_day_low_time": "12:00am", + "relative_humidity_in": "40", + "relative_humidity_in_day_high": "40", + "relative_humidity_in_day_high_time": "12:00am", + "relative_humidity_in_day_low": "40", + "relative_humidity_in_day_low_time": "12:00am", + "relative_humidity_in_month_high": "50", + "relative_humidity_in_month_low": "31", + "relative_humidity_in_year_high": "61", + "relative_humidity_in_year_low": "19", + "sunrise": "10:17am", + "sunset": "3:54pm", + "temp_day_high_f": "32.2", + "temp_day_high_time": "12:00am", + "temp_day_low_f": "32.0", + "temp_day_low_time": "12:14am", + "temp_month_high_f": "44.6", + "temp_month_low_f": "14.1", + "temp_year_high_f": "79.2", + "temp_year_low_f": "-1.0", + "temp_in_day_high_f": "71.4", + "temp_in_day_high_time": "12:00am", + "temp_in_day_low_f": "70.7", + "temp_in_day_low_time": "12:14am", + "temp_in_f": "70.7", + "temp_in_month_high_f": "75.3", + "temp_in_month_low_f": "54.0", + "temp_in_year_high_f": "80.0", + "temp_in_year_low_f": "52.0", + "wind_day_high_mph": "12.0", + "wind_day_high_time": "12:14am", + "wind_month_high_mph": "29.0", + "wind_ten_min_avg_mph": "4.0", + "wind_ten_min_gust_mph": "12.0", + "wind_year_high_mph": "42.0", + "windchill_day_low_f": "27", + "windchill_day_low_time": "12:00am", + "windchill_month_low_f": "10", + "windchill_year_low_f": "3" + }, + "time_to_generate": "0.020508 seconds" +} diff --git a/tests/snapshots/test_binary_sensor.ambr b/tests/snapshots/test_binary_sensor.ambr index b310836..c1b7114 100644 --- a/tests/snapshots/test_binary_sensor.ambr +++ b/tests/snapshots/test_binary_sensor.ambr @@ -1,5 +1,5 @@ # serializer version: 1 -# name: test_sensor[binary_sensor.strp81_connectivity-entry] +# name: test_binary_sensor[binary_sensor.strp81_connectivity-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -32,7 +32,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensor[binary_sensor.strp81_connectivity-state] +# name: test_binary_sensor[binary_sensor.strp81_connectivity-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', @@ -47,7 +47,7 @@ 'state': 'off', }) # --- -# name: test_sensor[binary_sensor.strp81_transmitter_battery-entry] +# name: test_binary_sensor[binary_sensor.strp81_transmitter_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -80,7 +80,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensor[binary_sensor.strp81_transmitter_battery-state] +# name: test_binary_sensor[binary_sensor.strp81_transmitter_battery-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'battery', diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py index 1e1999f..0b3ac24 100644 --- a/tests/test_binary_sensor.py +++ b/tests/test_binary_sensor.py @@ -19,7 +19,7 @@ @pytest.mark.usefixtures("entity_registry_enabled_by_default") -async def test_sensor( +async def test_binary_sensor( hass: HomeAssistant, bypass_get_data, bypass_get_station, diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index ba697ac..2374c23 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -9,10 +9,12 @@ DOMAIN, CONF_API_KEY_V2, CONF_API_SECRET, + CONF_API_TOKEN, CONF_STATION_ID, ) from homeassistant import config_entries from homeassistant.core import HomeAssistant +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResultType @@ -47,8 +49,8 @@ async def test_succesful_flow( assert result["step_id"] == "user_2" with patch( - "custom_components.weatherlink.config_flow.validate_input_v2", - return_value={"title": "test"}, + "custom_components.weatherlink.WLHubV2.authenticate", + return_value=True, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY_V2: "123", CONF_API_SECRET: "456"} @@ -74,10 +76,10 @@ async def test_succesful_flow( ], ids=["cannot_connect", "invalid_auth", "other_exception"], ) -async def test_failed_flow( +async def test_failed_flow_api_v2_step_2( hass: HomeAssistant, bypass_get_all_stations, bypass_get_station, exc, key ) -> None: - """Test failing flows.""" + """Test failing flow step 2.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -101,6 +103,55 @@ async def test_failed_flow( result["flow_id"], {CONF_API_KEY_V2: "123", CONF_API_SECRET: "456"} ) + +@pytest.mark.parametrize( + ("exc", "key"), + [ + (CannotConnect, "cannot_connect"), + (InvalidAuth, "invalid_auth"), + (Exception, "unknown"), + ], + ids=["cannot_connect", "invalid_auth", "other_exception"], +) +async def test_failed_flow_api_v2_step_3( + hass: HomeAssistant, bypass_get_all_stations, bypass_get_station, exc, key +) -> None: + """Test failing flow step 3.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"api_version": "api_v2"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user_2" + + with patch( + "custom_components.weatherlink.config_flow.validate_input_v2", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_API_KEY_V2: "123", CONF_API_SECRET: "456"} + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user_3" + + with patch( + "custom_components.weatherlink.config_flow.validate_input_v2b", + side_effect=exc, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_STATION_ID: "167531"}, + ) + assert result["errors"] == {"base": key} @@ -133,3 +184,116 @@ async def test_auth_error( ) assert result["errors"] == {"base": "invalid_auth"} + + +async def test_succesful_flow_api_v1( + hass: HomeAssistant, bypass_get_data_api_1 +) -> None: + """Test that we get the form and create the entry using api v1.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"api_version": "api_v1"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user_1" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_API_TOKEN: "token", + }, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + + +async def test_auth_error_api_v1( + hass: HomeAssistant, + bypass_get_data_api_1, +) -> None: + """Test auth error.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"api_version": "api_v1"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user_1" + + with patch( + "custom_components.weatherlink.WLHub.authenticate", + return_value=False, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_API_TOKEN: "token", + }, + ) + + assert result["errors"] == {"base": "invalid_auth"} + + +@pytest.mark.parametrize( + ("exc", "key"), + [ + (CannotConnect, "cannot_connect"), + (InvalidAuth, "invalid_auth"), + (Exception, "unknown"), + ], + ids=["cannot_connect", "invalid_auth", "other_exception"], +) +async def test_failed_flow_api_1( + hass: HomeAssistant, bypass_get_data_api_1, exc, key +) -> None: + """Test failing flows.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"api_version": "api_v1"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user_1" + + with patch( + "custom_components.weatherlink.config_flow.validate_input", side_effect=exc + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_API_TOKEN: "token", + }, + ) + + assert result["errors"] == {"base": key}