mirror of
https://github.com/home-assistant/core.git
synced 2026-03-08 23:21:41 +01:00
Compare commits
7 Commits
2026.3.0b3
...
2026.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f9faa53a1 | ||
|
|
718607a758 | ||
|
|
3789156559 | ||
|
|
042ce6f2de | ||
|
|
0a5908002f | ||
|
|
3a5f71e10a | ||
|
|
04e4b05ab0 |
@@ -1,55 +0,0 @@
|
||||
"""Diagnostics support for AWS S3."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.backup import (
|
||||
DATA_MANAGER as BACKUP_DATA_MANAGER,
|
||||
BackupManager,
|
||||
)
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
CONF_ACCESS_KEY_ID,
|
||||
CONF_BUCKET,
|
||||
CONF_PREFIX,
|
||||
CONF_SECRET_ACCESS_KEY,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import S3ConfigEntry
|
||||
from .helpers import async_list_backups_from_s3
|
||||
|
||||
TO_REDACT = (CONF_ACCESS_KEY_ID, CONF_SECRET_ACCESS_KEY)
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
entry: S3ConfigEntry,
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
backup_manager: BackupManager = hass.data[BACKUP_DATA_MANAGER]
|
||||
backups = await async_list_backups_from_s3(
|
||||
coordinator.client,
|
||||
bucket=entry.data[CONF_BUCKET],
|
||||
prefix=entry.data.get(CONF_PREFIX, ""),
|
||||
)
|
||||
|
||||
data = {
|
||||
"coordinator_data": dataclasses.asdict(coordinator.data),
|
||||
"config": {
|
||||
**entry.data,
|
||||
**entry.options,
|
||||
},
|
||||
"backup_agents": [
|
||||
{"name": agent.name}
|
||||
for agent in backup_manager.backup_agents.values()
|
||||
if agent.domain == DOMAIN
|
||||
],
|
||||
"backup": [backup.as_dict() for backup in backups],
|
||||
}
|
||||
|
||||
return async_redact_data(data, TO_REDACT)
|
||||
@@ -43,7 +43,7 @@ rules:
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
diagnostics: todo
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: S3 is a cloud service that is not discovered on the network.
|
||||
|
||||
@@ -275,8 +275,11 @@ class FibaroController:
|
||||
# otherwise add the first visible device in the group
|
||||
# which is a hack, but solves a problem with FGT having
|
||||
# hidden compatibility devices before the real device
|
||||
if last_climate_parent != device.parent_fibaro_id or (
|
||||
device.has_endpoint_id and last_endpoint != device.endpoint_id
|
||||
# Second hack is for quickapps which have parent id 0 and no children
|
||||
if (
|
||||
last_climate_parent != device.parent_fibaro_id
|
||||
or (device.has_endpoint_id and last_endpoint != device.endpoint_id)
|
||||
or device.parent_fibaro_id == 0
|
||||
):
|
||||
_LOGGER.debug("Handle separately")
|
||||
self.fibaro_devices[platform].append(device)
|
||||
|
||||
@@ -29,7 +29,7 @@ from homeassistant.core import (
|
||||
)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||
from homeassistant.helpers.event import (
|
||||
TrackTemplate,
|
||||
TrackTemplateResult,
|
||||
@@ -264,16 +264,23 @@ class TemplateEntity(AbstractTemplateEntity):
|
||||
return None
|
||||
return cast(str, self._blueprint_inputs[CONF_USE_BLUEPRINT][CONF_PATH])
|
||||
|
||||
def _get_this_variable(self) -> TemplateStateFromEntityId:
|
||||
"""Create a this variable for the entity."""
|
||||
if self._preview_callback:
|
||||
preview_entity_id = async_generate_entity_id(
|
||||
self._entity_id_format, self._attr_name or "preview", hass=self.hass
|
||||
)
|
||||
return TemplateStateFromEntityId(self.hass, preview_entity_id)
|
||||
|
||||
return TemplateStateFromEntityId(self.hass, self.entity_id)
|
||||
|
||||
def _render_script_variables(self) -> dict[str, Any]:
|
||||
"""Render configured variables."""
|
||||
if isinstance(self._run_variables, dict):
|
||||
return self._run_variables
|
||||
|
||||
return self._run_variables.async_render(
|
||||
self.hass,
|
||||
{
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
},
|
||||
self.hass, {"this": self._get_this_variable()}
|
||||
)
|
||||
|
||||
def setup_state_template(
|
||||
@@ -451,7 +458,7 @@ class TemplateEntity(AbstractTemplateEntity):
|
||||
has_availability_template = False
|
||||
|
||||
variables = {
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
"this": self._get_this_variable(),
|
||||
**self._render_script_variables(),
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2026
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "0b3"
|
||||
PATCH_VERSION: Final = "0"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2026.3.0b3"
|
||||
version = "2026.3.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# serializer version: 1
|
||||
# name: test_entry_diagnostics[large]
|
||||
dict({
|
||||
'backup': list([
|
||||
dict({
|
||||
'addons': list([
|
||||
]),
|
||||
'backup_id': '23e64aec',
|
||||
'database_included': True,
|
||||
'date': '2024-11-22T11:48:48.727189+01:00',
|
||||
'extra_metadata': dict({
|
||||
}),
|
||||
'folders': list([
|
||||
]),
|
||||
'homeassistant_included': True,
|
||||
'homeassistant_version': '2024.12.0.dev0',
|
||||
'name': 'Core 2024.12.0.dev0',
|
||||
'protected': False,
|
||||
'size': 20971520,
|
||||
}),
|
||||
]),
|
||||
'backup_agents': list([
|
||||
dict({
|
||||
'name': 'test',
|
||||
}),
|
||||
]),
|
||||
'config': dict({
|
||||
'access_key_id': '**REDACTED**',
|
||||
'bucket': 'test',
|
||||
'endpoint_url': 'https://s3.eu-south-1.amazonaws.com',
|
||||
'secret_access_key': '**REDACTED**',
|
||||
}),
|
||||
'coordinator_data': dict({
|
||||
'all_backups_size': 20971520,
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_entry_diagnostics[small]
|
||||
dict({
|
||||
'backup': list([
|
||||
dict({
|
||||
'addons': list([
|
||||
]),
|
||||
'backup_id': '23e64aec',
|
||||
'database_included': True,
|
||||
'date': '2024-11-22T11:48:48.727189+01:00',
|
||||
'extra_metadata': dict({
|
||||
}),
|
||||
'folders': list([
|
||||
]),
|
||||
'homeassistant_included': True,
|
||||
'homeassistant_version': '2024.12.0.dev0',
|
||||
'name': 'Core 2024.12.0.dev0',
|
||||
'protected': False,
|
||||
'size': 1048576,
|
||||
}),
|
||||
]),
|
||||
'backup_agents': list([
|
||||
dict({
|
||||
'name': 'test',
|
||||
}),
|
||||
]),
|
||||
'config': dict({
|
||||
'access_key_id': '**REDACTED**',
|
||||
'bucket': 'test',
|
||||
'endpoint_url': 'https://s3.eu-south-1.amazonaws.com',
|
||||
'secret_access_key': '**REDACTED**',
|
||||
}),
|
||||
'coordinator_data': dict({
|
||||
'all_backups_size': 1048576,
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
@@ -1,29 +0,0 @@
|
||||
"""Tests for AWS S3 diagnostics."""
|
||||
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.backup import DOMAIN as BACKUP_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
async def test_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test config entry diagnostics."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
|
||||
== snapshot
|
||||
)
|
||||
@@ -286,6 +286,68 @@ def mock_thermostat_with_operating_mode() -> Mock:
|
||||
return climate
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_thermostat_quickapp_1() -> Mock:
|
||||
"""Fixture for a thermostat."""
|
||||
climate = Mock()
|
||||
climate.fibaro_id = 6
|
||||
climate.parent_fibaro_id = 0
|
||||
climate.has_endpoint_id = False
|
||||
climate.name = "Test climate"
|
||||
climate.room_id = 1
|
||||
climate.dead = False
|
||||
climate.visible = True
|
||||
climate.enabled = True
|
||||
climate.type = "com.fibaro.hvacSystemHeat"
|
||||
climate.base_type = "com.fibaro.hvacSystem"
|
||||
climate.properties = {"manufacturer": ""}
|
||||
climate.actions = {"setHeatingThermostatSetpoint": 1, "setThermostatMode": 1}
|
||||
climate.supported_features = {}
|
||||
climate.has_supported_operating_modes = False
|
||||
climate.has_supported_thermostat_modes = True
|
||||
climate.supported_thermostat_modes = ["Off", "Heat"]
|
||||
climate.has_thermostat_mode = True
|
||||
climate.thermostat_mode = "Heat"
|
||||
climate.has_unit = False
|
||||
climate.has_heating_thermostat_setpoint = False
|
||||
climate.has_heating_thermostat_setpoint_future = False
|
||||
value_mock = Mock()
|
||||
value_mock.has_value = False
|
||||
climate.value = value_mock
|
||||
return climate
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_thermostat_quickapp_2() -> Mock:
|
||||
"""Fixture for a thermostat."""
|
||||
climate = Mock()
|
||||
climate.fibaro_id = 7
|
||||
climate.parent_fibaro_id = 0
|
||||
climate.has_endpoint_id = False
|
||||
climate.name = "Test climate 2"
|
||||
climate.room_id = 1
|
||||
climate.dead = False
|
||||
climate.visible = True
|
||||
climate.enabled = True
|
||||
climate.type = "com.fibaro.hvacSystemHeat"
|
||||
climate.base_type = "com.fibaro.hvacSystem"
|
||||
climate.properties = {"manufacturer": ""}
|
||||
climate.actions = {"setHeatingThermostatSetpoint": 1, "setThermostatMode": 1}
|
||||
climate.supported_features = {}
|
||||
climate.has_supported_operating_modes = False
|
||||
climate.has_supported_thermostat_modes = True
|
||||
climate.supported_thermostat_modes = ["Off", "Heat"]
|
||||
climate.has_thermostat_mode = True
|
||||
climate.thermostat_mode = "Heat"
|
||||
climate.has_unit = False
|
||||
climate.has_heating_thermostat_setpoint = False
|
||||
climate.has_heating_thermostat_setpoint_future = False
|
||||
value_mock = Mock()
|
||||
value_mock.has_value = False
|
||||
climate.value = value_mock
|
||||
return climate
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_fan_device() -> Mock:
|
||||
"""Fixture for a fan endpoint of a thermostat device."""
|
||||
|
||||
@@ -41,6 +41,34 @@ async def test_climate_setup(
|
||||
)
|
||||
|
||||
|
||||
async def test_climate_setup_2_quickapps(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_fibaro_client: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_thermostat_quickapp_1: Mock,
|
||||
mock_thermostat_quickapp_2: Mock,
|
||||
mock_room: Mock,
|
||||
) -> None:
|
||||
"""Test that the climate creates entities for more than one QuickApp."""
|
||||
|
||||
# Arrange
|
||||
mock_fibaro_client.read_rooms.return_value = [mock_room]
|
||||
mock_fibaro_client.read_devices.return_value = [
|
||||
mock_thermostat_quickapp_1,
|
||||
mock_thermostat_quickapp_2,
|
||||
]
|
||||
|
||||
with patch("homeassistant.components.fibaro.PLATFORMS", [Platform.CLIMATE]):
|
||||
# Act
|
||||
await init_integration(hass, mock_config_entry)
|
||||
# Assert
|
||||
entry1 = entity_registry.async_get("climate.room_1_test_climate_6")
|
||||
assert entry1
|
||||
entry2 = entity_registry.async_get("climate.room_1_test_climate_2_7")
|
||||
assert entry2
|
||||
|
||||
|
||||
async def test_hvac_mode_preset(
|
||||
hass: HomeAssistant,
|
||||
mock_fibaro_client: Mock,
|
||||
|
||||
@@ -1853,3 +1853,69 @@ async def test_preview_error(
|
||||
# Test No preview is created
|
||||
with pytest.raises(TimeoutError):
|
||||
await client.receive_json(timeout=0.01)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("step_id", "user_input", "expected_state"),
|
||||
[
|
||||
(
|
||||
"sensor",
|
||||
{
|
||||
"name": "",
|
||||
"state": "{{ this.state }}",
|
||||
},
|
||||
"unknown",
|
||||
),
|
||||
(
|
||||
"binary_sensor",
|
||||
{
|
||||
"name": "",
|
||||
"state": "{{ this.state }}",
|
||||
},
|
||||
"off",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_preview_this_variable(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
step_id: str,
|
||||
user_input: dict,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test 'this' variable will not produce an error when rendering a template."""
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": step_id},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == step_id
|
||||
assert result["errors"] is None
|
||||
assert result["preview"] == "template"
|
||||
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "template/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": user_input,
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert msg["result"] is None
|
||||
|
||||
# Verify we do not get an error and that we receive a preview state.
|
||||
msg = await client.receive_json()
|
||||
assert "error" not in msg["event"]
|
||||
assert msg["event"]["state"] == expected_state
|
||||
|
||||
Reference in New Issue
Block a user