From 6eb2fb2b69f0bd419144aa3261da13e83fafd65d Mon Sep 17 00:00:00 2001 From: Chris <1105672+firstof9@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:57:17 -0700 Subject: [PATCH] fix: insert 'fake' station when no stations found and provide error message (#120) * fix: insert 'fake' station when no stations found and provide error message * update tests * linting * update tests * formatting --- .github/workflows/test.yml | 2 +- custom_components/gasbuddy/config_flow.py | 11 +- custom_components/gasbuddy/strings.json | 3 +- .../gasbuddy/translations/en.json | 3 +- .../gasbuddy/translations/pt.json | 3 +- requirements_test.txt | 12 +- setup.cfg | 2 +- tests/fixtures/location_results.json | 151 +++++++++++++++++ tests/fixtures/no_results.json | 9 + tests/test_config_flow.py | 160 +++++++++++++++++- tox.ini | 5 +- 11 files changed, 339 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/location_results.json create mode 100644 tests/fixtures/no_results.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7cf0686..9bee6b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: strategy: matrix: python-version: - - "3.12" + - "3.13" steps: - name: 📥 Checkout the repository diff --git a/custom_components/gasbuddy/config_flow.py b/custom_components/gasbuddy/config_flow.py index 0ca1ba9..f65e1fd 100644 --- a/custom_components/gasbuddy/config_flow.py +++ b/custom_components/gasbuddy/config_flow.py @@ -59,6 +59,9 @@ async def _get_station_list(hass, user_input) -> list | None: full_name = f'{station["name"]} @ {station["address"]["line1"]}' stations_list[station["id"]] = full_name + if len(stations_list) == 0: + stations_list["-"] = "No stations in search area." + _LOGGER.debug("stations_list: %s", stations_list) return stations_list @@ -140,7 +143,7 @@ def _get_default(key: str, fallback_default: Any = None) -> Any | None: { vol.Required( CONF_STATION_ID, default=_get_default(CONF_STATION_ID) - ): vol.In(station_list), + ): vol.All(vol.In(station_list), vol.NotIn(["-"])), vol.Required(CONF_NAME, default=_get_default(CONF_NAME, DEFAULT_NAME)): str, } ) @@ -239,6 +242,9 @@ async def _show_config_home(self, user_input): station_list = await _get_station_list(self.hass, user_input) + if "-" in station_list.keys(): + self._errors[CONF_STATION_ID] = "no_results" + return self.async_show_form( step_id="home", data_schema=_get_schema_home(self.hass, user_input, defaults, station_list), @@ -283,6 +289,9 @@ async def _show_config_station_list(self, user_input): station_list = await _get_station_list(self.hass, self._data) + if "-" in station_list.keys(): + self._errors[CONF_STATION_ID] = "no_results" + return self.async_show_form( step_id="station_list", data_schema=_get_schema_station_list( diff --git a/custom_components/gasbuddy/strings.json b/custom_components/gasbuddy/strings.json index dc94511..0206462 100644 --- a/custom_components/gasbuddy/strings.json +++ b/custom_components/gasbuddy/strings.json @@ -1,7 +1,8 @@ { "config": { "error": { - "station_id": "Invalid station ID" + "station_id": "Invalid station ID", + "no_results": "No stations found in this area." }, "step": { "user": { diff --git a/custom_components/gasbuddy/translations/en.json b/custom_components/gasbuddy/translations/en.json index 76729f2..bd9d1f6 100644 --- a/custom_components/gasbuddy/translations/en.json +++ b/custom_components/gasbuddy/translations/en.json @@ -1,7 +1,8 @@ { "config": { "error": { - "station_id": "Invalid station ID" + "station_id": "Invalid station ID", + "no_results": "No stations found in this area." }, "step": { "user": { diff --git a/custom_components/gasbuddy/translations/pt.json b/custom_components/gasbuddy/translations/pt.json index 21ae4bc..2389b8f 100644 --- a/custom_components/gasbuddy/translations/pt.json +++ b/custom_components/gasbuddy/translations/pt.json @@ -1,7 +1,8 @@ { "config": { "error": { - "station_id": "ID de estação inválido" + "station_id": "ID de estação inválido", + "no_results": "No stations found in this area." }, "step": { "user": { diff --git a/requirements_test.txt b/requirements_test.txt index 67d860e..538ad1b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,11 +1,11 @@ -r requirements.txt pytest-homeassistant-custom-component -black==24.10.0 -flake8==7.1.1 -mypy==1.13.0 -pydocstyle==6.3.0 +black +flake8 +mypy +pydocstyle isort -pylint==3.3.2 -tox==4.23.2 +pylint +tox pytest aioresponses \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index bdfd480..3cab166 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [mypy] -python_version = 3.12 +python_version = 3.13 show_error_codes = true ignore_errors = true follow_imports = silent diff --git a/tests/fixtures/location_results.json b/tests/fixtures/location_results.json new file mode 100644 index 0000000..3bda064 --- /dev/null +++ b/tests/fixtures/location_results.json @@ -0,0 +1,151 @@ +{ + "data": { + "locationBySearchTerm": { + "stations": { + "count": 92, + "results": [ + { + "address": { + "line1": "1520 N Verrado Way" + }, + "id": "187725", + "name": "Shell" + }, + { + "address": { + "line1": "1101 N Verrado Way" + }, + "id": "208656", + "name": "Costco" + }, + { + "address": { + "line1": "1419 N 195th Ave" + }, + "id": "87490", + "name": "Chevron" + }, + { + "address": { + "line1": "721 N 195th Ave" + }, + "id": "110402", + "name": "Circle K" + }, + { + "address": { + "line1": "19600 W Indian School Rd" + }, + "id": "203982", + "name": "Fry's" + }, + { + "address": { + "line1": "537 S Watson Rd" + }, + "id": "126744", + "name": "Circle K" + }, + { + "address": { + "line1": "900 S Watson Rd" + }, + "id": "201250", + "name": "QuikTrip" + }, + { + "address": { + "line1": "1300 S Watson Rd" + }, + "id": "38363", + "name": "Fry's" + }, + { + "address": { + "line1": "1610 N Miller Rd" + }, + "id": "27487", + "name": "Love's Travel Stop" + }, + { + "address": { + "line1": "1850 S Miller Rd" + }, + "id": "160044", + "name": "QuikTrip" + }, + { + "address": { + "line1": "2075 S Miller Rd" + }, + "id": "135437", + "name": "Chevron" + }, + { + "address": { + "line1": "16380 W Yuma Rd" + }, + "id": "130812", + "name": "Fry's" + }, + { + "address": { + "line1": "15535 W McDowell Rd" + }, + "id": "200905", + "name": "Circle K" + }, + { + "address": { + "line1": "440 N Estrella Pkwy" + }, + "id": "85320", + "name": "Safeway" + }, + { + "address": { + "line1": "575 N Estrella Pkwy" + }, + "id": "155795", + "name": "QuikTrip" + }, + { + "address": { + "line1": "307 E US-85" + }, + "id": "118417", + "name": "Circle K" + }, + { + "address": { + "line1": "825 E Monroe Ave" + }, + "id": "154238", + "name": "Chevron" + }, + { + "address": { + "line1": "501 E Monroe Ave" + }, + "id": "150938", + "name": "Shell" + }, + { + "address": { + "line1": "1540 N Bullard Ave" + }, + "id": "209199", + "name": "QuikTrip" + }, + { + "address": { + "line1": "14175 W Indian School Rd" + }, + "id": "27442", + "name": "Safeway" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/no_results.json b/tests/fixtures/no_results.json new file mode 100644 index 0000000..6c8d0ae --- /dev/null +++ b/tests/fixtures/no_results.json @@ -0,0 +1,9 @@ +{ + "data": { + "locationBySearchTerm": { + "stations": { + "results": [] + } + } + } +} \ No newline at end of file diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index afb7670..7d633a0 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -17,8 +17,12 @@ DEFAULT_NAME, DOMAIN, ) +from tests.common import load_fixture from tests.const import CONFIG_DATA, STATION_LIST +BASE_URL = "https://www.gasbuddy.com/graphql" +NO_STATIONS_LIST = {"-": "No stations in search area."} + pytestmark = pytest.mark.asyncio @@ -48,8 +52,15 @@ async def test_form_home( data, hass, mock_gasbuddy, + mock_aioclient, ): """Test we get the form.""" + mock_aioclient.post( + BASE_URL, + status=200, + body=load_fixture("location_results.json"), + repeat=True, + ) await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -60,10 +71,7 @@ async def test_form_home( with patch( "custom_components.gasbuddy.async_setup_entry", return_value=True, - ) as mock_setup_entry, patch( - "custom_components.gasbuddy.config_flow._get_station_list", - return_value=STATION_LIST, - ): + ) as mock_setup_entry: result = await hass.config_entries.flow.async_configure( result["flow_id"], {"next_step_id": "search"} ) @@ -116,8 +124,15 @@ async def test_form_postal( data, hass, mock_gasbuddy, + mock_aioclient, ): """Test we get the form.""" + mock_aioclient.post( + BASE_URL, + status=200, + body=load_fixture("location_results.json"), + repeat=True, + ) await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -128,10 +143,7 @@ async def test_form_postal( with patch( "custom_components.gasbuddy.async_setup_entry", return_value=True, - ) as mock_setup_entry, patch( - "custom_components.gasbuddy.config_flow._get_station_list", - return_value=STATION_LIST, - ): + ) as mock_setup_entry: result = await hass.config_entries.flow.async_configure( result["flow_id"], {"next_step_id": "search"} ) @@ -225,3 +237,135 @@ async def test_form_manual( await hass.async_block_till_done() assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "input,step_id,title,data", + [ + ( + { + CONF_NAME: DEFAULT_NAME, + CONF_STATION_ID: "208656", + }, + "user", + DEFAULT_NAME, + { + CONF_NAME: DEFAULT_NAME, + CONF_STATION_ID: "208656", + CONF_INTERVAL: 3600, + CONF_UOM: True, + }, + ), + ], +) +async def test_form_home_no_stations( + input, + step_id, + title, + data, + hass, + mock_gasbuddy, + mock_aioclient, +): + """Test we get the form.""" + mock_aioclient.post( + BASE_URL, + status=200, + body=load_fixture("no_results.json"), + repeat=True, + ) + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == step_id + + with patch( + "custom_components.gasbuddy.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"next_step_id": "search"} + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.MENU + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"next_step_id": "home"} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "home" + assert result["errors"] == {"station_id": "no_results"} + + +@pytest.mark.parametrize( + "input,input2,title,data", + [ + ( + { + CONF_POSTAL: "85396", + }, + { + CONF_STATION_ID: "208656", + CONF_NAME: DEFAULT_NAME, + }, + DEFAULT_NAME, + { + CONF_NAME: DEFAULT_NAME, + CONF_STATION_ID: "208656", + CONF_INTERVAL: 3600, + CONF_UOM: True, + }, + ), + ], +) +async def test_form_postal_no_stations( + input, + input2, + title, + data, + hass, + mock_gasbuddy, + mock_aioclient, +): + """Test we get the form.""" + mock_aioclient.post( + BASE_URL, + status=200, + body=load_fixture("no_results.json"), + repeat=True, + ) + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "user" + + with patch( + "custom_components.gasbuddy.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"next_step_id": "search"} + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.MENU + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"next_step_id": "postal"} + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.FORM + + assert result["step_id"] == "postal" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], input + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "station_list" + assert result["errors"] == {"station_id": "no_results"} diff --git a/tox.ini b/tox.ini index 7f34bb8..de66a9f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,14 @@ [tox] skipsdist = true -envlist = py310, py311, py312, lint, mypy +envlist = py310, py311, py312, py313, lint, mypy skip_missing_interpreters = True [gh-actions] python = 3.10: py310 3.11: py311 - 3.12: py312, lint, mypy + 3.12: py312 + 3.13: py313, lint, mypy [pytest] asyncio_default_fixture_loop_scope=function