diff --git a/homeassistant/components/whois/const.py b/homeassistant/components/whois/const.py index f196053f48d..0b1d1717474 100644 --- a/homeassistant/components/whois/const.py +++ b/homeassistant/components/whois/const.py @@ -19,3 +19,31 @@ ATTR_EXPIRES = "expires" ATTR_NAME_SERVERS = "name_servers" ATTR_REGISTRAR = "registrar" ATTR_UPDATED = "updated" + +# Mapping of ICANN status codes to Home Assistant status types. +# From https://www.icann.org/resources/pages/epp-status-codes-2014-06-16-en +STATUS_TYPES = { + "addPeriod": "add_period", + "autoRenewPeriod": "auto_renew_period", + "inactive": "inactive", + "active": "active", + "pendingCreate": "pending_create", + "pendingRenew": "pending_renew", + "pendingRestore": "pending_restore", + "pendingTransfer": "pending_transfer", + "pendingUpdate": "pending_update", + "redemptionPeriod": "redemption_period", + "renewPeriod": "renew_period", + "serverDeleteProhibited": "server_delete_prohibited", + "serverHold": "server_hold", + "serverRenewProhibited": "server_renew_prohibited", + "serverTransferProhibited": "server_transfer_prohibited", + "serverUpdateProhibited": "server_update_prohibited", + "transferPeriod": "transfer_period", + "clientDeleteProhibited": "client_delete_prohibited", + "clientHold": "client_hold", + "clientRenewProhibited": "client_renew_prohibited", + "clientTransferProhibited": "client_transfer_prohibited", + "clientUpdateProhibited": "client_update_prohibited", + "ok": "ok", +} diff --git a/homeassistant/components/whois/icons.json b/homeassistant/components/whois/icons.json index 459ae252138..5ce1fb9717b 100644 --- a/homeassistant/components/whois/icons.json +++ b/homeassistant/components/whois/icons.json @@ -18,6 +18,9 @@ }, "reseller": { "default": "mdi:store" + }, + "status": { + "default": "mdi:check-circle" } } } diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 8098e052575..474ac366be2 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -25,7 +25,14 @@ from homeassistant.helpers.update_coordinator import ( ) from homeassistant.util import dt as dt_util -from .const import ATTR_EXPIRES, ATTR_NAME_SERVERS, ATTR_REGISTRAR, ATTR_UPDATED, DOMAIN +from .const import ( + ATTR_EXPIRES, + ATTR_NAME_SERVERS, + ATTR_REGISTRAR, + ATTR_UPDATED, + DOMAIN, + STATUS_TYPES, +) @dataclass(frozen=True, kw_only=True) @@ -58,6 +65,24 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: return timestamp +def _get_status_type(status: str | None) -> str | None: + """Get the status type from the status string. + + Returns the status type in snake_case, so it can be used as a key for the translations. + E.g: "clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited" -> "client_delete_prohibited". + """ + if status is None: + return None + + # If the status is not in the STATUS_TYPES, return the status as is. + for icann_status, hass_status in STATUS_TYPES.items(): + if icann_status in status: + return hass_status + + # If the status is not in the STATUS_TYPES, return None. + return None + + SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="admin", @@ -121,6 +146,15 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( entity_registry_enabled_default=False, value_fn=lambda domain: getattr(domain, "reseller", None), ), + WhoisSensorEntityDescription( + key="status", + translation_key="status", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.ENUM, + options=list(STATUS_TYPES.values()), + entity_registry_enabled_default=False, + value_fn=lambda domain: _get_status_type(domain.status), + ), ) diff --git a/homeassistant/components/whois/strings.json b/homeassistant/components/whois/strings.json index 3b0f9dfd4d1..b236bb06208 100644 --- a/homeassistant/components/whois/strings.json +++ b/homeassistant/components/whois/strings.json @@ -47,6 +47,34 @@ }, "reseller": { "name": "Reseller" + }, + "status": { + "name": "Status", + "state": { + "add_period": "Add period", + "auto_renew_period": "Auto renew period", + "inactive": "Inactive", + "ok": "Active", + "active": "Active", + "pending_create": "Pending create", + "pending_renew": "Pending renew", + "pending_restore": "Pending restore", + "pending_transfer": "Pending transfer", + "pending_update": "Pending update", + "redemption_period": "Redemption period", + "renew_period": "Renew period", + "server_delete_prohibited": "Server delete prohibited", + "server_hold": "Server hold", + "server_renew_prohibited": "Server renew prohibited", + "server_transfer_prohibited": "Server transfer prohibited", + "server_update_prohibited": "Server update prohibited", + "transfer_period": "Transfer period", + "client_delete_prohibited": "Client delete prohibited", + "client_hold": "Client hold", + "client_renew_prohibited": "Client renew prohibited", + "client_transfer_prohibited": "Client transfer prohibited", + "client_update_prohibited": "Client update prohibited" + } } } } diff --git a/tests/components/whois/conftest.py b/tests/components/whois/conftest.py index 4bb18581c1a..c4138a5d1d2 100644 --- a/tests/components/whois/conftest.py +++ b/tests/components/whois/conftest.py @@ -63,7 +63,7 @@ def mock_whois() -> Generator[MagicMock]: domain.registrant = "registrant@example.com" domain.registrar = "My Registrar" domain.reseller = "Top Domains, Low Prices" - domain.status = "OK" + domain.status = "ok" domain.statuses = ["OK"] yield whois_mock @@ -86,7 +86,7 @@ def mock_whois_missing_some_attrs() -> Generator[Mock]: self.name = "home-assistant.io" self.name_servers = ["ns1.example.com", "ns2.example.com"] self.registrar = "My Registrar" - self.status = "OK" + self.status = "ok" self.statuses = ["OK"] with patch( diff --git a/tests/components/whois/snapshots/test_diagnostics.ambr b/tests/components/whois/snapshots/test_diagnostics.ambr index f373a20700e..a498d0f88e9 100644 --- a/tests/components/whois/snapshots/test_diagnostics.ambr +++ b/tests/components/whois/snapshots/test_diagnostics.ambr @@ -5,7 +5,7 @@ 'dnssec': True, 'expiration_date': '2023-01-01T00:00:00', 'last_updated': '2022-01-01T00:00:00+01:00', - 'status': 'OK', + 'status': 'ok', 'statuses': list([ 'OK', ]), diff --git a/tests/components/whois/snapshots/test_sensor.ambr b/tests/components/whois/snapshots/test_sensor.ambr index b5b1dde1c3d..61499ba0f9d 100644 --- a/tests/components/whois/snapshots/test_sensor.ambr +++ b/tests/components/whois/snapshots/test_sensor.ambr @@ -727,6 +727,138 @@ 'via_device_id': None, }) # --- +# name: test_whois_sensors[sensor.home_assistant_io_status] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'home-assistant.io Status', + 'options': list([ + 'add_period', + 'auto_renew_period', + 'inactive', + 'active', + 'pending_create', + 'pending_renew', + 'pending_restore', + 'pending_transfer', + 'pending_update', + 'redemption_period', + 'renew_period', + 'server_delete_prohibited', + 'server_hold', + 'server_renew_prohibited', + 'server_transfer_prohibited', + 'server_update_prohibited', + 'transfer_period', + 'client_delete_prohibited', + 'client_hold', + 'client_renew_prohibited', + 'client_transfer_prohibited', + 'client_update_prohibited', + 'ok', + ]), + }), + 'context': , + 'entity_id': 'sensor.home_assistant_io_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'ok', + }) +# --- +# name: test_whois_sensors[sensor.home_assistant_io_status].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'add_period', + 'auto_renew_period', + 'inactive', + 'active', + 'pending_create', + 'pending_renew', + 'pending_restore', + 'pending_transfer', + 'pending_update', + 'redemption_period', + 'renew_period', + 'server_delete_prohibited', + 'server_hold', + 'server_renew_prohibited', + 'server_transfer_prohibited', + 'server_update_prohibited', + 'transfer_period', + 'client_delete_prohibited', + 'client_hold', + 'client_renew_prohibited', + 'client_transfer_prohibited', + 'client_update_prohibited', + 'ok', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.home_assistant_io_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'whois', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'home-assistant.io_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_whois_sensors[sensor.home_assistant_io_status].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': , + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'whois', + 'home-assistant.io', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': None, + 'model': None, + 'model_id': None, + 'name': 'home-assistant.io', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_whois_sensors_missing_some_attrs StateSnapshot({ 'attributes': ReadOnlyDict({ diff --git a/tests/components/whois/test_sensor.py b/tests/components/whois/test_sensor.py index d290bc347a9..69e32d923c4 100644 --- a/tests/components/whois/test_sensor.py +++ b/tests/components/whois/test_sensor.py @@ -32,6 +32,7 @@ pytestmark = [ "sensor.home_assistant_io_registrant", "sensor.home_assistant_io_registrar", "sensor.home_assistant_io_reseller", + "sensor.home_assistant_io_status", ], ) async def test_whois_sensors( @@ -73,6 +74,7 @@ async def test_whois_sensors_missing_some_attrs( "sensor.home_assistant_io_registrant", "sensor.home_assistant_io_registrar", "sensor.home_assistant_io_reseller", + "sensor.home_assistant_io_status", ], ) async def test_disabled_by_default_sensors( @@ -98,6 +100,7 @@ async def test_disabled_by_default_sensors( "sensor.home_assistant_io_registrant", "sensor.home_assistant_io_registrar", "sensor.home_assistant_io_reseller", + "sensor.home_assistant_io_status", ], ) async def test_no_data(