合并外部维护版本并同步自动每日功能
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "app"))
|
||||
from pet_state import PetState, gift_recovery, split_emotion
|
||||
from app.danmu_queue import get_room_live_status, monitor_pet_live_status
|
||||
|
||||
|
||||
def timestamp(iso: str) -> float:
|
||||
return datetime.fromisoformat(iso).timestamp()
|
||||
|
||||
|
||||
class PetStateTests(unittest.TestCase):
|
||||
def test_only_active_sessions_count_and_partial_hours_carry_over(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
pet = PetState(Path(directory) / "pet.json")
|
||||
start = timestamp("2026-09-25T22:00:00")
|
||||
pet.updated_at = start
|
||||
self.assertEqual(pet.sync(start + 3600)["stamina"], 100)
|
||||
pet.set_live_active(True, start + 3600)
|
||||
pet.set_live_active(False, start + 5400)
|
||||
self.assertEqual(pet.sync(start + 9000)["stamina"], 100)
|
||||
pet.set_live_active(True, start + 9000)
|
||||
self.assertEqual(pet.sync(start + 10800)["stamina"], 90)
|
||||
pet.set_live_active(False, start + 10800)
|
||||
self.assertEqual(pet.sync(start + 86400)["stamina"], 90)
|
||||
|
||||
def test_hourly_decay_mood_and_persistence(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "pet.json"
|
||||
pet = PetState(path)
|
||||
pet.updated_at = timestamp("2026-09-25T09:00:00")
|
||||
pet.set_live_active(True, timestamp("2026-09-25T09:00:00"))
|
||||
state = pet.sync(timestamp("2026-09-25T16:00:00"))
|
||||
self.assertEqual(state, {"stamina": 30, "cleanliness": 30, "mood": 95})
|
||||
self.assertEqual(json.loads(path.read_text(encoding="utf-8"))["stamina"], 30)
|
||||
loaded = PetState(path)
|
||||
self.assertEqual(loaded.values, state)
|
||||
self.assertFalse(loaded.live_active)
|
||||
self.assertEqual(loaded.sync(timestamp("2026-09-25T17:00:00")), state)
|
||||
|
||||
def test_emotion_and_gift_clamp(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
pet = PetState(Path(directory) / "pet.json")
|
||||
pet.values = {"stamina": 95, "cleanliness": 99, "mood": 1}
|
||||
self.assertEqual(pet.apply_emotion("很生气")["mood"], 0)
|
||||
self.assertEqual(pet.restore_gift(20, 20),
|
||||
{"stamina": 100, "cleanliness": 100, "mood": 0})
|
||||
self.assertEqual(split_emotion("[很开心] 今天真好"), ("今天真好", "很开心"))
|
||||
self.assertEqual(split_emotion("[未知] 今天真好"), ("[未知] 今天真好", None))
|
||||
|
||||
def test_mood_reaction_events_cover_four_deltas_and_hourly_gain(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
pet = PetState(Path(directory) / "pet.json")
|
||||
pet.values["mood"] = 50
|
||||
for emotion in ("很开心", "开心", "生气", "很生气", "平静"):
|
||||
pet.apply_emotion(emotion)
|
||||
self.assertEqual([event["delta"] for event in pet.recent_emotions],
|
||||
[3, 1, -1, -3])
|
||||
self.assertEqual(len({event["id"] for event in pet.recent_emotions}), 4)
|
||||
start = timestamp("2026-09-25T09:00:00")
|
||||
pet.values.update(stamina=100, cleanliness=100)
|
||||
pet.updated_at = start
|
||||
pet.live_active = True
|
||||
pet.sync(start + 3600)
|
||||
self.assertEqual(pet.recent_emotions[-1]["delta"], 3)
|
||||
|
||||
def test_named_gifts_and_full_stat_keep_nominal_animation_points(self):
|
||||
self.assertEqual(gift_recovery("饭团"), (5, 0))
|
||||
self.assertEqual(gift_recovery("薯条", 2), (10, 0))
|
||||
self.assertEqual(gift_recovery("人气票"), (0, 5))
|
||||
self.assertEqual(gift_recovery("粉丝团灯牌", 2), (0, 10))
|
||||
self.assertEqual(gift_recovery("灯牌"), (0, 5))
|
||||
self.assertEqual(gift_recovery("其他礼物"), (0, 0))
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
pet = PetState(Path(directory) / "pet.json")
|
||||
nominal_stamina, nominal_cleanliness = gift_recovery("饭团")
|
||||
self.assertEqual((nominal_stamina, nominal_cleanliness), (5, 0))
|
||||
self.assertEqual(pet.restore_gift(nominal_stamina, nominal_cleanliness)["stamina"], 100)
|
||||
|
||||
def test_room_state_only_counts_confirmed_live(self):
|
||||
with patch("app.danmu_queue._request_bilibili_json") as request:
|
||||
for status, expected in ((0, False), (1, True), (2, False)):
|
||||
request.return_value = {"code": 0, "data": {"live_status": status}}
|
||||
self.assertIs(get_room_live_status(76), expected)
|
||||
request.return_value = {"code": -1, "data": {"live_status": 1}}
|
||||
with self.assertRaises(RuntimeError):
|
||||
get_room_live_status(76)
|
||||
|
||||
def test_room_monitor_switches_pet_session_on_confirmed_state(self):
|
||||
pet = Mock()
|
||||
system = SimpleNamespace(pet_state=pet)
|
||||
config = SimpleNamespace(room_id=76, bilibili_cookie="")
|
||||
with patch("app.danmu_queue.asyncio.to_thread", new=AsyncMock(side_effect=[True, False])), \
|
||||
patch("app.danmu_queue.asyncio.sleep", new=AsyncMock(
|
||||
side_effect=[None, asyncio.CancelledError()])):
|
||||
with self.assertRaises(asyncio.CancelledError):
|
||||
asyncio.run(monitor_pet_live_status(system, config, Mock()))
|
||||
self.assertEqual([call.args[0] for call in pet.set_live_active.call_args_list],
|
||||
[True, False, False])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user