diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index 537019fb9c1..27f7f790637 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -2,14 +2,12 @@ from __future__ import annotations -from dataclasses import dataclass from datetime import timedelta import logging from typing import Final from aioazuredevops.builds import DevOpsBuild from aioazuredevops.client import DevOpsClient -from aioazuredevops.core import DevOpsProject import aiohttp from homeassistant.config_entries import ConfigEntry @@ -18,7 +16,6 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo -from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -34,14 +31,6 @@ PLATFORMS = [Platform.SENSOR] BUILDS_QUERY: Final = "?queryOrder=queueTimeDescending&maxBuildsPerDefinition=1" -@dataclass(frozen=True) -class AzureDevOpsEntityDescription(EntityDescription): - """Class describing Azure DevOps entities.""" - - organization: str = "" - project: DevOpsProject = None - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Azure DevOps from a config entry.""" aiohttp_session = async_get_clientsession(hass) @@ -108,32 +97,17 @@ class AzureDevOpsEntity(CoordinatorEntity[DataUpdateCoordinator[list[DevOpsBuild _attr_has_entity_name = True - entity_description: AzureDevOpsEntityDescription - def __init__( self, coordinator: DataUpdateCoordinator[list[DevOpsBuild]], - entity_description: AzureDevOpsEntityDescription, + organization: str, + project_name: str, ) -> None: """Initialize the Azure DevOps entity.""" super().__init__(coordinator) - self.entity_description = entity_description - self._attr_unique_id: str = ( - f"{entity_description.organization}_{entity_description.key}" - ) - self._organization: str = entity_description.organization - self._project_name: str = entity_description.project.name - - -class AzureDevOpsDeviceEntity(AzureDevOpsEntity): - """Defines a Azure DevOps device entity.""" - - @property - def device_info(self) -> DeviceInfo: - """Return device information about this Azure DevOps instance.""" - return DeviceInfo( + self._attr_device_info = DeviceInfo( entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN, self._organization, self._project_name)}, # type: ignore[arg-type] - manufacturer=self._organization, - name=self._project_name, + identifiers={(DOMAIN, organization, project_name)}, # type: ignore[arg-type] + manufacturer=organization, + name=project_name, ) diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 514db5462e9..b1d975f0a70 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -2,89 +2,186 @@ from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Mapping from dataclasses import dataclass +from datetime import datetime +import logging from typing import Any from aioazuredevops.builds import DevOpsBuild -from homeassistant.components.sensor import SensorEntity, SensorEntityDescription +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util import dt as dt_util -from . import AzureDevOpsDeviceEntity, AzureDevOpsEntityDescription +from . import AzureDevOpsEntity from .const import CONF_ORG, DOMAIN +_LOGGER = logging.getLogger(__name__) + @dataclass(frozen=True, kw_only=True) -class AzureDevOpsSensorEntityDescription( - AzureDevOpsEntityDescription, SensorEntityDescription -): - """Class describing Azure DevOps sensor entities.""" +class AzureDevOpsBuildSensorEntityDescription(SensorEntityDescription): + """Class describing Azure DevOps base build sensor entities.""" - build_key: int - attrs: Callable[[DevOpsBuild], Any] - value: Callable[[DevOpsBuild], StateType] + attr_fn: Callable[[DevOpsBuild], dict[str, Any] | None] = lambda _: None + value_fn: Callable[[DevOpsBuild], datetime | StateType] + + +BASE_BUILD_SENSOR_DESCRIPTIONS: tuple[AzureDevOpsBuildSensorEntityDescription, ...] = ( + # Attributes are deprecated in 2024.7 and can be removed in 2025.1 + AzureDevOpsBuildSensorEntityDescription( + key="latest_build", + translation_key="latest_build", + attr_fn=lambda build: { + "definition_id": (build.definition.build_id if build.definition else None), + "definition_name": (build.definition.name if build.definition else None), + "id": build.build_id, + "reason": build.reason, + "result": build.result, + "source_branch": build.source_branch, + "source_version": build.source_version, + "status": build.status, + "url": build.links.web if build.links else None, + "queue_time": build.queue_time, + "start_time": build.start_time, + "finish_time": build.finish_time, + }, + value_fn=lambda build: build.build_number, + ), + AzureDevOpsBuildSensorEntityDescription( + key="build_id", + translation_key="build_id", + entity_registry_visible_default=False, + value_fn=lambda build: build.build_id, + ), + AzureDevOpsBuildSensorEntityDescription( + key="reason", + translation_key="reason", + entity_registry_visible_default=False, + value_fn=lambda build: build.reason, + ), + AzureDevOpsBuildSensorEntityDescription( + key="result", + translation_key="result", + entity_registry_visible_default=False, + value_fn=lambda build: build.result, + ), + AzureDevOpsBuildSensorEntityDescription( + key="source_branch", + translation_key="source_branch", + entity_registry_enabled_default=False, + entity_registry_visible_default=False, + value_fn=lambda build: build.source_branch, + ), + AzureDevOpsBuildSensorEntityDescription( + key="source_version", + translation_key="source_version", + entity_registry_visible_default=False, + value_fn=lambda build: build.source_version, + ), + AzureDevOpsBuildSensorEntityDescription( + key="queue_time", + translation_key="queue_time", + device_class=SensorDeviceClass.TIMESTAMP, + entity_registry_enabled_default=False, + entity_registry_visible_default=False, + value_fn=lambda build: parse_datetime(build.queue_time), + ), + AzureDevOpsBuildSensorEntityDescription( + key="start_time", + translation_key="start_time", + device_class=SensorDeviceClass.TIMESTAMP, + entity_registry_visible_default=False, + value_fn=lambda build: parse_datetime(build.start_time), + ), + AzureDevOpsBuildSensorEntityDescription( + key="finish_time", + translation_key="finish_time", + device_class=SensorDeviceClass.TIMESTAMP, + entity_registry_visible_default=False, + value_fn=lambda build: parse_datetime(build.finish_time), + ), + AzureDevOpsBuildSensorEntityDescription( + key="url", + translation_key="url", + value_fn=lambda build: build.links.web if build.links else None, + ), +) + + +def parse_datetime(value: str | None) -> datetime | None: + """Parse datetime string.""" + if value is None: + return None + + return dt_util.parse_datetime(value) async def async_setup_entry( - hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: """Set up Azure DevOps sensor based on a config entry.""" coordinator, project = hass.data[DOMAIN][entry.entry_id] + initial_builds: list[DevOpsBuild] = coordinator.data - sensors = [ - AzureDevOpsSensor( + async_add_entities( + AzureDevOpsBuildSensor( coordinator, - AzureDevOpsSensorEntityDescription( - key=f"{build.project.project_id}_{build.definition.build_id}_latest_build", - translation_key="latest_build", - translation_placeholders={"definition_name": build.definition.name}, - attrs=lambda build: { - "definition_id": ( - build.definition.build_id if build.definition else None - ), - "definition_name": ( - build.definition.name if build.definition else None - ), - "id": build.build_id, - "reason": build.reason, - "result": build.result, - "source_branch": build.source_branch, - "source_version": build.source_version, - "status": build.status, - "url": build.links.web if build.links else None, - "queue_time": build.queue_time, - "start_time": build.start_time, - "finish_time": build.finish_time, - }, - build_key=key, - organization=entry.data[CONF_ORG], - project=project, - value=lambda build: build.build_number, - ), + description, + entry.data[CONF_ORG], + project.name, + key, ) - for key, build in enumerate(coordinator.data) - ] - - async_add_entities(sensors, True) + for description in BASE_BUILD_SENSOR_DESCRIPTIONS + for key, build in enumerate(initial_builds) + if build.project and build.definition + ) -class AzureDevOpsSensor(AzureDevOpsDeviceEntity, SensorEntity): - """Define a Azure DevOps sensor.""" +class AzureDevOpsBuildSensor(AzureDevOpsEntity, SensorEntity): + """Define a Azure DevOps build sensor.""" - entity_description: AzureDevOpsSensorEntityDescription + entity_description: AzureDevOpsBuildSensorEntityDescription + + def __init__( + self, + coordinator: DataUpdateCoordinator[list[DevOpsBuild]], + description: AzureDevOpsBuildSensorEntityDescription, + organization: str, + project_name: str, + item_key: int, + ) -> None: + """Initialize.""" + super().__init__(coordinator, organization, project_name) + self.entity_description = description + self.item_key = item_key + self._attr_unique_id = f"{organization}_{self.build.project.project_id}_{self.build.definition.build_id}_{description.key}" + self._attr_translation_placeholders = { + "definition_name": self.build.definition.name + } @property - def native_value(self) -> StateType: + def build(self) -> DevOpsBuild: + """Return the build.""" + return self.coordinator.data[self.item_key] + + @property + def native_value(self) -> datetime | StateType: """Return the state.""" - build: DevOpsBuild = self.coordinator.data[self.entity_description.build_key] - return self.entity_description.value(build) + return self.entity_description.value_fn(self.build) @property - def extra_state_attributes(self) -> dict[str, Any]: + def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return the state attributes of the entity.""" - build: DevOpsBuild = self.coordinator.data[self.entity_description.build_key] - return self.entity_description.attrs(build) + return self.entity_description.attr_fn(self.build) diff --git a/homeassistant/components/azure_devops/strings.json b/homeassistant/components/azure_devops/strings.json index c163aee5b7f..7bd6d8af561 100644 --- a/homeassistant/components/azure_devops/strings.json +++ b/homeassistant/components/azure_devops/strings.json @@ -31,8 +31,35 @@ }, "entity": { "sensor": { + "build_id": { + "name": "{definition_name} latest build id" + }, + "finish_time": { + "name": "{definition_name} latest build finish time" + }, "latest_build": { "name": "{definition_name} latest build" + }, + "queue_time": { + "name": "{definition_name} latest build queue time" + }, + "reason": { + "name": "{definition_name} latest build reason" + }, + "result": { + "name": "{definition_name} latest build result" + }, + "source_branch": { + "name": "{definition_name} latest build source branch" + }, + "source_version": { + "name": "{definition_name} latest build source version" + }, + "start_time": { + "name": "{definition_name} latest build start time" + }, + "url": { + "name": "{definition_name} latest build url" } } } diff --git a/tests/components/azure_devops/__init__.py b/tests/components/azure_devops/__init__.py index fb0817671b5..7c540cd3c6d 100644 --- a/tests/components/azure_devops/__init__.py +++ b/tests/components/azure_devops/__init__.py @@ -43,7 +43,7 @@ DEVOPS_PROJECT = DevOpsProject( DEVOPS_BUILD_DEFINITION = DevOpsBuildDefinition( build_id=9876, - name="Test Build", + name="CI", url=f"https://dev.azure.com/{ORGANIZATION}/{PROJECT}/_apis/build/definitions/1", path="", build_type="build", @@ -68,6 +68,16 @@ DEVOPS_BUILD = DevOpsBuild( links=None, ) +DEVOPS_BUILD_MISSING_DATA = DevOpsBuild( + build_id=6789, + definition=DEVOPS_BUILD_DEFINITION, + project=DEVOPS_PROJECT, +) + +DEVOPS_BUILD_MISSING_PROJECT_DEFINITION = DevOpsBuild( + build_id=9876, +) + async def setup_integration( hass: HomeAssistant, diff --git a/tests/components/azure_devops/snapshots/test_sensor.ambr b/tests/components/azure_devops/snapshots/test_sensor.ambr index b99d2c4e49d..0ce82cae1e8 100644 --- a/tests/components/azure_devops/snapshots/test_sensor.ambr +++ b/tests/components/azure_devops/snapshots/test_sensor.ambr @@ -1,4 +1,1034 @@ # serializer version: 1 +# name: test_sensors[sensor.testproject_ci_build_finish_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_finish_time', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'CI build finish time', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'finish_time', + 'unique_id': 'testorg_1234_9876_finish_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_finish_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'date', + 'friendly_name': 'testproject CI build finish time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_finish_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-01-01T00:00:00+00:00', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_id-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_id', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build id', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'build_id', + 'unique_id': 'testorg_1234_9876_build_id', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_id-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build id', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_id', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5678', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_queue_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_queue_time', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'CI build queue time', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'queue_time', + 'unique_id': 'testorg_1234_9876_queue_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_queue_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'date', + 'friendly_name': 'testproject CI build queue time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_queue_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-01-01T00:00:00+00:00', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_reason-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_reason', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build reason', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'reason', + 'unique_id': 'testorg_1234_9876_reason', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_reason-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build reason', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_reason', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'manual', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_result-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_result', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build result', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'result', + 'unique_id': 'testorg_1234_9876_result', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_result-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build result', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_result', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'succeeded', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_source_branch-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_source_branch', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build source branch', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'source_branch', + 'unique_id': 'testorg_1234_9876_source_branch', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_source_branch-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build source branch', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_source_branch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'main', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_source_version-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_source_version', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build source version', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'source_version', + 'unique_id': 'testorg_1234_9876_source_version', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_source_version-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build source version', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_source_version', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_start_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_start_time', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'CI build start time', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'start_time', + 'unique_id': 'testorg_1234_9876_start_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_start_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'date', + 'friendly_name': 'testproject CI build start time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-01-01T00:00:00+00:00', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_status', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build status', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'testorg_1234_9876_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build status', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'completed', + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_url-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_build_url', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI build url', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'url', + 'unique_id': 'testorg_1234_9876_url', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_build_url-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build url', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_url', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'latest_build', + 'unique_id': 'testorg_1234_9876_latest_build', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'definition_id': 9876, + 'definition_name': 'CI', + 'finish_time': '2021-01-01T00:00:00Z', + 'friendly_name': 'testproject CI latest build', + 'id': 5678, + 'queue_time': '2021-01-01T00:00:00Z', + 'reason': 'manual', + 'result': 'succeeded', + 'source_branch': 'main', + 'source_version': '123', + 'start_time': '2021-01-01T00:00:00Z', + 'status': 'completed', + 'url': None, + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_finish_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_finish_time', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'CI latest build finish time', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'finish_time', + 'unique_id': 'testorg_1234_9876_finish_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_finish_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'testproject CI latest build finish time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_finish_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-01-01T00:00:00+00:00', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_id-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_id', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build id', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'build_id', + 'unique_id': 'testorg_1234_9876_build_id', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_id-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build id', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_id', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5678', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_queue_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_queue_time', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'CI latest build queue time', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'queue_time', + 'unique_id': 'testorg_1234_9876_queue_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_queue_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'testproject CI latest build queue time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_queue_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-01-01T00:00:00+00:00', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_reason-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_reason', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build reason', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'reason', + 'unique_id': 'testorg_1234_9876_reason', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_reason-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build reason', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_reason', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'manual', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_result-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_result', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build result', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'result', + 'unique_id': 'testorg_1234_9876_result', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_result-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build result', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_result', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'succeeded', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_source_branch-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_source_branch', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build source branch', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'source_branch', + 'unique_id': 'testorg_1234_9876_source_branch', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_source_branch-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build source branch', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_source_branch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'main', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_source_version-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_source_version', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build source version', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'source_version', + 'unique_id': 'testorg_1234_9876_source_version', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_source_version-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build source version', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_source_version', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_start_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_start_time', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'CI latest build start time', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'start_time', + 'unique_id': 'testorg_1234_9876_start_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_start_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'testproject CI latest build start time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-01-01T00:00:00+00:00', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_status', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build status', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'testorg_1234_9876_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build status', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'completed', + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_url-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_ci_latest_build_url', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CI latest build url', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'url', + 'unique_id': 'testorg_1234_9876_url', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_ci_latest_build_url-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build url', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_url', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.testproject_test_build_build_id-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.testproject_test_build_build_id', + 'has_entity_name': True, + 'hidden_by': , + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Test Build build id', + 'platform': 'azure_devops', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'build_id', + 'unique_id': 'testorg_1234_9876_build_id', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.testproject_test_build_build_id-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject Test Build build id', + }), + 'context': , + 'entity_id': 'sensor.testproject_test_build_build_id', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5678', + }) +# --- # name: test_sensors[sensor.testproject_test_build_latest_build-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -57,3 +1087,294 @@ 'state': '1', }) # --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_finish_time-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'date', + 'friendly_name': 'testproject CI build finish time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_finish_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_id-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build id', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_id', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6789', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_queue_time-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'date', + 'friendly_name': 'testproject CI build queue time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_queue_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_reason-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build reason', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_reason', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_result-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build result', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_result', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_source_branch-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build source branch', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_source_branch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_source_version-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build source version', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_source_version', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_start_time-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'date', + 'friendly_name': 'testproject CI build start time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_status-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build status', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_build_url-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI build url', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_build_url', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'definition_id': 9876, + 'definition_name': 'CI', + 'finish_time': None, + 'friendly_name': 'testproject CI latest build', + 'id': 6789, + 'queue_time': None, + 'reason': None, + 'result': None, + 'source_branch': None, + 'source_version': None, + 'start_time': None, + 'status': None, + 'url': None, + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_finish_time-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'testproject CI latest build finish time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_finish_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_id-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build id', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_id', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6789', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_queue_time-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'testproject CI latest build queue time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_queue_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_reason-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build reason', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_reason', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_result-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build result', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_result', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_source_branch-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build source branch', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_source_branch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_source_version-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build source version', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_source_version', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_start_time-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'testproject CI latest build start time', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_status-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build status', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors_missing_data[sensor.testproject_ci_latest_build_url-state-missing-data] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'testproject CI latest build url', + }), + 'context': , + 'entity_id': 'sensor.testproject_ci_latest_build_url', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/azure_devops/test_init.py b/tests/components/azure_devops/test_init.py index a35acb375ec..240edee82d7 100644 --- a/tests/components/azure_devops/test_init.py +++ b/tests/components/azure_devops/test_init.py @@ -22,7 +22,7 @@ async def test_load_unload_entry( assert mock_devops_client.authorized assert mock_devops_client.authorize.call_count == 1 - assert mock_devops_client.get_builds.call_count == 2 + assert mock_devops_client.get_builds.call_count == 1 assert mock_config_entry.state is ConfigEntryState.LOADED diff --git a/tests/components/azure_devops/test_sensor.py b/tests/components/azure_devops/test_sensor.py index 1c518d919c2..cb49c3d67cd 100644 --- a/tests/components/azure_devops/test_sensor.py +++ b/tests/components/azure_devops/test_sensor.py @@ -8,10 +8,28 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from . import setup_integration +from . import ( + DEVOPS_BUILD_MISSING_DATA, + DEVOPS_BUILD_MISSING_PROJECT_DEFINITION, + setup_integration, +) from tests.common import MockConfigEntry +BASE_ENTITY_ID = "sensor.testproject_ci" +SENSOR_KEYS = [ + "latest_build", + "latest_build_id", + "latest_build_reason", + "latest_build_result", + "latest_build_source_branch", + "latest_build_source_version", + "latest_build_queue_time", + "latest_build_start_time", + "latest_build_finish_time", + "latest_build_url", +] + @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_sensors( @@ -21,13 +39,53 @@ async def test_sensors( mock_config_entry: MockConfigEntry, mock_devops_client: AsyncMock, ) -> None: - """Test the sensor entities.""" + """Test sensor entities.""" assert await setup_integration(hass, mock_config_entry) - assert ( - entry := entity_registry.async_get("sensor.testproject_test_build_latest_build") - ) + for sensor_key in SENSOR_KEYS: + assert (entry := entity_registry.async_get(f"{BASE_ENTITY_ID}_{sensor_key}")) - assert entry == snapshot(name=f"{entry.entity_id}-entry") + assert entry == snapshot(name=f"{entry.entity_id}-entry") - assert hass.states.get(entry.entity_id) == snapshot(name=f"{entry.entity_id}-state") + assert hass.states.get(entry.entity_id) == snapshot( + name=f"{entry.entity_id}-state" + ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensors_missing_data( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + mock_devops_client: AsyncMock, +) -> None: + """Test sensor entities with missing data.""" + mock_devops_client.get_builds.return_value = [DEVOPS_BUILD_MISSING_DATA] + + assert await setup_integration(hass, mock_config_entry) + + for sensor_key in SENSOR_KEYS: + assert (entry := entity_registry.async_get(f"{BASE_ENTITY_ID}_{sensor_key}")) + + assert hass.states.get(entry.entity_id) == snapshot( + name=f"{entry.entity_id}-state-missing-data" + ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensors_missing_project_definition( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + mock_devops_client: AsyncMock, +) -> None: + """Test sensor entities with missing project and definition.""" + mock_devops_client.get_builds.return_value = [ + DEVOPS_BUILD_MISSING_PROJECT_DEFINITION + ] + + assert await setup_integration(hass, mock_config_entry) + + for sensor_key in SENSOR_KEYS: + assert not entity_registry.async_get(f"{BASE_ENTITY_ID}_{sensor_key}")