hass-core/tests/components/scrape/test_sensor.py
J. Nick Koston 9a79320861
Mark executor jobs as background unless created from a tracked task (#114450)
* 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
2024-03-30 00:16:53 -04:00

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