hass.tibber_prices/tests/test_midnight_periods.py

136 lines
4.8 KiB
Python

"""Test midnight-crossing period assignment with group_periods_by_day()."""
from __future__ import annotations
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import pytest
from custom_components.tibber_prices.coordinator.period_handlers.relaxation import (
group_periods_by_day,
)
@pytest.fixture
def base_date() -> datetime:
"""Provide base date for tests."""
return datetime(2025, 11, 21, 0, 0, 0, tzinfo=ZoneInfo("Europe/Berlin"))
def create_test_period(start_hour: int, end_hour: int, base_date: datetime) -> dict:
"""Create a test period dict."""
start = base_date.replace(hour=start_hour, minute=0, second=0, microsecond=0)
# Handle periods crossing midnight
if end_hour < start_hour:
end = (base_date + timedelta(days=1)).replace(hour=end_hour, minute=0, second=0, microsecond=0)
else:
end = base_date.replace(hour=end_hour, minute=0, second=0, microsecond=0)
return {
"start": start,
"end": end,
"duration_minutes": int((end - start).total_seconds() / 60),
"price_median": 25.5,
}
@pytest.mark.unit
def test_period_within_single_day(base_date: datetime) -> None:
"""Test period completely within one day (10:00-14:00)."""
periods = [create_test_period(10, 14, base_date)]
result = group_periods_by_day(periods)
assert len(result) == 1, f"Expected 1 day, got {len(result)}"
@pytest.mark.unit
def test_period_crossing_midnight(base_date: datetime) -> None:
"""Test period crossing midnight (23:00-02:00)."""
periods = [create_test_period(23, 2, base_date)]
result = group_periods_by_day(periods)
assert len(result) == 2, f"Expected 2 days, got {len(result)}"
# Verify the period appears in both days
today = base_date.date()
tomorrow = (base_date + timedelta(days=1)).date()
assert today in result, f"Period should appear in {today}"
assert tomorrow in result, f"Period should appear in {tomorrow}"
@pytest.mark.unit
def test_multiple_periods_with_midnight_crossing(base_date: datetime) -> None:
"""Test multiple periods, some crossing midnight (8:00-12:00, 14:00-18:00, 22:00-03:00)."""
periods = [
create_test_period(8, 12, base_date), # Morning, same day
create_test_period(14, 18, base_date), # Afternoon, same day
create_test_period(22, 3, base_date), # Night, crosses midnight
]
result = group_periods_by_day(periods)
today = base_date.date()
tomorrow = (base_date + timedelta(days=1)).date()
# Check that today has 3 periods (all of them)
today_periods = result.get(today, [])
assert len(today_periods) == 3, f"Today should have 3 periods, got {len(today_periods)}"
# Check that tomorrow has 1 period (the midnight-crossing one)
tomorrow_periods = result.get(tomorrow, [])
assert len(tomorrow_periods) == 1, f"Tomorrow should have 1 period, got {len(tomorrow_periods)}"
@pytest.mark.unit
def test_period_spanning_three_days(base_date: datetime) -> None:
"""Test period spanning 3 days (22:00 day1 - 02:00 day3)."""
day1 = base_date
day3 = base_date + timedelta(days=2)
period = {
"start": day1.replace(hour=22, minute=0),
"end": day3.replace(hour=2, minute=0),
"duration_minutes": int((day3.replace(hour=2) - day1.replace(hour=22)).total_seconds() / 60),
"price_median": 25.5,
}
periods = [period]
result = group_periods_by_day(periods)
assert len(result) == 3, f"Expected 3 days, got {len(result)}"
# Verify the period appears in all 3 days
day1_date = day1.date()
day2_date = (base_date + timedelta(days=1)).date()
day3_date = day3.date()
assert day1_date in result, f"Period should appear in {day1_date}"
assert day2_date in result, f"Period should appear in {day2_date}"
assert day3_date in result, f"Period should appear in {day3_date}"
@pytest.mark.unit
def test_min_periods_scenario(base_date: datetime) -> None:
"""Test real-world scenario with min_periods=2 per day."""
# Yesterday: 2 periods (one crosses midnight to today)
yesterday = base_date - timedelta(days=1)
periods = [
create_test_period(10, 14, yesterday), # Yesterday 10-14
create_test_period(23, 2, yesterday), # Yesterday 23 - Today 02 (crosses midnight!)
create_test_period(15, 19, base_date), # Today 15-19
]
result = group_periods_by_day(periods)
yesterday_date = yesterday.date()
today_date = base_date.date()
yesterday_periods = result.get(yesterday_date, [])
today_periods = result.get(today_date, [])
# Both days should have 2 periods (min_periods requirement met)
assert len(yesterday_periods) == 2, "Yesterday should have 2 periods"
assert len(today_periods) == 2, "Today should have 2 periods (including midnight-crosser)"