* Mark executor jobs as background unless created from a tracked task If the current task is not tracked the executor job should not be a background task to avoid delaying startup and shutdown. Currently any executor job created in a untracked task or background task would end up being tracked and delaying startup/shutdown * import exec has the same issue * Avoid tracking import executor jobs There is no reason to track these jobs as they are always awaited and we do not want to support fire and forget import executor jobs * fix xiaomi_miio * lots of fire time changed without background await * revert changes moved to other PR * more * more * more * m * m * p * fix fire and forget tests * scrape * sonos * system * more * capture callback before block * coverage * more * more races * more races * more * missed some * more fixes * missed some more * fix * remove unneeded * one more race * two
624 lines
19 KiB
Python
624 lines
19 KiB
Python
"""The tests for the Scrape sensor platform."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from unittest.mock import patch
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
import pytest
|
|
|
|
from homeassistant.components.rest.const import DEFAULT_METHOD
|
|
from homeassistant.components.rest.data import DEFAULT_TIMEOUT
|
|
from homeassistant.components.scrape.const import (
|
|
CONF_ENCODING,
|
|
CONF_INDEX,
|
|
CONF_SELECT,
|
|
DEFAULT_ENCODING,
|
|
DEFAULT_SCAN_INTERVAL,
|
|
DEFAULT_VERIFY_SSL,
|
|
)
|
|
from homeassistant.components.sensor import (
|
|
CONF_STATE_CLASS,
|
|
DOMAIN as SENSOR_DOMAIN,
|
|
SensorDeviceClass,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.const import (
|
|
CONF_DEVICE_CLASS,
|
|
CONF_ICON,
|
|
CONF_METHOD,
|
|
CONF_NAME,
|
|
CONF_RESOURCE,
|
|
CONF_TIMEOUT,
|
|
CONF_UNIQUE_ID,
|
|
CONF_UNIT_OF_MEASUREMENT,
|
|
CONF_VALUE_TEMPLATE,
|
|
CONF_VERIFY_SSL,
|
|
STATE_UNAVAILABLE,
|
|
STATE_UNKNOWN,
|
|
UnitOfTemperature,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.helpers.trigger_template_entity import (
|
|
CONF_AVAILABILITY,
|
|
CONF_PICTURE,
|
|
)
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from . import MockRestData, return_integration_config
|
|
|
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
|
|
|
DOMAIN = "scrape"
|
|
|
|
|
|
async def test_scrape_sensor(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor minimal."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[{"select": ".current-version h1", "name": "HA version"}]
|
|
)
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_version")
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
|
|
|
|
async def test_scrape_sensor_value_template(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with value template."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".current-version h1",
|
|
"name": "HA version",
|
|
"value_template": "{{ value.split(':')[1] }}",
|
|
}
|
|
]
|
|
)
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_version")
|
|
assert state.state == "2021.12.10"
|
|
|
|
|
|
async def test_scrape_uom_and_classes(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor for unit of measurement, device class and state class."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".current-temp h3",
|
|
"name": "Current Temp",
|
|
"value_template": "{{ value.split(':')[1] }}",
|
|
"unit_of_measurement": "°C",
|
|
"device_class": "temperature",
|
|
"state_class": "measurement",
|
|
}
|
|
]
|
|
)
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_uom_and_classes")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.current_temp")
|
|
assert state.state == "22.1"
|
|
assert state.attributes[CONF_UNIT_OF_MEASUREMENT] == UnitOfTemperature.CELSIUS
|
|
assert state.attributes[CONF_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE
|
|
assert state.attributes[CONF_STATE_CLASS] == SensorStateClass.MEASUREMENT
|
|
|
|
|
|
async def test_scrape_unique_id(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor for unique id."""
|
|
config = {
|
|
DOMAIN: return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".current-temp h3",
|
|
"name": "Current Temp",
|
|
"value_template": "{{ value.split(':')[1] }}",
|
|
"unique_id": "very_unique_id",
|
|
}
|
|
]
|
|
)
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_uom_and_classes")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.current_temp")
|
|
assert state.state == "22.1"
|
|
|
|
registry = er.async_get(hass)
|
|
entry = registry.async_get("sensor.current_temp")
|
|
assert entry
|
|
assert entry.unique_id == "very_unique_id"
|
|
|
|
|
|
async def test_scrape_sensor_authentication(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with authentication."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
authentication="digest",
|
|
username="user@secret.com",
|
|
password="12345678",
|
|
sensors=[
|
|
{
|
|
"select": ".return",
|
|
"name": "Auth page",
|
|
},
|
|
],
|
|
),
|
|
return_integration_config(
|
|
username="user@secret.com",
|
|
password="12345678",
|
|
sensors=[
|
|
{
|
|
"select": ".return",
|
|
"name": "Auth page2",
|
|
},
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor_authentication")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.auth_page")
|
|
assert state.state == "secret text"
|
|
state2 = hass.states.get("sensor.auth_page2")
|
|
assert state2.state == "secret text"
|
|
|
|
|
|
async def test_scrape_sensor_no_data(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test Scrape sensor fails on no data."""
|
|
config = {
|
|
DOMAIN: return_integration_config(
|
|
sensors=[{"select": ".current-version h1", "name": "HA version"}]
|
|
)
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor_no_data")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_version")
|
|
assert state is None
|
|
|
|
assert "Platform scrape not ready yet" in caplog.text
|
|
|
|
|
|
async def test_scrape_sensor_no_data_refresh(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor no data on refresh."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[{"select": ".current-version h1", "name": "HA version"}]
|
|
)
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_version")
|
|
assert state
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
|
|
mocker.payload = "test_scrape_sensor_no_data"
|
|
async_fire_time_changed(hass, dt_util.utcnow() + DEFAULT_SCAN_INTERVAL)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get("sensor.ha_version")
|
|
assert state is not None
|
|
assert state.state == STATE_UNAVAILABLE
|
|
|
|
|
|
async def test_scrape_sensor_attribute_and_tag(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with attribute and tag."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"index": 1,
|
|
"select": "div",
|
|
"name": "HA class",
|
|
"attribute": "class",
|
|
},
|
|
{"select": "template", "name": "HA template"},
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_class")
|
|
assert state.state == "['links']"
|
|
state2 = hass.states.get("sensor.ha_template")
|
|
assert state2.state == "Trying to get"
|
|
|
|
|
|
async def test_scrape_sensor_device_date(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with a device of type DATE."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".release-date",
|
|
"name": "HA Date",
|
|
"device_class": "date",
|
|
"value_template": "{{ strptime(value, '%B %d, %Y').strftime('%Y-%m-%d') }}",
|
|
}
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_date")
|
|
assert state.state == "2022-01-17"
|
|
|
|
|
|
async def test_scrape_sensor_device_date_errors(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with a device of type DATE."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".current-version h1",
|
|
"name": "HA Date",
|
|
"device_class": "date",
|
|
}
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_date")
|
|
assert state.state == STATE_UNKNOWN
|
|
|
|
|
|
async def test_scrape_sensor_device_timestamp(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with a device of type TIMESTAMP."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".utc-time",
|
|
"name": "HA Timestamp",
|
|
"device_class": "timestamp",
|
|
}
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_timestamp")
|
|
assert state.state == "2022-12-22T13:15:30+00:00"
|
|
|
|
|
|
async def test_scrape_sensor_device_timestamp_error(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with a device of type TIMESTAMP."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".current-time",
|
|
"name": "HA Timestamp",
|
|
"device_class": "timestamp",
|
|
}
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_timestamp")
|
|
assert state.state == STATE_UNKNOWN
|
|
|
|
|
|
async def test_scrape_sensor_errors(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor handle errors."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"index": 5,
|
|
"select": "div",
|
|
"name": "HA class",
|
|
"attribute": "class",
|
|
},
|
|
{
|
|
"select": "div",
|
|
"name": "HA class2",
|
|
"attribute": "classes",
|
|
},
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_class")
|
|
assert state.state == STATE_UNKNOWN
|
|
state2 = hass.states.get("sensor.ha_class2")
|
|
assert state2.state == STATE_UNKNOWN
|
|
|
|
|
|
async def test_scrape_sensor_unique_id(hass: HomeAssistant) -> None:
|
|
"""Test Scrape sensor with unique_id."""
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
"select": ".current-version h1",
|
|
"name": "HA version",
|
|
"unique_id": "ha_version_unique_id",
|
|
}
|
|
]
|
|
)
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.ha_version")
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
|
|
entity_reg = er.async_get(hass)
|
|
entity = entity_reg.async_get("sensor.ha_version")
|
|
|
|
assert entity.unique_id == "ha_version_unique_id"
|
|
|
|
|
|
async def test_setup_config_entry(
|
|
hass: HomeAssistant, loaded_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test setup from config entry."""
|
|
|
|
state = hass.states.get("sensor.current_version")
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
|
|
entity_reg = er.async_get(hass)
|
|
entity = entity_reg.async_get("sensor.current_version")
|
|
|
|
assert entity.unique_id == "3699ef88-69e6-11ed-a1eb-0242ac120002"
|
|
|
|
|
|
async def test_templates_with_yaml(hass: HomeAssistant) -> None:
|
|
"""Test the Scrape sensor from yaml config with templates."""
|
|
|
|
hass.states.async_set("sensor.input1", "on")
|
|
hass.states.async_set("sensor.input2", "on")
|
|
await hass.async_block_till_done()
|
|
|
|
config = {
|
|
DOMAIN: [
|
|
return_integration_config(
|
|
sensors=[
|
|
{
|
|
CONF_NAME: "Get values with template",
|
|
CONF_SELECT: ".current-version h1",
|
|
CONF_INDEX: 0,
|
|
CONF_UNIQUE_ID: "3699ef88-69e6-11ed-a1eb-0242ac120002",
|
|
CONF_ICON: '{% if states("sensor.input1")=="on" %} mdi:on {% else %} mdi:off {% endif %}',
|
|
CONF_PICTURE: '{% if states("sensor.input1")=="on" %} /local/picture1.jpg {% else %} /local/picture2.jpg {% endif %}',
|
|
CONF_AVAILABILITY: '{{ states("sensor.input2")=="on" }}',
|
|
}
|
|
]
|
|
)
|
|
]
|
|
}
|
|
|
|
mocker = MockRestData("test_scrape_sensor")
|
|
with patch(
|
|
"homeassistant.components.rest.RestData",
|
|
return_value=mocker,
|
|
):
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("sensor.get_values_with_template")
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
assert state.attributes[CONF_ICON] == "mdi:on"
|
|
assert state.attributes["entity_picture"] == "/local/picture1.jpg"
|
|
|
|
hass.states.async_set("sensor.input1", "off")
|
|
await hass.async_block_till_done()
|
|
|
|
async_fire_time_changed(
|
|
hass,
|
|
dt_util.utcnow() + timedelta(minutes=10),
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get("sensor.get_values_with_template")
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
assert state.attributes[CONF_ICON] == "mdi:off"
|
|
assert state.attributes["entity_picture"] == "/local/picture2.jpg"
|
|
|
|
hass.states.async_set("sensor.input2", "off")
|
|
await hass.async_block_till_done()
|
|
|
|
async_fire_time_changed(
|
|
hass,
|
|
dt_util.utcnow() + timedelta(minutes=20),
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get("sensor.get_values_with_template")
|
|
assert state.state == STATE_UNAVAILABLE
|
|
|
|
hass.states.async_set("sensor.input1", "on")
|
|
hass.states.async_set("sensor.input2", "on")
|
|
await hass.async_block_till_done()
|
|
|
|
async_fire_time_changed(
|
|
hass,
|
|
dt_util.utcnow() + timedelta(minutes=30),
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get("sensor.get_values_with_template")
|
|
assert state.state == "Current Version: 2021.12.10"
|
|
assert state.attributes[CONF_ICON] == "mdi:on"
|
|
assert state.attributes["entity_picture"] == "/local/picture1.jpg"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"get_config",
|
|
[
|
|
{
|
|
CONF_RESOURCE: "https://www.home-assistant.io",
|
|
CONF_METHOD: DEFAULT_METHOD,
|
|
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
|
|
CONF_TIMEOUT: DEFAULT_TIMEOUT,
|
|
CONF_ENCODING: DEFAULT_ENCODING,
|
|
SENSOR_DOMAIN: [
|
|
{
|
|
CONF_SELECT: ".current-version h1",
|
|
CONF_NAME: "Current version",
|
|
CONF_VALUE_TEMPLATE: "{{ value.split(':')[1] }}",
|
|
CONF_INDEX: 0,
|
|
CONF_UNIQUE_ID: "3699ef88-69e6-11ed-a1eb-0242ac120002",
|
|
CONF_AVAILABILITY: '{{ states("sensor.input1")=="on" }}',
|
|
}
|
|
],
|
|
}
|
|
],
|
|
)
|
|
async def test_availability(
|
|
hass: HomeAssistant,
|
|
loaded_entry: MockConfigEntry,
|
|
freezer: FrozenDateTimeFactory,
|
|
) -> None:
|
|
"""Test availability when setup from config entry."""
|
|
|
|
hass.states.async_set("sensor.input1", "on")
|
|
freezer.tick(timedelta(minutes=10))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get("sensor.current_version")
|
|
assert state.state == "2021.12.10"
|
|
|
|
hass.states.async_set("sensor.input1", "off")
|
|
await hass.async_block_till_done()
|
|
|
|
freezer.tick(timedelta(minutes=10))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get("sensor.current_version")
|
|
assert state.state == STATE_UNAVAILABLE
|