58 lines
2.8 KiB
Python
58 lines
2.8 KiB
Python
import datetime as dt
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import subprocess
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
spec = importlib.util.spec_from_file_location('power_cycle', Path(__file__).resolve().parents[1] / 'scripts/night-power-cycle.py')
|
|
power = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(power)
|
|
|
|
|
|
class PowerCycleTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.now = dt.datetime(2026, 11, 10, 0, 0, 1, tzinfo=power.TZ)
|
|
|
|
def test_six_am_uses_shanghai_time_even_when_input_is_utc(self):
|
|
target = power.wake_time(self.now.astimezone(dt.timezone.utc))
|
|
self.assertEqual(target.isoformat(), '2026-11-10T06:00:00+08:00')
|
|
tomorrow = power.wake_time(self.now.replace(hour=18))
|
|
self.assertEqual(tomorrow.isoformat(), '2026-11-11T06:00:00+08:00')
|
|
|
|
def test_dry_check_and_daytime_run_never_shutdown(self):
|
|
with patch.object(power.shutil, 'which', side_effect=lambda name: '/usr/bin/' + name), \
|
|
patch.object(Path, 'exists', return_value=True), \
|
|
patch.object(power.subprocess, 'run') as run:
|
|
power.power_cycle(self.now, check=True)
|
|
run.assert_not_called()
|
|
with self.assertRaises(RuntimeError):
|
|
power.power_cycle(self.now.replace(hour=18))
|
|
run.assert_not_called()
|
|
|
|
def test_shutdown_only_after_alarm_is_armed_and_verified(self):
|
|
target = power.wake_time(self.now)
|
|
with patch.object(power.shutil, 'which', side_effect=lambda name: '/usr/bin/' + name), \
|
|
patch.object(Path, 'exists', return_value=True), \
|
|
patch.object(Path, 'read_text', return_value=str(int(target.timestamp()))), \
|
|
patch.object(power.subprocess, 'run') as run:
|
|
power.power_cycle(self.now)
|
|
self.assertEqual(run.call_count, 2)
|
|
self.assertIn('no', run.call_args_list[0].args[0])
|
|
self.assertIn(str(int(target.timestamp())), run.call_args_list[0].args[0])
|
|
self.assertEqual(run.call_args_list[1].args[0], ['/usr/bin/systemctl', '--no-block', 'poweroff'])
|
|
|
|
def test_failed_or_wrong_alarm_prevents_shutdown(self):
|
|
with patch.object(power.shutil, 'which', side_effect=lambda name: '/usr/bin/' + name), \
|
|
patch.object(Path, 'exists', return_value=True), \
|
|
patch.object(Path, 'read_text', return_value='0'), \
|
|
patch.object(power.subprocess, 'run') as run:
|
|
with self.assertRaises(RuntimeError):
|
|
power.power_cycle(self.now)
|
|
self.assertEqual(run.call_count, 1)
|
|
run.reset_mock()
|
|
run.side_effect = subprocess.CalledProcessError(1, 'rtcwake')
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
power.power_cycle(self.now)
|
|
self.assertEqual(run.call_count, 1)
|