Add ScorpionTrack diagnostics (#181656)

This commit is contained in:
Anthony
2026-09-08 18:17:17 +02:00
committed by GitHub
parent 87c4b86627
commit d7b3513b9d
4 changed files with 281 additions and 1 deletions
@@ -0,0 +1,40 @@
"""Diagnostics support for ScorpionTrack."""
from dataclasses import asdict
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from .const import CONF_SHARE_TOKEN
from .coordinator import ScorpionTrackConfigEntry
TO_REDACT = {
CONF_SHARE_TOKEN,
CONF_LATITUDE,
CONF_LONGITUDE,
"address",
"id",
"name",
"owner_name",
"registration",
"title",
"token",
}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ScorpionTrackConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
data = asdict(coordinator.data)
# The redactor traverses lists, but not tuples.
data["vehicles"] = list(data["vehicles"])
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"last_update_success": coordinator.last_update_success,
"data": async_redact_data(data, TO_REDACT),
}
@@ -50,7 +50,7 @@ rules:
test-coverage: done
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery:
status: exempt
comment: |
@@ -0,0 +1,143 @@
# serializer version: 1
# name: test_diagnostics[failed_update]
dict({
'data': dict({
'created_at': '2026-09-04T12:00:00+00:00',
'distance_units': 'miles',
'expires_at': '2026-10-05T12:00:00+00:00',
'id': '**REDACTED**',
'owner_name': '**REDACTED**',
'title': '**REDACTED**',
'token': '**REDACTED**',
'vehicles': list([
dict({
'id': '**REDACTED**',
'make': 'Volkswagen',
'model': 'Golf R',
'name': '**REDACTED**',
'position': dict({
'address': '**REDACTED**',
'bearing': 182.0,
'ignition': True,
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
'speed_kmh': 48.3,
'timestamp': '2026-09-05T12:00:00+00:00',
}),
'registration': '**REDACTED**',
'status': 'Moving',
}),
dict({
'id': '**REDACTED**',
'make': 'Volkswagen',
'model': 'Golf R',
'name': '**REDACTED**',
'position': dict({
'address': '**REDACTED**',
'bearing': 182.0,
'ignition': True,
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
'speed_kmh': 48.3,
'timestamp': '2026-09-05T12:00:00+00:00',
}),
'registration': '**REDACTED**',
'status': 'Moving',
}),
]),
}),
'entry_data': dict({
'share_token': '**REDACTED**',
}),
'last_update_success': False,
})
# ---
# name: test_diagnostics[successful_update]
dict({
'data': dict({
'created_at': '2026-09-04T12:00:00+00:00',
'distance_units': 'miles',
'expires_at': '2026-10-05T12:00:00+00:00',
'id': '**REDACTED**',
'owner_name': '**REDACTED**',
'title': '**REDACTED**',
'token': '**REDACTED**',
'vehicles': list([
dict({
'id': '**REDACTED**',
'make': 'Volkswagen',
'model': 'Golf R',
'name': '**REDACTED**',
'position': dict({
'address': '**REDACTED**',
'bearing': 182.0,
'ignition': True,
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
'speed_kmh': 48.3,
'timestamp': '2026-09-05T12:00:00+00:00',
}),
'registration': '**REDACTED**',
'status': 'Moving',
}),
dict({
'id': '**REDACTED**',
'make': 'Volkswagen',
'model': 'Golf R',
'name': '**REDACTED**',
'position': dict({
'address': '**REDACTED**',
'bearing': 182.0,
'ignition': True,
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
'speed_kmh': 48.3,
'timestamp': '2026-09-05T12:00:00+00:00',
}),
'registration': '**REDACTED**',
'status': 'Moving',
}),
]),
}),
'entry_data': dict({
'share_token': '**REDACTED**',
}),
'last_update_success': True,
})
# ---
# name: test_diagnostics_missing_position
dict({
'data': dict({
'created_at': '2026-09-04T12:00:00+00:00',
'distance_units': 'miles',
'expires_at': '2026-10-05T12:00:00+00:00',
'id': '**REDACTED**',
'owner_name': '**REDACTED**',
'title': '**REDACTED**',
'token': '**REDACTED**',
'vehicles': list([
dict({
'id': '**REDACTED**',
'make': 'Volkswagen',
'model': 'Golf R',
'name': '**REDACTED**',
'position': dict({
'address': None,
'bearing': None,
'ignition': None,
'latitude': None,
'longitude': None,
'speed_kmh': None,
'timestamp': None,
}),
'registration': '**REDACTED**',
'status': 'unknown',
}),
]),
}),
'entry_data': dict({
'share_token': '**REDACTED**',
}),
'last_update_success': True,
})
# ---
@@ -0,0 +1,97 @@
"""Test ScorpionTrack diagnostics."""
from dataclasses import replace
from unittest.mock import AsyncMock
from pyscorpiontrack import ScorpionTrackConnectionError, ScorpionTrackShare
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.scorpiontrack.const import CONF_SHARE_TOKEN
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
pytestmark = pytest.mark.freeze_time("2026-09-07T12:00:00Z")
@pytest.mark.parametrize(
"update_error",
[
pytest.param(None, id="successful_update"),
pytest.param(
ScorpionTrackConnectionError("Failed request for canonical-token"),
id="failed_update",
),
],
)
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
mock_scorpiontrack_client: AsyncMock,
mock_share: ScorpionTrackShare,
snapshot: SnapshotAssertion,
update_error: ScorpionTrackConnectionError | None,
) -> None:
"""Test redaction for every vehicle without fetching or changing cached data."""
vehicle = mock_share.vehicles[0]
share = replace(
mock_share,
vehicles=(vehicle, replace(vehicle, id=2, registration="EF34 GHI")),
)
mock_scorpiontrack_client.async_get_share.return_value = share
await setup_integration(hass, mock_config_entry)
mock_scorpiontrack_client.async_get_share.side_effect = update_error
await mock_config_entry.runtime_data.async_refresh()
mock_scorpiontrack_client.async_get_share.reset_mock()
result = await get_diagnostics_for_config_entry(
hass, hass_client, mock_config_entry
)
assert result == snapshot
mock_scorpiontrack_client.async_get_share.assert_not_awaited()
assert mock_config_entry.runtime_data.data == share
assert mock_config_entry.data[CONF_SHARE_TOKEN] == "canonical-token"
async def test_diagnostics_missing_position(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
mock_scorpiontrack_client: AsyncMock,
mock_share: ScorpionTrackShare,
snapshot: SnapshotAssertion,
) -> None:
"""Test diagnostics when a vehicle has no position data."""
vehicle = mock_share.vehicles[0]
mock_scorpiontrack_client.async_get_share.return_value = replace(
mock_share,
vehicles=(
replace(
vehicle,
position=replace(
vehicle.position,
latitude=None,
longitude=None,
timestamp=None,
speed_kmh=None,
ignition=None,
bearing=None,
address=None,
),
status="unknown",
),
),
)
await setup_integration(hass, mock_config_entry)
assert (
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
== snapshot
)