Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

144 lines
4.1 KiB
Python
Raw Permalink Normal View History

2026-07-02 16:50:08 +02:00
"""Tests for the syncthing integration setup and client."""
import asyncio
from unittest.mock import MagicMock, patch
from aiosyncthing.exceptions import SyncthingError
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.syncthing.const import (
DOMAIN,
EVENTS,
RECONNECT_INTERVAL,
SERVER_AVAILABLE,
SERVER_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import dispatcher
from . import SERVER_ID
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
load_json_object_fixture,
)
async def test_syncthing_client_reconnect_on_error(
hass: HomeAssistant,
entry: MockConfigEntry,
mock_syncthing_client: MagicMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test SyncthingClient reconnects when server becomes unavailable."""
call_count = 0
async def mock_listen():
"""Mock listen that raises error first, then succeeds."""
nonlocal call_count
call_count += 1
if call_count == 1:
raise SyncthingError("Connection lost")
while True:
yield await hass.async_add_executor_job(
load_json_object_fixture, "state_changed_event.json", DOMAIN
)
mock_syncthing_client.events.last_seen_id = 10
mock_syncthing_client.events.listen = mock_listen
server_unavailable_calls = []
server_available_calls = []
async def server_unavailable_handler() -> None:
"""Handle server unavailable signal."""
server_unavailable_calls.append(True)
async def server_available_handler() -> None:
"""Handle server available signal."""
server_available_calls.append(True)
dispatcher.async_dispatcher_connect(
hass,
f"{SERVER_UNAVAILABLE}-{SERVER_ID}",
server_unavailable_handler,
)
dispatcher.async_dispatcher_connect(
hass,
f"{SERVER_AVAILABLE}-{SERVER_ID}",
server_available_handler,
)
with patch(
"homeassistant.components.syncthing.aiosyncthing.Syncthing",
autospec=True,
) as mock_class:
mock_class.return_value = mock_syncthing_client
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(server_unavailable_calls) >= 1
assert len(server_available_calls) == 0
freezer.tick(RECONNECT_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert len(server_available_calls) >= 1
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
@pytest.mark.parametrize(
"event_fixture",
[
"folder_summary_event.json",
"state_changed_event.json",
"folder_paused_event.json",
],
)
async def test_client_dispatches_event(
hass: HomeAssistant,
entry: MockConfigEntry,
mock_syncthing_client: MagicMock,
event_fixture: str,
) -> None:
"""Test SyncthingClient dispatches the expected signal for syncthing events."""
event = await hass.async_add_executor_job(
load_json_object_fixture, event_fixture, DOMAIN
)
async def mock_listen():
yield event
await asyncio.Event().wait()
mock_syncthing_client.events.listen = mock_listen
mock_syncthing_client.events.last_seen_id = 10
folder = event["data"].get("folder") or event["data"]["id"]
signal = f"{EVENTS[event['type']]}-{SERVER_ID}-{folder}"
received = asyncio.Event()
captured: list[dict] = []
@callback
def handler(received_event: dict) -> None:
captured.append(received_event)
received.set()
dispatcher.async_dispatcher_connect(hass, signal, handler)
with patch(
"homeassistant.components.syncthing.aiosyncthing.Syncthing",
autospec=True,
) as mock_class:
mock_class.return_value = mock_syncthing_client
assert await hass.config_entries.async_setup(entry.entry_id)
async with asyncio.timeout(5):
await received.wait()
assert captured == [event]