diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2f4c80e --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_mode = auto diff --git a/scripts/test b/scripts/test index cec81ac..18f17e0 100755 --- a/scripts/test +++ b/scripts/test @@ -6,4 +6,9 @@ # Install requirements pip install -r requirements_test.txt # Run tests and get a summary of successes/failures and code coverage -pytest --durations=10 --cov-report term-missing --cov=custom_components.bermuda tests +pytest --durations=10 --cov-report term-missing --cov=custom_components.bermuda tests --cov-fail-under=43 + +# Note that the github workflow runs: +# +# pytest --timeout=9 --durations=10 -n auto -p no:sugar tests +# which doesn't care about coverage. diff --git a/tests/conftest.py b/tests/conftest.py index 3b921a2..ac3e06d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,9 +6,22 @@ import pytest +from .const import SERVICE_INFOS + +# from custom_components.bermuda import BermudaDataUpdateCoordinator + + pytest_plugins = "pytest_homeassistant_custom_component" +# This fixture enables loading custom integrations in all tests. +# Remove to enable selective use of this fixture +@pytest.fixture(autouse=True) +def auto_enable_custom_integrations(enable_custom_integrations): + """Enable loading custom integrations.""" + yield + + # This fixture is used to prevent HomeAssistant from # attempting to create and dismiss persistent # notifications. These calls would fail without this @@ -30,9 +43,8 @@ def skip_notifications_fixture(): @pytest.fixture(name="bypass_get_data") def bypass_get_data_fixture(): """Skip calls to get data from API.""" - # AJG: We don't implement this, so nothing to monkeypatch. - # with patch("custom_components.bermuda.BermudaApiClient.async_get_data"): - # yield + with patch("custom_components.bermuda.BermudaDataUpdateCoordinator.async_refresh"): + yield # In this fixture, we are forcing calls to async_get_data to raise @@ -41,9 +53,22 @@ def bypass_get_data_fixture(): @pytest.fixture(name="error_on_get_data") def error_get_data_fixture(): """Simulate error when retrieving data from API.""" - # AJG: again we don't implement async_get_data... - # with patch( - # "custom_components.bermuda.BermudaApiClient.async_get_data", - # side_effect=Exception, - # ): - # yield + with patch( + "custom_components.bermuda.BermudaDataUpdateCoordinator.async_refresh", + side_effect=Exception, + ): + yield + + +@pytest.fixture(autouse=True) +def mock_bluetooth(enable_bluetooth): + """Auto mock bluetooth.""" + + +# This fixture ensures that the config flow gets service info for the anticipated address +# to go into configured_devices +@pytest.fixture(autouse=True) +def mock_service_info(): + """Simulate a discovered advertisement for config_flow""" + with patch("custom_components.bermuda.bluetooth.async_discovered_service_info"): + return SERVICE_INFOS diff --git a/tests/const.py b/tests/const.py index 2a71fd9..a10e673 100644 --- a/tests/const.py +++ b/tests/const.py @@ -1,13 +1,38 @@ """Constants for Bermuda BLE Trilateration tests.""" -# AJG: We don't use these vars in our flow. TODO: Add some we _do_ use! -# from custom_components.bermuda.const import ( -# CONF_PASSWORD, -# ) -# from custom_components.bermuda.const import ( -# CONF_USERNAME, -# ) -# MOCK_CONFIG = {CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password"} from __future__ import annotations -MOCK_CONFIG = {} +import custom_components.bermuda.const + +# from custom_components.bermuda.const import CONF_DEVICES +# from custom_components.bermuda.const import CONF_MAX_RADIUS + + +MOCK_OPTIONS = { + custom_components.bermuda.const.CONF_MAX_RADIUS: 20.0, + custom_components.bermuda.const.CONF_MAX_VELOCITY: 3.0, + custom_components.bermuda.const.CONF_DEVTRACK_TIMEOUT: 30, + custom_components.bermuda.const.CONF_UPDATE_INTERVAL: 10.0, + custom_components.bermuda.const.CONF_SMOOTHING_SAMPLES: 20, + custom_components.bermuda.const.CONF_ATTENUATION: 3.0, + custom_components.bermuda.const.CONF_REF_POWER: -55.0, + custom_components.bermuda.const.CONF_DEVICES: [], # ["EE:E8:37:9F:6B:54"], +} + +MOCK_CONFIG = {"source": "user"} + + +SERVICE_INFOS = [ + { + "name": "test device", + "advertisement": {"local_name": "test local name"}, + "device": {"name": "test device name"}, + "address": "EE:E8:37:9F:6B:54", + }, + { + "name": "test device2", + "advertisement": {"local_name": "test local name2"}, + "device": {"name": "test device name2"}, + "address": "EE:E8:37:9F:6B:56", + }, +] diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 1849d58..55a0915 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -9,13 +9,11 @@ from homeassistant import data_entry_flow from pytest_homeassistant_custom_component.common import MockConfigEntry -from custom_components.bermuda.const import BINARY_SENSOR from custom_components.bermuda.const import DOMAIN -from custom_components.bermuda.const import PLATFORMS -from custom_components.bermuda.const import SENSOR -from custom_components.bermuda.const import SWITCH +from custom_components.bermuda.const import NAME from .const import MOCK_CONFIG +from .const import MOCK_OPTIONS # This fixture bypasses the actual setup of the integration @@ -57,8 +55,9 @@ async def test_successful_config_flow(hass, bypass_get_data): # Check that the config flow is complete and a new entry is created with # the input data assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "test_username" - assert result["data"] == MOCK_CONFIG + assert result["title"] == NAME + assert result["data"] == {"source": "user"} + assert result["options"] == {} assert result["result"] @@ -80,8 +79,8 @@ async def test_failed_config_flow(hass, error_on_get_data): result["flow_id"], user_input=MOCK_CONFIG ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "auth"} + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result.get("errors") is None # Our config flow also has an options flow, so we must test it as well. @@ -98,17 +97,17 @@ async def test_options_flow(hass): # Verify that the first options step is a user form assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" + assert result["step_id"] == "globalopts" # Enter some fake data into the form result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={platform: platform != SENSOR for platform in PLATFORMS}, + user_input=MOCK_OPTIONS, ) # Verify that the flow finishes assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "test_username" + assert result["title"] == NAME # Verify that the options were updated - assert entry.options == {BINARY_SENSOR: True, SENSOR: False, SWITCH: True} + assert entry.options == MOCK_OPTIONS diff --git a/tests/test_init.py b/tests/test_init.py index 85dc8d5..a6d7684 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -2,8 +2,7 @@ from __future__ import annotations -import pytest -from homeassistant.exceptions import ConfigEntryNotReady +# from homeassistant.exceptions import ConfigEntryNotReady from pytest_homeassistant_custom_component.common import MockConfigEntry from custom_components.bermuda import BermudaDataUpdateCoordinator @@ -34,15 +33,15 @@ async def test_setup_unload_and_reload_entry(hass, bypass_get_data): # call, no code from custom_components/bermuda/api.py actually runs. assert await async_setup_entry(hass, config_entry) assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert type(hass.data[DOMAIN][config_entry.entry_id]).isinstance( - BermudaDataUpdateCoordinator + assert isinstance( + hass.data[DOMAIN][config_entry.entry_id], BermudaDataUpdateCoordinator ) # Reload the entry and assert that the data from above is still there assert await async_reload_entry(hass, config_entry) is None assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert type(hass.data[DOMAIN][config_entry.entry_id]).isinstance( - BermudaDataUpdateCoordinator + assert isinstance( + hass.data[DOMAIN][config_entry.entry_id], BermudaDataUpdateCoordinator ) # Unload the entry and verify that the data has been removed @@ -54,8 +53,15 @@ async def test_setup_entry_exception(hass, error_on_get_data): """Test ConfigEntryNotReady when API raises an exception during entry setup.""" config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") + assert config_entry is not None + # In this case we are testing the condition where async_setup_entry raises # ConfigEntryNotReady using the `error_on_get_data` fixture which simulates # an error. - with pytest.raises(ConfigEntryNotReady): - assert await async_setup_entry(hass, config_entry) + + # Hmmm... this doesn't seem to be how this works. The super's _async_refresh might + # handle exceptions, in which it then sets self.last_update_status, which is what + # async_setup_entry checks in order to raise ConfigEntryNotReady, but I don't think + # anything will "catch" our over-ridded async_refresh's exception. + # with pytest.raises(ConfigEntryNotReady): + # assert await async_setup_entry(hass, config_entry) diff --git a/tests/test_switch.py b/tests/test_switch.py index 0ebcb36..9c18ab9 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -2,21 +2,24 @@ from __future__ import annotations -from unittest.mock import call -from unittest.mock import patch - -from homeassistant.components.switch import SERVICE_TURN_OFF -from homeassistant.components.switch import SERVICE_TURN_ON -from homeassistant.const import ATTR_ENTITY_ID +# from homeassistant.components.switch import SERVICE_TURN_OFF +# from homeassistant.components.switch import SERVICE_TURN_ON +# from homeassistant.const import ATTR_ENTITY_ID from pytest_homeassistant_custom_component.common import MockConfigEntry from custom_components.bermuda import async_setup_entry -from custom_components.bermuda.const import DEFAULT_NAME + +# from custom_components.bermuda.const import DEFAULT_NAME from custom_components.bermuda.const import DOMAIN -from custom_components.bermuda.const import SWITCH from .const import MOCK_CONFIG +# from unittest.mock import call +# from unittest.mock import patch + + +# from custom_components.bermuda.const import SWITCH + async def test_switch_services(hass): """Test switch services.""" @@ -29,25 +32,25 @@ async def test_switch_services(hass): # in test code as well and can be used to test # additional things, like whether a function # was called or what arguments it was called with - with patch( - "custom_components.bermuda.BermudaApiClient.async_set_title" - ) as title_func: - await hass.services.async_call( - SWITCH, - SERVICE_TURN_OFF, - service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, - blocking=True, - ) - assert title_func.called - assert title_func.call_args == call("foo") - - title_func.reset_mock() - - await hass.services.async_call( - SWITCH, - SERVICE_TURN_ON, - service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, - blocking=True, - ) - assert title_func.called - assert title_func.call_args == call("bar") + # with patch( + # "custom_components.bermuda.BermudaDataUpdateCoordinator.async_set_title" + # ) as title_func: + # await hass.services.async_call( + # SWITCH, + # SERVICE_TURN_OFF, + # service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, + # blocking=True, + # ) + # assert title_func.called + # assert title_func.call_args == call("foo") + + # title_func.reset_mock() + + # await hass.services.async_call( + # SWITCH, + # SERVICE_TURN_ON, + # service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, + # blocking=True, + # ) + # assert title_func.called + # assert title_func.call_args == call("bar")