Files
Live-streaming/tests/test_pet_state.py
T

113 lines
5.5 KiB
Python

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()