1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-27 14:31:13 +00:00
Files
core/tests/components/unifiprotect/test_diagnostics.py
2025-12-18 22:23:44 +00:00

107 lines
4.0 KiB
Python

"""Test UniFi Protect diagnostics."""
import re
from typing import Any
from syrupy.assertion import SnapshotAssertion
from uiprotect.data import Light
from homeassistant.core import HomeAssistant
from .utils import MockUFPFixture, init_entry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
# Pattern for hex IDs (24 char hex strings like device/user IDs)
HEX_ID_PATTERN = re.compile(r"^[a-f0-9]{24}$")
# Pattern for MAC addresses (12 hex chars)
MAC_PATTERN = re.compile(r"^[A-F0-9]{12}$")
# Pattern for IPv4 addresses (anonymized by library)
IPV4_PATTERN = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
# Pattern for UUIDs (anonymized by library)
UUID_PATTERN = re.compile(
r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
)
# Pattern for anonymized names (capitalized words with random letters)
ANON_NAME_PATTERN = re.compile(r"^[A-Z][a-z]+( [A-Z][a-z]+)*$")
# Pattern for anonymized emails
EMAIL_PATTERN = re.compile(r"^[A-Za-z]+@example\.com$")
# Pattern for permission strings with embedded IDs
PERMISSION_ID_PATTERN = re.compile(r"^(.+:\*:)[a-f0-9]{24}$")
# Keys that should be redacted for security
REDACT_KEYS = {"accessKey"}
# Keys that contain anonymized names (need normalization) - pattern-matched
NAME_KEYS = {"name", "firstName", "lastName"}
# Keys that always need normalization (not pattern-matched)
ALWAYS_REDACT_KEYS = {"localUsername"}
def _normalize_diagnostics(data: Any, parent_key: str | None = None) -> Any:
"""Normalize diagnostics data for deterministic snapshots.
Removes repr fields (contain memory addresses), redacts sensitive keys,
and normalizes hex IDs, MAC addresses, IP addresses, UUIDs, emails, and
anonymized names that may be randomly generated.
"""
if isinstance(data, dict):
return {
k: _normalize_diagnostics(v, k)
for k, v in data.items()
if k != "repr" # Remove repr fields with memory addresses
}
if isinstance(data, list):
return [_normalize_diagnostics(item) for item in data]
if isinstance(data, str):
# Redact sensitive keys
if parent_key in REDACT_KEYS:
return "**REDACTED**"
# Always redact certain keys regardless of pattern
if parent_key in ALWAYS_REDACT_KEYS:
return "**REDACTED_NAME**"
# Normalize anonymized names (pattern-matched)
if parent_key in NAME_KEYS and ANON_NAME_PATTERN.match(data):
return "**REDACTED_NAME**"
if HEX_ID_PATTERN.match(data):
return "**REDACTED_ID**"
if MAC_PATTERN.match(data):
return "**REDACTED_MAC**"
if IPV4_PATTERN.match(data):
return "**REDACTED_IP**"
if UUID_PATTERN.match(data):
return "**REDACTED_UUID**"
if EMAIL_PATTERN.match(data):
return "**REDACTED**@example.com"
# Normalize permission strings with embedded IDs
if match := PERMISSION_ID_PATTERN.match(data):
return f"{match.group(1)}**REDACTED_ID**"
return data
async def test_diagnostics(
hass: HomeAssistant,
ufp: MockUFPFixture,
light: Light,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
) -> None:
"""Test generating diagnostics for a config entry."""
await init_entry(hass, ufp, [light])
diag = await get_diagnostics_for_config_entry(hass, hass_client, ufp.entry)
# Validate that anonymization is working - original values should not appear
bootstrap = diag["bootstrap"]
nvr = ufp.api.bootstrap.nvr
assert bootstrap["nvr"]["id"] != nvr.id
assert bootstrap["nvr"]["mac"] != nvr.mac
assert bootstrap["nvr"]["host"] != str(nvr.host)
assert bootstrap["lights"][0]["id"] != light.id
assert bootstrap["lights"][0]["mac"] != light.mac
assert bootstrap["lights"][0]["host"] != str(light.host)
# Normalize data to remove non-deterministic values (memory addresses, random IDs)
diag_normalized = _normalize_diagnostics(diag)
assert diag_normalized == snapshot