* docs: add Home Assistant 0.4.0 hardening roadmap * fix: protect media credentials and resume state * feat: authenticate RelayTV API operations * fix: make targeting and media state reliable * test: add Home Assistant integration CI * fix: validate API credentials during setup * release: prepare HACS 0.4.0 * fix: align pytest dependency pin * fix: close URL sanitizer gaps and correct player state reporting Sync the sensitive-query-key list with the RelayTV server (adds auth, exp, jwt, X-Emby-Token, X-Jellyfin-Token), filter query credentials from relative URLs instead of returning them verbatim, and preserve brackets around IPv6 literal hosts. Also report volume on RelayTV's 0-100 scale unconditionally (a raw 1 is 1%, not full volume) and give the coordinator its own position_updated_at stamp — the base DataUpdateCoordinator has no last_update_success_time, so media_position_updated_at silently fell back to now() on every read and the seek bar never extrapolated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
36 lines
1.5 KiB
Python
36 lines
1.5 KiB
Python
"""Tests for RelayTV coordinator state deduplication."""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from custom_components.relaytv.coordinator import RelayTVCoordinator, _material_state_view
|
|
|
|
|
|
def test_idle_snapshots_ignore_stale_audio_properties() -> None:
|
|
compact_idle = {"playing": False, "paused": False, "volume": 80, "mute": True}
|
|
full_idle = {"playing": False, "paused": False, "volume": None, "mute": None}
|
|
|
|
assert _material_state_view(compact_idle) == _material_state_view(full_idle)
|
|
|
|
|
|
def test_playing_snapshots_track_audio_properties() -> None:
|
|
first = {"playing": True, "paused": False, "volume": 80, "mute": False}
|
|
second = {"playing": True, "paused": False, "volume": 30, "mute": True}
|
|
|
|
assert _material_state_view(first) != _material_state_view(second)
|
|
|
|
|
|
async def test_note_position_update_stamps_only_position_changes(hass) -> None:
|
|
coordinator = RelayTVCoordinator(hass=hass, api=MagicMock(base_url="http://relaytv.local:8787"))
|
|
assert coordinator.position_updated_at is None
|
|
|
|
coordinator.note_position_update({"playing": True, "paused": False, "position": 10.0})
|
|
first_stamp = coordinator.position_updated_at
|
|
assert first_stamp is not None
|
|
|
|
coordinator.note_position_update({"playing": True, "paused": False, "position": 10.0, "volume": 50})
|
|
assert coordinator.position_updated_at == first_stamp
|
|
|
|
coordinator.note_position_update({"playing": True, "paused": False, "position": 25.0})
|
|
assert coordinator.position_updated_at is not None
|
|
assert coordinator.position_updated_at >= first_stamp
|