mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27:10 +00:00
Add sensor platform to backup integration (#138663)
* add sensor platform to backup integration * adjust namings, remove system integration flag * add first simple test * apply review comments * fix test * add sensor tests * adjustements to use backup helper * remove obsolet async_get_manager from init * unsubscribe from events on entry unload * add configuration_url * fix doc string * fix sensor tests * mark async_unsubscribe as callback * set integration_type service * extend sensor test * set integration_type on correct integration :) * fix after online conflict resolution * add sensor update tests * simplify the sensor update tests * avoid io during tests * Add comment --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
265a2ace90
commit
e96e95c32d
@ -1,7 +1,9 @@
|
||||
"""The Backup integration."""
|
||||
|
||||
from homeassistant.config_entries import SOURCE_SYSTEM
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv, discovery_flow
|
||||
from homeassistant.helpers.backup import DATA_BACKUP
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@ -18,10 +20,12 @@ from .agent import (
|
||||
)
|
||||
from .config import BackupConfig, CreateBackupParametersDict
|
||||
from .const import DATA_MANAGER, DOMAIN
|
||||
from .coordinator import BackupConfigEntry, BackupDataUpdateCoordinator
|
||||
from .http import async_register_http_views
|
||||
from .manager import (
|
||||
BackupManager,
|
||||
BackupManagerError,
|
||||
BackupPlatformEvent,
|
||||
BackupPlatformProtocol,
|
||||
BackupReaderWriter,
|
||||
BackupReaderWriterError,
|
||||
@ -52,6 +56,7 @@ __all__ = [
|
||||
"BackupConfig",
|
||||
"BackupManagerError",
|
||||
"BackupNotFound",
|
||||
"BackupPlatformEvent",
|
||||
"BackupPlatformProtocol",
|
||||
"BackupReaderWriter",
|
||||
"BackupReaderWriterError",
|
||||
@ -74,6 +79,8 @@ __all__ = [
|
||||
"suggested_filename_from_name_date",
|
||||
]
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
||||
|
||||
@ -128,4 +135,28 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
async_register_http_views(hass)
|
||||
|
||||
discovery_flow.async_create_flow(
|
||||
hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: BackupConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
backup_manager: BackupManager = hass.data[DATA_MANAGER]
|
||||
coordinator = BackupDataUpdateCoordinator(hass, entry, backup_manager)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.async_on_unload(coordinator.async_unsubscribe)
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: BackupConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
21
homeassistant/components/backup/config_flow.py
Normal file
21
homeassistant/components/backup/config_flow.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Config flow for Home Assistant Backup integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class BackupConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Home Assistant Backup."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_system(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
return self.async_create_entry(title="Backup", data={})
|
81
homeassistant/components/backup/coordinator.py
Normal file
81
homeassistant/components/backup/coordinator.py
Normal file
@ -0,0 +1,81 @@
|
||||
"""Coordinator for Home Assistant Backup integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.backup import (
|
||||
async_subscribe_events,
|
||||
async_subscribe_platform_events,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .manager import (
|
||||
BackupManager,
|
||||
BackupManagerState,
|
||||
BackupPlatformEvent,
|
||||
ManagerStateEvent,
|
||||
)
|
||||
|
||||
type BackupConfigEntry = ConfigEntry[BackupDataUpdateCoordinator]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BackupCoordinatorData:
|
||||
"""Class to hold backup data."""
|
||||
|
||||
backup_manager_state: BackupManagerState
|
||||
last_successful_automatic_backup: datetime | None
|
||||
next_scheduled_automatic_backup: datetime | None
|
||||
|
||||
|
||||
class BackupDataUpdateCoordinator(DataUpdateCoordinator[BackupCoordinatorData]):
|
||||
"""Class to retrieve backup status."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
backup_manager: BackupManager,
|
||||
) -> None:
|
||||
"""Initialize coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=DOMAIN,
|
||||
update_interval=None,
|
||||
)
|
||||
self.unsubscribe: list[Callable[[], None]] = [
|
||||
async_subscribe_events(hass, self._on_event),
|
||||
async_subscribe_platform_events(hass, self._on_event),
|
||||
]
|
||||
|
||||
self.backup_manager = backup_manager
|
||||
|
||||
@callback
|
||||
def _on_event(self, event: ManagerStateEvent | BackupPlatformEvent) -> None:
|
||||
"""Handle new event."""
|
||||
LOGGER.debug("Received backup event: %s", event)
|
||||
self.config_entry.async_create_task(self.hass, self.async_refresh())
|
||||
|
||||
async def _async_update_data(self) -> BackupCoordinatorData:
|
||||
"""Update backup manager data."""
|
||||
return BackupCoordinatorData(
|
||||
self.backup_manager.state,
|
||||
self.backup_manager.config.data.last_completed_automatic_backup,
|
||||
self.backup_manager.config.data.schedule.next_automatic_backup,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_unsubscribe(self) -> None:
|
||||
"""Unsubscribe from events."""
|
||||
for unsub in self.unsubscribe:
|
||||
unsub()
|
36
homeassistant/components/backup/entity.py
Normal file
36
homeassistant/components/backup/entity.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Base for backup entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.const import __version__ as HA_VERSION
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import BackupDataUpdateCoordinator
|
||||
|
||||
|
||||
class BackupManagerEntity(CoordinatorEntity[BackupDataUpdateCoordinator]):
|
||||
"""Base entity for backup manager."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: BackupDataUpdateCoordinator,
|
||||
entity_description: EntityDescription,
|
||||
) -> None:
|
||||
"""Initialize base entity."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = entity_description.key
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, "backup_manager")},
|
||||
manufacturer="Home Assistant",
|
||||
model="Home Assistant Backup",
|
||||
sw_version=HA_VERSION,
|
||||
name="Backup",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
configuration_url="homeassistant://config/backup",
|
||||
)
|
@ -229,6 +229,13 @@ class RestoreBackupEvent(ManagerStateEvent):
|
||||
state: RestoreBackupState
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True, slots=True)
|
||||
class BackupPlatformEvent:
|
||||
"""Backup platform class."""
|
||||
|
||||
domain: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True, slots=True)
|
||||
class BlockedEvent(ManagerStateEvent):
|
||||
"""Backup manager blocked, Home Assistant is starting."""
|
||||
@ -355,6 +362,9 @@ class BackupManager:
|
||||
self._backup_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
].backup_event_subscriptions
|
||||
self._backup_platform_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
].backup_platform_event_subscriptions
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up the backup manager."""
|
||||
@ -465,6 +475,9 @@ class BackupManager:
|
||||
LOGGER.debug("%s platforms loaded in total", len(self.platforms))
|
||||
LOGGER.debug("%s agents loaded in total", len(self.backup_agents))
|
||||
LOGGER.debug("%s local agents loaded in total", len(self.local_backup_agents))
|
||||
event = BackupPlatformEvent(domain=integration_domain)
|
||||
for subscription in self._backup_platform_event_subscriptions:
|
||||
subscription(event)
|
||||
|
||||
async def async_pre_backup_actions(self) -> None:
|
||||
"""Perform pre backup actions."""
|
||||
|
@ -5,8 +5,9 @@
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"dependencies": ["http", "websocket_api"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/backup",
|
||||
"integration_type": "system",
|
||||
"integration_type": "service",
|
||||
"iot_class": "calculated",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["cronsim==2.6", "securetar==2025.2.1"]
|
||||
"requirements": ["cronsim==2.6", "securetar==2025.2.1"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
75
homeassistant/components/backup/sensor.py
Normal file
75
homeassistant/components/backup/sensor.py
Normal file
@ -0,0 +1,75 @@
|
||||
"""Sensor platform for Home Assistant Backup integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import BackupConfigEntry, BackupCoordinatorData
|
||||
from .entity import BackupManagerEntity
|
||||
from .manager import BackupManagerState
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BackupSensorEntityDescription(SensorEntityDescription):
|
||||
"""Description for Home Assistant Backup sensor entities."""
|
||||
|
||||
value_fn: Callable[[BackupCoordinatorData], str | datetime | None]
|
||||
|
||||
|
||||
BACKUP_MANAGER_DESCRIPTIONS = (
|
||||
BackupSensorEntityDescription(
|
||||
key="backup_manager_state",
|
||||
translation_key="backup_manager_state",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[state.value for state in BackupManagerState],
|
||||
value_fn=lambda data: data.backup_manager_state,
|
||||
),
|
||||
BackupSensorEntityDescription(
|
||||
key="next_scheduled_automatic_backup",
|
||||
translation_key="next_scheduled_automatic_backup",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda data: data.next_scheduled_automatic_backup,
|
||||
),
|
||||
BackupSensorEntityDescription(
|
||||
key="last_successful_automatic_backup",
|
||||
translation_key="last_successful_automatic_backup",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda data: data.last_successful_automatic_backup,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: BackupConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Sensor set up for backup config entry."""
|
||||
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
BackupManagerSensor(coordinator, description)
|
||||
for description in BACKUP_MANAGER_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
class BackupManagerSensor(BackupManagerEntity, SensorEntity):
|
||||
"""Sensor to track backup manager state."""
|
||||
|
||||
entity_description: BackupSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | datetime | None:
|
||||
"""Return native value of entity."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
@ -22,5 +22,24 @@
|
||||
"name": "Create automatic backup",
|
||||
"description": "Creates a new backup with automatic backup settings."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"backup_manager_state": {
|
||||
"name": "Backup Manager State",
|
||||
"state": {
|
||||
"idle": "Idle",
|
||||
"create_backup": "Creating a backup",
|
||||
"receive_backup": "Receiving a backup",
|
||||
"restore_backup": "Restoring a backup"
|
||||
}
|
||||
},
|
||||
"next_scheduled_automatic_backup": {
|
||||
"name": "Next scheduled automatic backup"
|
||||
},
|
||||
"last_successful_automatic_backup": {
|
||||
"name": "Last successful automatic backup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -611,6 +611,13 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"backup": {
|
||||
"name": "Backup",
|
||||
"integration_type": "service",
|
||||
"config_flow": false,
|
||||
"iot_class": "calculated",
|
||||
"single_config_entry": true
|
||||
},
|
||||
"baf": {
|
||||
"name": "Big Ass Fans",
|
||||
"integration_type": "hub",
|
||||
|
@ -12,7 +12,11 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components.backup import BackupManager, ManagerStateEvent
|
||||
from homeassistant.components.backup import (
|
||||
BackupManager,
|
||||
BackupPlatformEvent,
|
||||
ManagerStateEvent,
|
||||
)
|
||||
|
||||
DATA_BACKUP: HassKey[BackupData] = HassKey("backup_data")
|
||||
DATA_MANAGER: HassKey[BackupManager] = HassKey("backup")
|
||||
@ -25,6 +29,9 @@ class BackupData:
|
||||
backup_event_subscriptions: list[Callable[[ManagerStateEvent], None]] = field(
|
||||
default_factory=list
|
||||
)
|
||||
backup_platform_event_subscriptions: list[Callable[[BackupPlatformEvent], None]] = (
|
||||
field(default_factory=list)
|
||||
)
|
||||
manager_ready: asyncio.Future[None] = field(default_factory=asyncio.Future)
|
||||
|
||||
|
||||
@ -68,3 +75,20 @@ def async_subscribe_events(
|
||||
|
||||
backup_event_subscriptions.append(on_event)
|
||||
return remove_subscription
|
||||
|
||||
|
||||
@callback
|
||||
def async_subscribe_platform_events(
|
||||
hass: HomeAssistant,
|
||||
on_event: Callable[[BackupPlatformEvent], None],
|
||||
) -> Callable[[], None]:
|
||||
"""Subscribe to backup platform events."""
|
||||
backup_platform_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
].backup_platform_event_subscriptions
|
||||
|
||||
def remove_subscription() -> None:
|
||||
backup_platform_event_subscriptions.remove(on_event)
|
||||
|
||||
backup_platform_event_subscriptions.append(on_event)
|
||||
return remove_subscription
|
||||
|
160
tests/components/backup/snapshots/test_sensors.ambr
Normal file
160
tests/components/backup/snapshots/test_sensors.ambr
Normal file
@ -0,0 +1,160 @@
|
||||
# serializer version: 1
|
||||
# name: test_sensors[sensor.backup_backup_manager_state-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'idle',
|
||||
'create_backup',
|
||||
'blocked',
|
||||
'receive_backup',
|
||||
'restore_backup',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.backup_backup_manager_state',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Backup Manager State',
|
||||
'platform': 'backup',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'backup_manager_state',
|
||||
'unique_id': 'backup_manager_state',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.backup_backup_manager_state-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Backup Backup Manager State',
|
||||
'options': list([
|
||||
'idle',
|
||||
'create_backup',
|
||||
'blocked',
|
||||
'receive_backup',
|
||||
'restore_backup',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.backup_backup_manager_state',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'idle',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.backup_last_successful_automatic_backup-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.backup_last_successful_automatic_backup',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Last successful automatic backup',
|
||||
'platform': 'backup',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'last_successful_automatic_backup',
|
||||
'unique_id': 'last_successful_automatic_backup',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.backup_last_successful_automatic_backup-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Backup Last successful automatic backup',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.backup_last_successful_automatic_backup',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.backup_next_scheduled_automatic_backup-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.backup_next_scheduled_automatic_backup',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Next scheduled automatic backup',
|
||||
'platform': 'backup',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'next_scheduled_automatic_backup',
|
||||
'unique_id': 'next_scheduled_automatic_backup',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.backup_next_scheduled_automatic_backup-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Backup Next scheduled automatic backup',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.backup_next_scheduled_automatic_backup',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
@ -6,11 +6,13 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.backup.const import DATA_MANAGER, DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceNotFound
|
||||
|
||||
from .common import setup_backup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@ -141,3 +143,17 @@ async def test_create_automatic_service(
|
||||
)
|
||||
|
||||
generate_backup.assert_called_once_with(**expected_kwargs)
|
||||
|
||||
|
||||
async def test_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test setup backup config entry."""
|
||||
await setup_backup_integration(hass, with_hassio=False)
|
||||
entry = MockConfigEntry(domain=DOMAIN, source=SOURCE_SYSTEM)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.backup.PLATFORMS", return_value=[]):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
119
tests/components/backup/test_sensors.py
Normal file
119
tests/components/backup/test_sensors.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""Tests for the sensors of the Backup integration."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.backup import store
|
||||
from homeassistant.components.backup.const import DOMAIN
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .common import setup_backup_integration
|
||||
|
||||
from tests.common import async_fire_time_changed, snapshot_platform
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_backup_generation")
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test setup of backup sensors."""
|
||||
with patch("homeassistant.components.backup.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_backup_integration(hass, with_hassio=False)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
|
||||
|
||||
# start backup and check sensor states again
|
||||
client = await hass_ws_client(hass)
|
||||
await hass.async_block_till_done()
|
||||
await client.send_json_auto_id(
|
||||
{"type": "backup/generate", "agent_ids": ["backup.local"]}
|
||||
)
|
||||
|
||||
assert await client.receive_json()
|
||||
state = hass.states.get("sensor.backup_backup_manager_state")
|
||||
assert state.state == "create_backup"
|
||||
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
state = hass.states.get("sensor.backup_backup_manager_state")
|
||||
assert state.state == "idle"
|
||||
|
||||
|
||||
async def test_sensor_updates(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
hass_storage: dict[str, Any],
|
||||
create_backup: AsyncMock,
|
||||
) -> None:
|
||||
"""Test update of backup sensors."""
|
||||
# Ensure created backup is already protected,
|
||||
# to avoid manager creating a new EncryptedBackupStreamer
|
||||
# instead of using the already mocked stream writer.
|
||||
created_backup: MagicMock = create_backup.return_value[1].result().backup
|
||||
created_backup.protected = True
|
||||
|
||||
await hass.config.async_set_time_zone("Europe/Amsterdam")
|
||||
freezer.move_to("2024-11-12T12:00:00+01:00")
|
||||
storage_data = {
|
||||
"backups": [],
|
||||
"config": {
|
||||
"agents": {},
|
||||
"automatic_backups_configured": True,
|
||||
"create_backup": {
|
||||
"agent_ids": ["test.remote"],
|
||||
"include_addons": [],
|
||||
"include_all_addons": False,
|
||||
"include_database": True,
|
||||
"include_folders": [],
|
||||
"name": "test-name",
|
||||
"password": "test-password",
|
||||
},
|
||||
"retention": {"copies": None, "days": None},
|
||||
"last_attempted_automatic_backup": "2024-11-11T04:45:00+01:00",
|
||||
"last_completed_automatic_backup": "2024-11-11T04:45:00+01:00",
|
||||
"schedule": {
|
||||
"days": [],
|
||||
"recurrence": "daily",
|
||||
"state": "never",
|
||||
"time": "06:00",
|
||||
},
|
||||
},
|
||||
}
|
||||
hass_storage[DOMAIN] = {
|
||||
"data": storage_data,
|
||||
"key": DOMAIN,
|
||||
"version": store.STORAGE_VERSION,
|
||||
"minor_version": store.STORAGE_VERSION_MINOR,
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.backup.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_backup_integration(
|
||||
hass, with_hassio=False, remote_agents=["test.remote"]
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
state = hass.states.get("sensor.backup_last_successful_automatic_backup")
|
||||
assert state.state == "2024-11-11T03:45:00+00:00"
|
||||
state = hass.states.get("sensor.backup_next_scheduled_automatic_backup")
|
||||
assert state.state == "2024-11-13T05:00:00+00:00"
|
||||
|
||||
freezer.move_to("2024-11-13T12:00:00+01:00")
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.backup_last_successful_automatic_backup")
|
||||
assert state.state == "2024-11-13T11:00:00+00:00"
|
||||
state = hass.states.get("sensor.backup_next_scheduled_automatic_backup")
|
||||
assert state.state == "2024-11-14T05:00:00+00:00"
|
Loading…
x
Reference in New Issue
Block a user