1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-18 14:29:57 +01:00
Files

227 lines
8.5 KiB
Python

"""Test the OMIE sensor platform."""
import datetime as dt
from unittest.mock import MagicMock
import aiohttp
from freezegun import freeze_time
from pyomie.model import OMIEResults
import pytest
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import spot_price_fetcher
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_sensor_setup(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pyomie: MagicMock,
entity_registry: er.EntityRegistry,
) -> None:
"""Test sensor platform setup."""
mock_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entities = er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id
)
# Should have 2 sensors (PT and ES)
assert len(entities) == 2
unique_ids = {entity.unique_id for entity in entities}
expected_ids = {"pt_spot_price", "es_spot_price"}
assert unique_ids == expected_ids
@pytest.mark.usefixtures("hass_lisbon")
async def test_sensor_state_lisbon_timezone(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pyomie: MagicMock,
mock_omie_results_jan15: OMIEResults,
mock_omie_results_jan16: OMIEResults,
) -> None:
"""Test sensor state updates in Lisbon timezone across publication boundary."""
mock_config_entry.add_to_hass(hass)
mock_pyomie.spot_price.side_effect = spot_price_fetcher(
{
"2024-01-15": mock_omie_results_jan15,
"2024-01-16": mock_omie_results_jan16,
}
)
# Step 1: 1 PM CET (before 13:30 CET publication)
# Lisbon day spans two CET dates: Jan 15 available, Jan 16 not yet published
with freeze_time("2024-01-15T12:01:00Z"): # 12:01 UTC = 12:01 Lisbon = 13:01 CET
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_pyomie.spot_price.call_count == 1
# Step 2: 3 PM CET - verify listeners update with existing data, no new API calls
mock_pyomie.spot_price.reset_mock()
with freeze_time("2024-01-15T14:01:00Z"): # 14:01 UTC = 14:01 Lisbon = 15:01 CET
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_pyomie.spot_price.call_count == 0
pt_state_14 = hass.states.get("sensor.omie_portugal_spot_price")
es_state_14 = hass.states.get("sensor.omie_spain_spot_price")
assert pt_state_14.state == "351151500.0"
assert es_state_14.state == "34151500.0"
assert pt_state_14.attributes["unit_of_measurement"] == "€/kWh"
assert es_state_14.attributes["unit_of_measurement"] == "€/kWh"
# 23 UTC = 23 Lisbon = 00 CET (+1 day)
mock_pyomie.spot_price.reset_mock()
with freeze_time("2024-01-15T23:01:00Z"):
async_fire_time_changed(hass)
await hass.async_block_till_done()
# CET has rolled over to the next date, one additional
# call to OMIE must be made.
assert mock_pyomie.spot_price.call_count == 1
pt_state_23 = hass.states.get("sensor.omie_portugal_spot_price")
es_state_23 = hass.states.get("sensor.omie_spain_spot_price")
assert pt_state_23.state == "351160000.0"
assert es_state_23.state == "34160000.0"
# 00 UTC (+1 day) = 00 Lisbon (+1 day) = 01 CET (+1 day)
mock_pyomie.spot_price.reset_mock()
with freeze_time("2024-01-16T00:31:00Z"):
async_fire_time_changed(hass)
await hass.async_block_till_done()
# No additional API calls should be made, was already fetched at 00 PM CET
assert mock_pyomie.spot_price.call_count == 0
pt_state_00 = hass.states.get("sensor.omie_portugal_spot_price")
es_state_00 = hass.states.get("sensor.omie_spain_spot_price")
assert pt_state_00.state == "351160130.0"
assert es_state_00.state == "34160130.0"
@pytest.mark.usefixtures("hass_madrid")
async def test_sensor_state_madrid_timezone(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pyomie: MagicMock,
mock_omie_results_jan15: OMIEResults,
mock_omie_results_jan16: OMIEResults,
) -> None:
"""Test sensor state updates in Madrid timezone across publication boundary."""
mock_config_entry.add_to_hass(hass)
mock_pyomie.spot_price.side_effect = spot_price_fetcher(
{
"2024-01-15": mock_omie_results_jan15,
"2024-01-16": mock_omie_results_jan16,
}
)
# Step 1: 1 PM CET (before 13:30 CET publication) - only Jan 15 data available
with freeze_time("2024-01-15T12:01:00Z"): # 12:00 UTC = 1 PM CET (Madrid)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_pyomie.spot_price.call_count == 1
# Step 2: 2 PM CET (after 13:30 CET publication) - Jan 16 data now available
mock_pyomie.spot_price.reset_mock()
with freeze_time("2024-01-15T13:01:00Z"): # 13:00 UTC = 2 PM CET (Madrid)
async_fire_time_changed(hass)
await hass.async_block_till_done()
# No additional call needed - Madrid only needs Jan 15 for the full day
assert mock_pyomie.spot_price.call_count == 0
# Step 3: 3 PM CET - verify listeners update with existing data, no new API calls
mock_pyomie.spot_price.reset_mock()
with freeze_time("2024-01-15T14:23:00Z"): # 14:23 UTC = 3:23 PM CET (Madrid)
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Still no additional call - Madrid doesn't need Jan 16 for Jan 15 prices
assert mock_pyomie.spot_price.call_count == 0
pt_state = hass.states.get("sensor.omie_portugal_spot_price")
es_state = hass.states.get("sensor.omie_spain_spot_price")
assert pt_state.state == "351151515.0"
assert es_state.state == "34151515.0"
assert pt_state.attributes["unit_of_measurement"] == "€/kWh"
assert es_state.attributes["unit_of_measurement"] == "€/kWh"
@pytest.mark.parametrize(
"raise_exc", [Exception("something bad"), aiohttp.ClientError("Connection timeout")]
)
async def test_sensor_unavailable_when_pyomie_throws(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pyomie: MagicMock,
mock_omie_results_jan15: OMIEResults,
raise_exc: Exception,
) -> None:
"""Test sensor becomes unavailable when pyomie throws."""
mock_config_entry.add_to_hass(hass)
mock_pyomie.spot_price.side_effect = spot_price_fetcher(
{"2024-01-15": mock_omie_results_jan15}
)
# Setup at 22:01 UTC (23:01 CET, still Jan 15 in CET)
with freeze_time("2024-01-15T22:01:00Z"):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Advance to 23:16 UTC (00:16 CET Jan 16) — new CET day forces a fresh fetch
mock_pyomie.spot_price.side_effect = raise_exc
with freeze_time("2024-01-15T23:16:02Z"):
async_fire_time_changed(hass)
await hass.async_block_till_done()
pt_state = hass.states.get("sensor.omie_portugal_spot_price")
es_state = hass.states.get("sensor.omie_spain_spot_price")
assert pt_state.state == STATE_UNAVAILABLE
assert es_state.state == STATE_UNAVAILABLE
async def test_sensor_unavailable_when_pyomie_returns_incomplete_data(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pyomie: MagicMock,
) -> None:
"""Test sensor becomes unavailable when no data is available."""
mock_config_entry.add_to_hass(hass)
mock_pyomie.spot_price.side_effect = spot_price_fetcher(
{
"2024-01-15": OMIEResults(
updated_at=dt.datetime(2024, 1, 15, 12, 1, tzinfo=dt.UTC),
market_date=dt.date.fromisoformat("2024-01-15"),
contents=None,
raw="",
)
}
)
with freeze_time("2024-01-15T12:01:00Z"):
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
with freeze_time("2024-01-15T12:16:02Z"):
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Both sensors should be unavailable
pt_state = hass.states.get("sensor.omie_portugal_spot_price")
es_state = hass.states.get("sensor.omie_spain_spot_price")
assert pt_state.state == STATE_UNAVAILABLE
assert es_state.state == STATE_UNAVAILABLE