first commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from app.danmu_queue import SystemScheduler
|
||||
|
||||
|
||||
class _FakeConfig:
|
||||
def __init__(self, system_cfg):
|
||||
self.system_cfg = system_cfg
|
||||
|
||||
|
||||
class RebootAfterStopTests(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
self.logger = logging.getLogger("test-system-scheduler")
|
||||
self.scheduler = SystemScheduler(
|
||||
_FakeConfig({
|
||||
"reboot_after_stop_enabled": True,
|
||||
"reboot_after_stop_delay_sec": 60,
|
||||
}),
|
||||
self.logger,
|
||||
)
|
||||
|
||||
@patch("app.danmu_queue.os.name", "nt")
|
||||
@patch("app.danmu_queue.subprocess.Popen")
|
||||
def test_schedules_one_minute_reboot_only_once(self, popen):
|
||||
stopped_at = datetime(2026, 7, 23, 23, 0)
|
||||
|
||||
self.assertTrue(self.scheduler._schedule_reboot_after_stop(stopped_at))
|
||||
self.assertFalse(self.scheduler._schedule_reboot_after_stop(stopped_at))
|
||||
|
||||
popen.assert_called_once()
|
||||
command = popen.call_args.args[0]
|
||||
self.assertEqual(command[:5], ["shutdown", "/r", "/t", "60", "/c"])
|
||||
|
||||
async def test_failed_stop_still_schedules_reboot(self):
|
||||
self.scheduler._live_occurrences = MagicMock(
|
||||
return_value=[("stop", datetime.now().replace(second=0, microsecond=0))]
|
||||
)
|
||||
self.scheduler.stop_bilibili_live = AsyncMock(return_value=False)
|
||||
self.scheduler._schedule_reboot_after_stop = MagicMock()
|
||||
self.scheduler._end_live_statistics = MagicMock()
|
||||
|
||||
async def stop_after_first_sleep(_seconds):
|
||||
self.scheduler._stop = True
|
||||
|
||||
with patch("app.danmu_queue.asyncio.sleep", side_effect=stop_after_first_sleep):
|
||||
await self.scheduler.run()
|
||||
|
||||
self.scheduler._schedule_reboot_after_stop.assert_called_once()
|
||||
|
||||
async def test_stop_exception_still_schedules_reboot(self):
|
||||
self.scheduler._live_occurrences = MagicMock(
|
||||
return_value=[("stop", datetime.now().replace(second=0, microsecond=0))]
|
||||
)
|
||||
self.scheduler.stop_bilibili_live = AsyncMock(side_effect=RuntimeError("click failed"))
|
||||
self.scheduler._schedule_reboot_after_stop = MagicMock()
|
||||
self.scheduler._end_live_statistics = MagicMock()
|
||||
|
||||
async def stop_after_first_sleep(_seconds):
|
||||
self.scheduler._stop = True
|
||||
|
||||
with patch("app.danmu_queue.asyncio.sleep", side_effect=stop_after_first_sleep):
|
||||
await self.scheduler.run()
|
||||
|
||||
self.scheduler._schedule_reboot_after_stop.assert_called_once()
|
||||
|
||||
|
||||
class StartupLiveCompensationTests(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
self.logger = logging.getLogger("test-startup-live-compensation")
|
||||
self.scheduler = SystemScheduler(
|
||||
_FakeConfig({
|
||||
"live_start_time": "09:00",
|
||||
"live_end_time": "23:00",
|
||||
"launch_bilibili_live_enabled": True,
|
||||
"launch_genshin_enabled": True,
|
||||
"bilibili_push_enabled": True,
|
||||
}),
|
||||
self.logger,
|
||||
)
|
||||
self.start_at = datetime.now() - timedelta(hours=1)
|
||||
self.end_at = datetime.now() + timedelta(hours=1)
|
||||
self.scheduler._startup_window_active = MagicMock(return_value=True)
|
||||
self.scheduler._is_exe_running = MagicMock(return_value=False)
|
||||
self.scheduler._launch_exe = AsyncMock(side_effect=[True, True])
|
||||
self.scheduler.push_bilibili_live = AsyncMock(return_value=True)
|
||||
|
||||
async def test_launches_apps_after_one_minute_then_pushes_after_another(self):
|
||||
with patch("app.danmu_queue.asyncio.sleep", new_callable=AsyncMock) as sleep:
|
||||
await self.scheduler._run_startup_live_compensation(self.start_at, self.end_at)
|
||||
|
||||
self.assertEqual(
|
||||
[call.args[0] for call in sleep.await_args_list],
|
||||
[60, 60],
|
||||
)
|
||||
self.assertEqual(
|
||||
[call.args for call in self.scheduler._launch_exe.await_args_list],
|
||||
[("B站直播姬", "bilibili_live_exe"), ("原神", "genshin_exe")],
|
||||
)
|
||||
self.scheduler.push_bilibili_live.assert_awaited_once()
|
||||
|
||||
async def test_skips_push_when_livehime_was_already_running(self):
|
||||
self.scheduler._is_exe_running = MagicMock(return_value=True)
|
||||
self.scheduler._launch_exe = AsyncMock(side_effect=[False, False])
|
||||
|
||||
with patch("app.danmu_queue.asyncio.sleep", new_callable=AsyncMock):
|
||||
await self.scheduler._run_startup_live_compensation(self.start_at, self.end_at)
|
||||
|
||||
self.scheduler.push_bilibili_live.assert_not_awaited()
|
||||
|
||||
async def test_cancels_before_push_when_live_window_ends(self):
|
||||
self.scheduler._startup_window_active = MagicMock(side_effect=[True, False])
|
||||
|
||||
with patch("app.danmu_queue.asyncio.sleep", new_callable=AsyncMock):
|
||||
await self.scheduler._run_startup_live_compensation(self.start_at, self.end_at)
|
||||
|
||||
self.scheduler.push_bilibili_live.assert_not_awaited()
|
||||
|
||||
async def test_scheduling_marks_prepare_and_start_to_avoid_duplicate_clicks(self):
|
||||
with patch.object(self.scheduler, "_run_startup_live_compensation", new_callable=AsyncMock):
|
||||
scheduled = self.scheduler._schedule_startup_live_compensation(self.start_at, self.end_at)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
if self.scheduler._startup_compensation_task:
|
||||
await self.scheduler._startup_compensation_task
|
||||
|
||||
self.assertTrue(scheduled)
|
||||
self.assertFalse(self.scheduler._schedule_startup_live_compensation(self.start_at, self.end_at))
|
||||
self.assertIn(
|
||||
self.scheduler._event_key("prepare_tts", self.start_at - timedelta(minutes=15)),
|
||||
self.scheduler._triggered_events,
|
||||
)
|
||||
self.assertIn(
|
||||
self.scheduler._event_key("prepare", self.start_at - timedelta(minutes=10)),
|
||||
self.scheduler._triggered_events,
|
||||
)
|
||||
self.assertIn(
|
||||
self.scheduler._event_key("start", self.start_at),
|
||||
self.scheduler._triggered_events,
|
||||
)
|
||||
|
||||
def test_current_live_window_supports_cross_midnight_schedule(self):
|
||||
self.scheduler.config.system_cfg["live_start_time"] = "23:00"
|
||||
self.scheduler.config.system_cfg["live_end_time"] = "02:00"
|
||||
current = datetime(2026, 7, 31, 1, 0)
|
||||
|
||||
window = self.scheduler._current_live_window(current)
|
||||
|
||||
self.assertEqual(
|
||||
window,
|
||||
(datetime(2026, 7, 30, 23, 0), datetime(2026, 7, 31, 2, 0)),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user