* Upgrade pytest-aiohttp * Make sure executors, tasks and timers are closed Some test will trigger warnings on garbage collect, these warnings spills over into next test. Some test trigger tasks that raise errors on shutdown, these spill over into next test. This is to mimic older pytest-aiohttp and it's behaviour on test cleanup. Discussions on similar changes for pytest-aiohttp are here: https://github.com/pytest-dev/pytest-asyncio/pull/309 * Replace loop with event_loop * Make sure time is frozen for tests * Make sure the ConditionType is not async /home-assistant/homeassistant/helpers/template.py:2082: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited def wrapper(*args, **kwargs): Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. * Increase litejet press tests with a factor 10 The times are simulated anyway, and we can't stop the normal event from occuring. * Use async handlers for aiohttp tests/components/motioneye/test_camera.py::test_get_still_image_from_camera tests/components/motioneye/test_camera.py::test_get_still_image_from_camera tests/components/motioneye/test_camera.py::test_get_stream_from_camera tests/components/motioneye/test_camera.py::test_get_stream_from_camera tests/components/motioneye/test_camera.py::test_camera_option_stream_url_template tests/components/motioneye/test_camera.py::test_camera_option_stream_url_template /Users/joakim/src/hass/home-assistant/venv/lib/python3.9/site-packages/aiohttp/web_urldispatcher.py:189: DeprecationWarning: Bare functions are deprecated, use async ones warnings.warn( * Switch to freezegun in modbus tests The tests allowed clock to tick in between steps * Make sure skybell object are fully mocked Old tests would trigger attempts to post to could services: ``` DEBUG:aioskybell:HTTP post https://cloud.myskybell.com/api/v3/login/ Request with headers: {'content-type': 'application/json', 'accept': '*/*', 'x-skybell-app-id': 'd2b542c7-a7e4-4e1e-b77d-2b76911c7c46', 'x-skybell-client-id': '1f36a3c0-6dee-4997-a6db-4e1c67338e57'} ``` * Fix sorting that broke after rebase
321 lines
10 KiB
Python
321 lines
10 KiB
Python
"""The tests for the Geofency device tracker platform."""
|
|
from http import HTTPStatus
|
|
|
|
# pylint: disable=redefined-outer-name
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant import config_entries, data_entry_flow
|
|
from homeassistant.components import zone
|
|
from homeassistant.components.geofency import CONF_MOBILE_BEACONS, DOMAIN
|
|
from homeassistant.config import async_process_ha_core_config
|
|
from homeassistant.const import (
|
|
ATTR_LATITUDE,
|
|
ATTR_LONGITUDE,
|
|
STATE_HOME,
|
|
STATE_NOT_HOME,
|
|
)
|
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import slugify
|
|
|
|
HOME_LATITUDE = 37.239622
|
|
HOME_LONGITUDE = -115.815811
|
|
|
|
NOT_HOME_LATITUDE = 37.239394
|
|
NOT_HOME_LONGITUDE = -115.763283
|
|
|
|
GPS_ENTER_HOME = {
|
|
"latitude": HOME_LATITUDE,
|
|
"longitude": HOME_LONGITUDE,
|
|
"device": "4A7FE356-2E9D-4264-A43F-BF80ECAEE416",
|
|
"name": "Home",
|
|
"radius": 100,
|
|
"id": "BAAD384B-A4AE-4983-F5F5-4C2F28E68205",
|
|
"date": "2017-08-19T10:53:53Z",
|
|
"address": "Testing Trail 1",
|
|
"entry": "1",
|
|
}
|
|
|
|
GPS_EXIT_HOME = {
|
|
"latitude": HOME_LATITUDE,
|
|
"longitude": HOME_LONGITUDE,
|
|
"device": "4A7FE356-2E9D-4264-A43F-BF80ECAEE416",
|
|
"name": "Home",
|
|
"radius": 100,
|
|
"id": "BAAD384B-A4AE-4983-F5F5-4C2F28E68205",
|
|
"date": "2017-08-19T10:53:53Z",
|
|
"address": "Testing Trail 1",
|
|
"entry": "0",
|
|
}
|
|
|
|
BEACON_ENTER_HOME = {
|
|
"latitude": HOME_LATITUDE,
|
|
"longitude": HOME_LONGITUDE,
|
|
"beaconUUID": "FFEF0E83-09B2-47C8-9837-E7B563F5F556",
|
|
"minor": "36138",
|
|
"major": "8629",
|
|
"device": "4A7FE356-2E9D-4264-A43F-BF80ECAEE416",
|
|
"name": "Home",
|
|
"radius": 100,
|
|
"id": "BAAD384B-A4AE-4983-F5F5-4C2F28E68205",
|
|
"date": "2017-08-19T10:53:53Z",
|
|
"address": "Testing Trail 1",
|
|
"entry": "1",
|
|
}
|
|
|
|
BEACON_EXIT_HOME = {
|
|
"latitude": HOME_LATITUDE,
|
|
"longitude": HOME_LONGITUDE,
|
|
"beaconUUID": "FFEF0E83-09B2-47C8-9837-E7B563F5F556",
|
|
"minor": "36138",
|
|
"major": "8629",
|
|
"device": "4A7FE356-2E9D-4264-A43F-BF80ECAEE416",
|
|
"name": "Home",
|
|
"radius": 100,
|
|
"id": "BAAD384B-A4AE-4983-F5F5-4C2F28E68205",
|
|
"date": "2017-08-19T10:53:53Z",
|
|
"address": "Testing Trail 1",
|
|
"entry": "0",
|
|
}
|
|
|
|
BEACON_ENTER_CAR = {
|
|
"latitude": NOT_HOME_LATITUDE,
|
|
"longitude": NOT_HOME_LONGITUDE,
|
|
"beaconUUID": "FFEF0E83-09B2-47C8-9837-E7B563F5F556",
|
|
"minor": "36138",
|
|
"major": "8629",
|
|
"device": "4A7FE356-2E9D-4264-A43F-BF80ECAEE416",
|
|
"name": "Car 1",
|
|
"radius": 100,
|
|
"id": "BAAD384B-A4AE-4983-F5F5-4C2F28E68205",
|
|
"date": "2017-08-19T10:53:53Z",
|
|
"address": "Testing Trail 1",
|
|
"entry": "1",
|
|
}
|
|
|
|
BEACON_EXIT_CAR = {
|
|
"latitude": NOT_HOME_LATITUDE,
|
|
"longitude": NOT_HOME_LONGITUDE,
|
|
"beaconUUID": "FFEF0E83-09B2-47C8-9837-E7B563F5F556",
|
|
"minor": "36138",
|
|
"major": "8629",
|
|
"device": "4A7FE356-2E9D-4264-A43F-BF80ECAEE416",
|
|
"name": "Car 1",
|
|
"radius": 100,
|
|
"id": "BAAD384B-A4AE-4983-F5F5-4C2F28E68205",
|
|
"date": "2017-08-19T10:53:53Z",
|
|
"address": "Testing Trail 1",
|
|
"entry": "0",
|
|
}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_dev_track(mock_device_tracker_conf):
|
|
"""Mock device tracker config loading."""
|
|
|
|
|
|
@pytest.fixture
|
|
async def geofency_client(event_loop, hass, hass_client_no_auth):
|
|
"""Geofency mock client (unauthenticated)."""
|
|
|
|
assert await async_setup_component(
|
|
hass, DOMAIN, {DOMAIN: {CONF_MOBILE_BEACONS: ["Car 1"]}}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch("homeassistant.components.device_tracker.legacy.update_config"):
|
|
return await hass_client_no_auth()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def setup_zones(event_loop, hass):
|
|
"""Set up Zone config in HA."""
|
|
assert await async_setup_component(
|
|
hass,
|
|
zone.DOMAIN,
|
|
{
|
|
"zone": {
|
|
"name": "Home",
|
|
"latitude": HOME_LATITUDE,
|
|
"longitude": HOME_LONGITUDE,
|
|
"radius": 100,
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.fixture
|
|
async def webhook_id(hass, geofency_client):
|
|
"""Initialize the Geofency component and get the webhook_id."""
|
|
await async_process_ha_core_config(
|
|
hass,
|
|
{"internal_url": "http://example.local:8123"},
|
|
)
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM, result
|
|
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
|
|
|
await hass.async_block_till_done()
|
|
return result["result"].data["webhook_id"]
|
|
|
|
|
|
async def test_data_validation(geofency_client, webhook_id):
|
|
"""Test data validation."""
|
|
url = f"/api/webhook/{webhook_id}"
|
|
|
|
# No data
|
|
req = await geofency_client.post(url)
|
|
assert req.status == HTTPStatus.UNPROCESSABLE_ENTITY
|
|
|
|
missing_attributes = ["address", "device", "entry", "latitude", "longitude", "name"]
|
|
|
|
# missing attributes
|
|
for attribute in missing_attributes:
|
|
copy = GPS_ENTER_HOME.copy()
|
|
del copy[attribute]
|
|
req = await geofency_client.post(url, data=copy)
|
|
assert req.status == HTTPStatus.UNPROCESSABLE_ENTITY
|
|
|
|
|
|
async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id):
|
|
"""Test GPS based zone enter and exit."""
|
|
url = f"/api/webhook/{webhook_id}"
|
|
|
|
# Enter the Home zone
|
|
req = await geofency_client.post(url, data=GPS_ENTER_HOME)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(GPS_ENTER_HOME["device"])
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_HOME
|
|
|
|
# Exit the Home zone
|
|
req = await geofency_client.post(url, data=GPS_EXIT_HOME)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(GPS_EXIT_HOME["device"])
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_NOT_HOME
|
|
|
|
# Exit the Home zone with "Send Current Position" enabled
|
|
data = GPS_EXIT_HOME.copy()
|
|
data["currentLatitude"] = NOT_HOME_LATITUDE
|
|
data["currentLongitude"] = NOT_HOME_LONGITUDE
|
|
|
|
req = await geofency_client.post(url, data=data)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(GPS_EXIT_HOME["device"])
|
|
current_latitude = hass.states.get(f"device_tracker.{device_name}").attributes[
|
|
"latitude"
|
|
]
|
|
assert current_latitude == NOT_HOME_LATITUDE
|
|
current_longitude = hass.states.get(f"device_tracker.{device_name}").attributes[
|
|
"longitude"
|
|
]
|
|
assert current_longitude == NOT_HOME_LONGITUDE
|
|
|
|
dev_reg = dr.async_get(hass)
|
|
assert len(dev_reg.devices) == 1
|
|
|
|
ent_reg = er.async_get(hass)
|
|
assert len(ent_reg.entities) == 1
|
|
|
|
|
|
async def test_beacon_enter_and_exit_home(hass, geofency_client, webhook_id):
|
|
"""Test iBeacon based zone enter and exit - a.k.a stationary iBeacon."""
|
|
url = f"/api/webhook/{webhook_id}"
|
|
|
|
# Enter the Home zone
|
|
req = await geofency_client.post(url, data=BEACON_ENTER_HOME)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(f"beacon_{BEACON_ENTER_HOME['name']}")
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_HOME
|
|
|
|
# Exit the Home zone
|
|
req = await geofency_client.post(url, data=BEACON_EXIT_HOME)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(f"beacon_{BEACON_ENTER_HOME['name']}")
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_NOT_HOME
|
|
|
|
|
|
async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id):
|
|
"""Test use of mobile iBeacon."""
|
|
url = f"/api/webhook/{webhook_id}"
|
|
|
|
# Enter the Car away from Home zone
|
|
req = await geofency_client.post(url, data=BEACON_ENTER_CAR)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(f"beacon_{BEACON_ENTER_CAR['name']}")
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_NOT_HOME
|
|
|
|
# Exit the Car away from Home zone
|
|
req = await geofency_client.post(url, data=BEACON_EXIT_CAR)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(f"beacon_{BEACON_ENTER_CAR['name']}")
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_NOT_HOME
|
|
|
|
# Enter the Car in the Home zone
|
|
data = BEACON_ENTER_CAR.copy()
|
|
data["latitude"] = HOME_LATITUDE
|
|
data["longitude"] = HOME_LONGITUDE
|
|
req = await geofency_client.post(url, data=data)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(f"beacon_{data['name']}")
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_HOME
|
|
|
|
# Exit the Car in the Home zone
|
|
req = await geofency_client.post(url, data=data)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(f"beacon_{data['name']}")
|
|
state_name = hass.states.get(f"device_tracker.{device_name}").state
|
|
assert state_name == STATE_HOME
|
|
|
|
|
|
async def test_load_unload_entry(hass, geofency_client, webhook_id):
|
|
"""Test that the appropriate dispatch signals are added and removed."""
|
|
url = f"/api/webhook/{webhook_id}"
|
|
|
|
# Enter the Home zone
|
|
req = await geofency_client.post(url, data=GPS_ENTER_HOME)
|
|
await hass.async_block_till_done()
|
|
assert req.status == HTTPStatus.OK
|
|
device_name = slugify(GPS_ENTER_HOME["device"])
|
|
state_1 = hass.states.get(f"device_tracker.{device_name}")
|
|
assert state_1.state == STATE_HOME
|
|
|
|
assert len(hass.data[DOMAIN]["devices"]) == 1
|
|
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
|
|
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert len(hass.data[DOMAIN]["devices"]) == 0
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
state_2 = hass.states.get(f"device_tracker.{device_name}")
|
|
assert state_2 is not None
|
|
assert state_1 is not state_2
|
|
|
|
assert state_2.state == STATE_HOME
|
|
assert state_2.attributes[ATTR_LATITUDE] == HOME_LATITUDE
|
|
assert state_2.attributes[ATTR_LONGITUDE] == HOME_LONGITUDE
|