File: test_schedule_models.py

package info (click to toggle)
python-bsblan 3.1.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,028 kB
  • sloc: python: 4,453; makefile: 3
file content (229 lines) | stat: -rw-r--r-- 9,278 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""Test cases for schedule models (TimeSlot, DaySchedule, DHWSchedule)."""

from __future__ import annotations

from datetime import time

import pytest

from bsblan.models import DaySchedule, DHWSchedule, TimeSlot


class TestTimeSlot:
    """Test cases for TimeSlot dataclass."""

    def test_valid_time_slot(self) -> None:
        """Test creating a valid time slot."""
        slot = TimeSlot(start=time(6, 0), end=time(8, 0))
        assert slot.start == time(6, 0)
        assert slot.end == time(8, 0)

    def test_time_slot_to_bsblan_format(self) -> None:
        """Test converting time slot to BSB-LAN format."""
        slot = TimeSlot(start=time(6, 0), end=time(8, 0))
        assert slot.to_bsblan_format() == "06:00-08:00"

    def test_time_slot_to_bsblan_format_with_minutes(self) -> None:
        """Test converting time slot with non-zero minutes."""
        slot = TimeSlot(start=time(6, 30), end=time(8, 45))
        assert slot.to_bsblan_format() == "06:30-08:45"

    def test_time_slot_from_bsblan_format(self) -> None:
        """Test parsing time slot from BSB-LAN format."""
        slot = TimeSlot.from_bsblan_format("06:00-08:00")
        assert slot.start == time(6, 0)
        assert slot.end == time(8, 0)

    def test_time_slot_from_bsblan_format_with_minutes(self) -> None:
        """Test parsing time slot with non-zero minutes."""
        slot = TimeSlot.from_bsblan_format("17:30-21:45")
        assert slot.start == time(17, 30)
        assert slot.end == time(21, 45)

    def test_time_slot_invalid_start_after_end(self) -> None:
        """Test that start time must be before end time."""
        with pytest.raises(ValueError, match="must be before end time"):
            TimeSlot(start=time(10, 0), end=time(8, 0))

    def test_time_slot_invalid_start_equals_end(self) -> None:
        """Test that start time cannot equal end time."""
        with pytest.raises(ValueError, match="must be before end time"):
            TimeSlot(start=time(8, 0), end=time(8, 0))

    def test_time_slot_from_invalid_format(self) -> None:
        """Test parsing invalid format raises ValueError."""
        with pytest.raises(ValueError, match="Invalid time slot format"):
            TimeSlot.from_bsblan_format("invalid")

    def test_time_slot_from_invalid_format_missing_dash(self) -> None:
        """Test parsing format without dash raises ValueError."""
        with pytest.raises(ValueError, match="Invalid time slot format"):
            TimeSlot.from_bsblan_format("06:00 08:00")

    def test_time_slot_from_invalid_format_bad_time(self) -> None:
        """Test parsing format with bad time values raises ValueError."""
        with pytest.raises(ValueError, match="Invalid time slot format"):
            TimeSlot.from_bsblan_format("25:00-08:00")

    def test_time_slot_roundtrip(self) -> None:
        """Test that converting to and from BSB-LAN format preserves values."""
        original = TimeSlot(start=time(6, 30), end=time(17, 45))
        bsblan_str = original.to_bsblan_format()
        parsed = TimeSlot.from_bsblan_format(bsblan_str)
        assert parsed.start == original.start
        assert parsed.end == original.end


class TestDaySchedule:
    """Test cases for DaySchedule dataclass."""

    def test_empty_day_schedule(self) -> None:
        """Test creating an empty day schedule."""
        schedule = DaySchedule()
        assert schedule.slots == []

    def test_day_schedule_with_slots(self) -> None:
        """Test creating a day schedule with time slots."""
        slots = [
            TimeSlot(time(6, 0), time(8, 0)),
            TimeSlot(time(17, 0), time(21, 0)),
        ]
        schedule = DaySchedule(slots=slots)
        assert len(schedule.slots) == 2

    def test_day_schedule_to_bsblan_format(self) -> None:
        """Test converting day schedule to BSB-LAN format."""
        schedule = DaySchedule(
            slots=[
                TimeSlot(time(6, 0), time(8, 0)),
                TimeSlot(time(17, 0), time(21, 0)),
            ]
        )
        assert schedule.to_bsblan_format() == "06:00-08:00 17:00-21:00"

    def test_day_schedule_to_bsblan_format_empty(self) -> None:
        """Test converting empty day schedule to BSB-LAN format."""
        schedule = DaySchedule()
        assert schedule.to_bsblan_format() == ""

    def test_day_schedule_to_bsblan_format_single_slot(self) -> None:
        """Test converting day schedule with single slot."""
        schedule = DaySchedule(slots=[TimeSlot(time(6, 0), time(8, 0))])
        assert schedule.to_bsblan_format() == "06:00-08:00"

    def test_day_schedule_from_bsblan_format(self) -> None:
        """Test parsing day schedule from BSB-LAN format."""
        schedule = DaySchedule.from_bsblan_format("06:00-08:00 17:00-21:00")
        assert len(schedule.slots) == 2
        assert schedule.slots[0].start == time(6, 0)
        assert schedule.slots[1].start == time(17, 0)

    def test_day_schedule_from_bsblan_format_empty(self) -> None:
        """Test parsing empty string returns empty schedule."""
        schedule = DaySchedule.from_bsblan_format("")
        assert schedule.slots == []

    def test_day_schedule_from_bsblan_format_undefined(self) -> None:
        """Test parsing '---' returns empty schedule."""
        schedule = DaySchedule.from_bsblan_format("---")
        assert schedule.slots == []

    def test_day_schedule_max_slots_valid(self) -> None:
        """Test that 3 slots (BSB-LAN max) is valid."""
        schedule = DaySchedule(
            slots=[
                TimeSlot(time(6, 0), time(8, 0)),
                TimeSlot(time(12, 0), time(13, 0)),
                TimeSlot(time(17, 0), time(21, 0)),
            ]
        )
        assert len(schedule.slots) == 3

    def test_day_schedule_too_many_slots(self) -> None:
        """Test that more than 3 slots raises ValueError."""
        with pytest.raises(ValueError, match="maximum 3 time slots per day"):
            DaySchedule(
                slots=[
                    TimeSlot(time(6, 0), time(7, 0)),
                    TimeSlot(time(8, 0), time(9, 0)),
                    TimeSlot(time(10, 0), time(11, 0)),
                    TimeSlot(time(12, 0), time(13, 0)),
                ]
            )

    def test_day_schedule_roundtrip(self) -> None:
        """Test that converting to and from BSB-LAN format preserves values."""
        original = DaySchedule(
            slots=[
                TimeSlot(time(6, 0), time(8, 0)),
                TimeSlot(time(17, 0), time(21, 0)),
            ]
        )
        bsblan_str = original.to_bsblan_format()
        parsed = DaySchedule.from_bsblan_format(bsblan_str)
        assert len(parsed.slots) == len(original.slots)
        for orig_slot, parsed_slot in zip(original.slots, parsed.slots, strict=True):
            assert orig_slot.start == parsed_slot.start
            assert orig_slot.end == parsed_slot.end


class TestDHWSchedule:
    """Test cases for DHWSchedule dataclass."""

    def test_empty_dhw_schedule(self) -> None:
        """Test creating an empty DHW schedule."""
        schedule = DHWSchedule()
        assert schedule.monday is None
        assert schedule.tuesday is None
        assert not schedule.has_any_schedule()

    def test_dhw_schedule_with_days(self) -> None:
        """Test creating a DHW schedule with multiple days."""
        monday = DaySchedule(slots=[TimeSlot(time(6, 0), time(8, 0))])
        tuesday = DaySchedule(slots=[TimeSlot(time(7, 0), time(9, 0))])
        schedule = DHWSchedule(monday=monday, tuesday=tuesday)
        assert schedule.monday is not None
        assert schedule.tuesday is not None
        assert schedule.wednesday is None

    def test_dhw_schedule_has_any_schedule_true(self) -> None:
        """Test has_any_schedule returns True when a day is set."""
        schedule = DHWSchedule(
            monday=DaySchedule(slots=[TimeSlot(time(6, 0), time(8, 0))])
        )
        assert schedule.has_any_schedule() is True

    def test_dhw_schedule_has_any_schedule_false(self) -> None:
        """Test has_any_schedule returns False when no days are set."""
        schedule = DHWSchedule()
        assert schedule.has_any_schedule() is False

    def test_dhw_schedule_all_days(self) -> None:
        """Test setting all days of the week."""
        day = DaySchedule(slots=[TimeSlot(time(6, 0), time(8, 0))])
        schedule = DHWSchedule(
            monday=day,
            tuesday=day,
            wednesday=day,
            thursday=day,
            friday=day,
            saturday=day,
            sunday=day,
        )
        assert schedule.has_any_schedule() is True
        assert schedule.monday is not None
        assert schedule.sunday is not None

    def test_dhw_schedule_weekend_only(self) -> None:
        """Test setting only weekend days."""
        weekend = DaySchedule(
            slots=[
                TimeSlot(time(8, 0), time(10, 0)),
                TimeSlot(time(18, 0), time(22, 0)),
            ]
        )
        schedule = DHWSchedule(saturday=weekend, sunday=weekend)
        assert schedule.saturday is not None
        assert schedule.sunday is not None
        assert schedule.monday is None
        assert schedule.has_any_schedule() is True