327 lines
6.3 KiB
Python
327 lines
6.3 KiB
Python
import asyncio
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from unittest.mock import patch
|
|
|
|
from schedule import (
|
|
ScheduleService,
|
|
load_schedule,
|
|
)
|
|
|
|
|
|
SCHEDULE_YAML = """
|
|
version: 1
|
|
|
|
enabled: true
|
|
timezone: local
|
|
|
|
templates:
|
|
|
|
test:
|
|
|
|
- time: "00:00"
|
|
action:
|
|
type: set
|
|
power: off
|
|
|
|
- time: "09:00"
|
|
action:
|
|
type: set
|
|
power: on
|
|
speed: 1
|
|
heater: on
|
|
target_temp: 20
|
|
|
|
- time: "10:00"
|
|
action:
|
|
type: auto
|
|
speed: 3
|
|
|
|
- time: "12:00"
|
|
action:
|
|
type: set
|
|
speed: 2
|
|
heater: off
|
|
|
|
days:
|
|
mon: test
|
|
tue: test
|
|
wed: test
|
|
thu: test
|
|
fri: test
|
|
sat: test
|
|
sun: test
|
|
"""
|
|
|
|
|
|
class FakeTionController:
|
|
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
async def power_on(self):
|
|
self.calls.append(
|
|
("power_on", None)
|
|
)
|
|
|
|
async def power_off(self):
|
|
self.calls.append(
|
|
("power_off", None)
|
|
)
|
|
|
|
async def set_speed(self, speed: int):
|
|
self.calls.append(
|
|
("set_speed", speed)
|
|
)
|
|
|
|
# Оставляем оба варианта управления heater,
|
|
# чтобы fake соответствовал фактическому
|
|
# интерфейсу TionController.
|
|
async def heater_on(self):
|
|
self.calls.append(
|
|
("heater_on", None)
|
|
)
|
|
|
|
async def heater_off(self):
|
|
self.calls.append(
|
|
("heater_off", None)
|
|
)
|
|
|
|
async def set_heater(self, enabled: bool):
|
|
self.calls.append(
|
|
("set_heater", enabled)
|
|
)
|
|
|
|
async def set_target_temperature(
|
|
self,
|
|
temperature: int,
|
|
):
|
|
self.calls.append(
|
|
(
|
|
"set_target_temperature",
|
|
temperature,
|
|
)
|
|
)
|
|
|
|
|
|
class FakeTionService:
|
|
|
|
def __init__(self):
|
|
self.controller = FakeTionController()
|
|
|
|
async def execute(self, operation):
|
|
return await operation(
|
|
self.controller
|
|
)
|
|
|
|
|
|
def load_test_schedule():
|
|
temp_dir = TemporaryDirectory()
|
|
|
|
path = (
|
|
Path(temp_dir.name)
|
|
/ "schedule.yaml"
|
|
)
|
|
|
|
path.write_text(
|
|
SCHEDULE_YAML,
|
|
encoding="utf-8",
|
|
)
|
|
|
|
config = load_schedule(path)
|
|
|
|
return temp_dir, config
|
|
|
|
|
|
def heater_calls(calls):
|
|
return [
|
|
call
|
|
for call in calls
|
|
if call[0] in {
|
|
"heater_on",
|
|
"heater_off",
|
|
"set_heater",
|
|
}
|
|
]
|
|
|
|
|
|
async def process_at(
|
|
service: ScheduleService,
|
|
when: datetime,
|
|
):
|
|
with patch(
|
|
"schedule.service.datetime",
|
|
wraps=datetime,
|
|
) as mocked_datetime:
|
|
|
|
mocked_datetime.now.return_value = when
|
|
|
|
await service._process()
|
|
|
|
|
|
async def test_auto_does_not_control_heater():
|
|
|
|
temp_dir, config = (
|
|
load_test_schedule()
|
|
)
|
|
|
|
try:
|
|
tion = FakeTionService()
|
|
|
|
service = ScheduleService(
|
|
config,
|
|
tion=tion,
|
|
)
|
|
|
|
# --------------------------------------------------
|
|
# 09:30
|
|
#
|
|
# Обычный SET.
|
|
# heater=on должен принадлежать ScheduleService.
|
|
# --------------------------------------------------
|
|
|
|
await process_at(
|
|
service,
|
|
datetime(
|
|
2026,
|
|
9,
|
|
14,
|
|
9,
|
|
30,
|
|
),
|
|
)
|
|
|
|
print()
|
|
print(
|
|
"09:30 SET calls:",
|
|
tion.controller.calls,
|
|
)
|
|
|
|
calls = heater_calls(
|
|
tion.controller.calls
|
|
)
|
|
|
|
assert calls != [], (
|
|
"SET must control heater"
|
|
)
|
|
|
|
tion.controller.calls.clear()
|
|
|
|
# --------------------------------------------------
|
|
# 10:30
|
|
#
|
|
# AUTO.
|
|
#
|
|
# В накопленных scheduled_settings heater всё ещё
|
|
# должен быть True, потому что последняя SET-точка
|
|
# включила heater.
|
|
#
|
|
# Но ScheduleService НЕ должен отправлять
|
|
# никаких heater-команд.
|
|
# --------------------------------------------------
|
|
|
|
resolution = service.resolve(
|
|
datetime(
|
|
2026,
|
|
9,
|
|
14,
|
|
10,
|
|
30,
|
|
)
|
|
)
|
|
|
|
print()
|
|
print(
|
|
"10:30 scheduled heater:",
|
|
resolution.scheduled_settings.heater,
|
|
)
|
|
|
|
assert (
|
|
resolution.auto_active
|
|
is True
|
|
)
|
|
|
|
assert (
|
|
resolution.scheduled_settings.heater
|
|
is True
|
|
)
|
|
|
|
await process_at(
|
|
service,
|
|
datetime(
|
|
2026,
|
|
9,
|
|
14,
|
|
10,
|
|
30,
|
|
),
|
|
)
|
|
|
|
print(
|
|
"10:30 AUTO calls:",
|
|
tion.controller.calls,
|
|
)
|
|
|
|
calls = heater_calls(
|
|
tion.controller.calls
|
|
)
|
|
|
|
assert calls == [], (
|
|
"ScheduleService must not control "
|
|
"heater during AUTO"
|
|
)
|
|
|
|
tion.controller.calls.clear()
|
|
|
|
# --------------------------------------------------
|
|
# 12:30
|
|
#
|
|
# AUTO закончился.
|
|
# heater=off снова принадлежит ScheduleService.
|
|
# --------------------------------------------------
|
|
|
|
await process_at(
|
|
service,
|
|
datetime(
|
|
2026,
|
|
9,
|
|
14,
|
|
12,
|
|
30,
|
|
),
|
|
)
|
|
|
|
print()
|
|
print(
|
|
"12:30 SET calls:",
|
|
tion.controller.calls,
|
|
)
|
|
|
|
calls = heater_calls(
|
|
tion.controller.calls
|
|
)
|
|
|
|
assert calls != [], (
|
|
"ScheduleService must regain heater "
|
|
"control after AUTO"
|
|
)
|
|
|
|
finally:
|
|
temp_dir.cleanup()
|
|
|
|
|
|
async def main():
|
|
|
|
await test_auto_does_not_control_heater()
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print(
|
|
"ALL AUTO HEATER OWNERSHIP TESTS PASSED"
|
|
)
|
|
print("=" * 70)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main()) |