Convert Feedreader to use an update coordinator (#118007)

This commit is contained in:
Michael
2024-05-27 14:48:41 +02:00
committed by GitHub
parent 97f6b578c8
commit a24d97d79d
4 changed files with 251 additions and 387 deletions

View File

@@ -1,23 +1,15 @@
"""The tests for the feedreader component."""
from collections.abc import Generator
from datetime import datetime, timedelta
import pickle
from time import gmtime
from typing import Any
from unittest import mock
from unittest.mock import MagicMock, mock_open, patch
from unittest.mock import patch
import pytest
from homeassistant.components import feedreader
from homeassistant.components.feedreader import (
CONF_MAX_ENTRIES,
CONF_URLS,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
EVENT_FEEDREADER,
)
from homeassistant.components.feedreader import CONF_MAX_ENTRIES, CONF_URLS
from homeassistant.components.feedreader.const import DOMAIN
from homeassistant.components.feedreader.coordinator import EVENT_FEEDREADER
from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START
from homeassistant.core import Event, HomeAssistant
from homeassistant.setup import async_setup_component
@@ -26,11 +18,11 @@ import homeassistant.util.dt as dt_util
from tests.common import async_capture_events, async_fire_time_changed, load_fixture
URL = "http://some.rss.local/rss_feed.xml"
VALID_CONFIG_1 = {feedreader.DOMAIN: {CONF_URLS: [URL]}}
VALID_CONFIG_2 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_SCAN_INTERVAL: 60}}
VALID_CONFIG_3 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 100}}
VALID_CONFIG_4 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 5}}
VALID_CONFIG_5 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 1}}
VALID_CONFIG_1 = {DOMAIN: {CONF_URLS: [URL]}}
VALID_CONFIG_2 = {DOMAIN: {CONF_URLS: [URL], CONF_SCAN_INTERVAL: 60}}
VALID_CONFIG_3 = {DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 100}}
VALID_CONFIG_4 = {DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 5}}
VALID_CONFIG_5 = {DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 1}}
def load_fixture_bytes(src: str) -> bytes:
@@ -81,105 +73,36 @@ async def fixture_events(hass: HomeAssistant) -> list[Event]:
return async_capture_events(hass, EVENT_FEEDREADER)
@pytest.fixture(name="storage")
def fixture_storage(request: pytest.FixtureRequest) -> Generator[None, None, None]:
"""Set up the test storage environment."""
if request.param == "legacy_storage":
with patch("os.path.exists", return_value=False):
yield
elif request.param == "json_storage":
with patch("os.path.exists", return_value=True):
yield
else:
raise RuntimeError("Invalid storage fixture")
@pytest.fixture(name="legacy_storage_open")
def fixture_legacy_storage_open() -> Generator[MagicMock, None, None]:
"""Mock builtins.open for feedreader storage."""
with patch(
"homeassistant.components.feedreader.open",
mock_open(),
create=True,
) as open_mock:
yield open_mock
@pytest.fixture(name="legacy_storage_load", autouse=True)
def fixture_legacy_storage_load(
legacy_storage_open,
) -> Generator[MagicMock, None, None]:
"""Mock builtins.open for feedreader storage."""
with patch(
"homeassistant.components.feedreader.pickle.load", return_value={}
) as pickle_load:
yield pickle_load
async def test_setup_one_feed(hass: HomeAssistant) -> None:
"""Test the general setup of this component."""
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_1)
async def test_setup_no_feeds(hass: HomeAssistant) -> None:
"""Test config with no urls."""
assert not await async_setup_component(
hass, feedreader.DOMAIN, {feedreader.DOMAIN: {CONF_URLS: []}}
)
assert not await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_URLS: []}})
@pytest.mark.parametrize(
("open_error", "load_error"),
[
(FileNotFoundError("No file"), None),
(OSError("Boom"), None),
(None, pickle.PickleError("Bad data")),
],
)
async def test_legacy_storage_error(
hass: HomeAssistant,
legacy_storage_open: MagicMock,
legacy_storage_load: MagicMock,
open_error: Exception | None,
load_error: Exception | None,
) -> None:
"""Test legacy storage error."""
legacy_storage_open.side_effect = open_error
legacy_storage_load.side_effect = load_error
with patch(
"homeassistant.components.feedreader.async_track_time_interval"
) as track_method:
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_1)
await hass.async_block_till_done()
track_method.assert_called_once_with(
hass, mock.ANY, DEFAULT_SCAN_INTERVAL, cancel_on_shutdown=True
)
@pytest.mark.parametrize("storage", ["legacy_storage", "json_storage"], indirect=True)
async def test_storage_data_loading(
hass: HomeAssistant,
events: list[Event],
feed_one_event: bytes,
legacy_storage_load: MagicMock,
hass_storage: dict[str, Any],
storage: None,
) -> None:
"""Test loading existing storage data."""
storage_data: dict[str, str] = {URL: "2018-04-30T05:10:00+00:00"}
hass_storage[feedreader.DOMAIN] = {
hass_storage[DOMAIN] = {
"version": 1,
"minor_version": 1,
"key": feedreader.DOMAIN,
"key": DOMAIN,
"data": storage_data,
}
legacy_storage_data = {
URL: gmtime(datetime.fromisoformat(storage_data[URL]).timestamp())
}
legacy_storage_load.return_value = legacy_storage_data
with patch(
"feedparser.http.get",
return_value=feed_one_event,
):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -202,9 +125,9 @@ async def test_storage_data_writing(
"feedparser.http.get",
return_value=feed_one_event,
),
patch("homeassistant.components.feedreader.DELAY_SAVE", new=0),
patch("homeassistant.components.feedreader.coordinator.DELAY_SAVE", new=0),
):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -213,39 +136,12 @@ async def test_storage_data_writing(
assert len(events) == 1
# storage data updated
assert hass_storage[feedreader.DOMAIN]["data"] == storage_data
@pytest.mark.parametrize("storage", ["legacy_storage", "json_storage"], indirect=True)
async def test_setup_one_feed(hass: HomeAssistant, storage: None) -> None:
"""Test the general setup of this component."""
with patch(
"homeassistant.components.feedreader.async_track_time_interval"
) as track_method:
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_1)
await hass.async_block_till_done()
track_method.assert_called_once_with(
hass, mock.ANY, DEFAULT_SCAN_INTERVAL, cancel_on_shutdown=True
)
async def test_setup_scan_interval(hass: HomeAssistant) -> None:
"""Test the setup of this component with scan interval."""
with patch(
"homeassistant.components.feedreader.async_track_time_interval"
) as track_method:
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
await hass.async_block_till_done()
track_method.assert_called_once_with(
hass, mock.ANY, timedelta(seconds=60), cancel_on_shutdown=True
)
assert hass_storage[DOMAIN]["data"] == storage_data
async def test_setup_max_entries(hass: HomeAssistant) -> None:
"""Test the setup of this component with max entries."""
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_3)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_3)
await hass.async_block_till_done()
@@ -255,7 +151,7 @@ async def test_feed(hass: HomeAssistant, events, feed_one_event) -> None:
"feedparser.http.get",
return_value=feed_one_event,
):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -278,7 +174,7 @@ async def test_atom_feed(hass: HomeAssistant, events, feed_atom_event) -> None:
"feedparser.http.get",
return_value=feed_atom_event,
):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_5)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_5)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -305,13 +201,13 @@ async def test_feed_identical_timestamps(
return_value=feed_identically_timed_events,
),
patch(
"homeassistant.components.feedreader.StoredData.get_timestamp",
"homeassistant.components.feedreader.coordinator.StoredData.get_timestamp",
return_value=gmtime(
datetime.fromisoformat("1970-01-01T00:00:00.0+0000").timestamp()
),
),
):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -365,10 +261,11 @@ async def test_feed_updates(
feed_two_event,
]
with patch("feedparser.http.get", side_effect=side_effect):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
with patch(
"homeassistant.components.feedreader.coordinator.feedparser.http.get",
side_effect=side_effect,
):
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
await hass.async_block_till_done()
assert len(events) == 1
@@ -393,7 +290,7 @@ async def test_feed_default_max_length(
) -> None:
"""Test long feed beyond the default 20 entry limit."""
with patch("feedparser.http.get", return_value=feed_21_events):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -404,7 +301,7 @@ async def test_feed_default_max_length(
async def test_feed_max_length(hass: HomeAssistant, events, feed_21_events) -> None:
"""Test long feed beyond a configured 5 entry limit."""
with patch("feedparser.http.get", return_value=feed_21_events):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_4)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_4)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -417,7 +314,7 @@ async def test_feed_without_publication_date_and_title(
) -> None:
"""Test simple feed with entry without publication date and title."""
with patch("feedparser.http.get", return_value=feed_three_events):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -432,7 +329,7 @@ async def test_feed_with_unrecognized_publication_date(
with patch(
"feedparser.http.get", return_value=load_fixture_bytes("feedreader4.xml")
):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -444,7 +341,7 @@ async def test_feed_invalid_data(hass: HomeAssistant, events) -> None:
"""Test feed with invalid data."""
invalid_data = bytes("INVALID DATA", "utf-8")
with patch("feedparser.http.get", return_value=invalid_data):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
@@ -459,7 +356,7 @@ async def test_feed_parsing_failed(
assert "Error fetching feed data" not in caplog.text
with patch("feedparser.parse", return_value=None):
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2)
assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_2)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()