refactor gift
This commit is contained in:
198
tests/test_action_gift.py
Normal file
198
tests/test_action_gift.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import pytest
|
||||
import asyncio
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.classes.mutual_action.gift import Gift
|
||||
from src.classes.action_runtime import ActionResult, ActionStatus
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.age import Age
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.relation import Relation
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.classes.calendar import create_month_stamp, Year, Month
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def target_avatar(base_world):
|
||||
"""创建第二个角色作为赠送目标"""
|
||||
return Avatar(
|
||||
world=base_world,
|
||||
name="TargetNPC",
|
||||
id=get_avatar_id(),
|
||||
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
|
||||
age=Age(20, Realm.Qi_Refinement),
|
||||
gender=Gender.FEMALE,
|
||||
pos_x=0,
|
||||
pos_y=0
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def gift_action(dummy_avatar, base_world):
|
||||
"""初始化 Gift 动作"""
|
||||
# 模拟 _call_llm_feedback,避免 step 中调用 asyncio.get_running_loop()
|
||||
with patch.object(Gift, '_call_llm_feedback') as mock_llm:
|
||||
# 返回一个 mock task,确保 task.done() 初始为 False,
|
||||
# 但在这里我们主要是为了让 step 不报错
|
||||
mock_llm.return_value = {}
|
||||
|
||||
action = Gift(dummy_avatar, base_world)
|
||||
|
||||
# 我们直接 Mock 掉 step 里的异步创建任务逻辑,
|
||||
# 因为我们主要测试的是:
|
||||
# 1. 参数是否正确解析 (step -> _resolve_gift)
|
||||
# 2. _can_start 逻辑
|
||||
# 3. 反馈结算逻辑 _settle_feedback / _apply_gift
|
||||
|
||||
# 但 step 本身有逻辑:
|
||||
# 1. 解析参数
|
||||
# 2. 检查 target
|
||||
# 3. 创建 task (这里会报错)
|
||||
|
||||
# 我们采用 patch asyncio.get_running_loop 的方式更简单
|
||||
yield action
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tests
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
class TestGiftAction:
|
||||
|
||||
# --- 1. 赠送灵石 ---
|
||||
|
||||
def test_gift_spirit_stone_success(self, gift_action, dummy_avatar, target_avatar):
|
||||
"""测试赠送灵石成功"""
|
||||
dummy_avatar.magic_stone = 1000
|
||||
target_avatar.magic_stone = 0
|
||||
|
||||
# Mock asyncio loop just to pass the step() check
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name="灵石", amount=100)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is True, f"Should be able to start: {reason}"
|
||||
|
||||
# 模拟接受
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert dummy_avatar.magic_stone == 900
|
||||
assert target_avatar.magic_stone == 100
|
||||
assert gift_action._gift_success is True
|
||||
|
||||
def test_gift_spirit_stone_insufficient(self, gift_action, dummy_avatar, target_avatar):
|
||||
"""测试灵石不足"""
|
||||
dummy_avatar.magic_stone = 50
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name="灵石", amount=100)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
|
||||
assert can_start is False
|
||||
assert "灵石不足" in reason
|
||||
|
||||
# --- 2. 赠送素材 ---
|
||||
|
||||
def test_gift_material_success(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送素材成功"""
|
||||
test_material = mock_item_data["obj_material"]
|
||||
dummy_avatar.add_material(test_material, quantity=5)
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
# 非灵石强制数量 1
|
||||
gift_action.step(target_avatar, item_name=test_material.name, amount=999)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is True
|
||||
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert dummy_avatar.get_material_quantity(test_material) == 4
|
||||
assert target_avatar.get_material_quantity(test_material) == 1
|
||||
assert gift_action._current_gift_context["amount"] == 1
|
||||
|
||||
def test_gift_material_not_owned(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送未持有的素材"""
|
||||
test_material = mock_item_data["obj_material"]
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_material.name, amount=1)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is False
|
||||
|
||||
# --- 3. 赠送装备 ---
|
||||
|
||||
def test_gift_weapon_success_and_auto_equip(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送装备成功,且目标自动装备"""
|
||||
test_weapon = mock_item_data["obj_weapon"]
|
||||
dummy_avatar.weapon = test_weapon
|
||||
assert target_avatar.weapon is None
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_weapon.name, amount=1)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is True
|
||||
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert dummy_avatar.weapon is None
|
||||
assert target_avatar.weapon == test_weapon
|
||||
|
||||
def test_gift_weapon_fail_not_equipped(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送未装备的装备"""
|
||||
test_weapon = mock_item_data["obj_weapon"]
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_weapon.name, amount=1)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is False
|
||||
|
||||
def test_gift_weapon_target_trade_in(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试目标已有装备时,收到新装备会自动折价卖出旧的"""
|
||||
from tests.conftest import create_test_weapon
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
new_weapon = mock_item_data["obj_weapon"]
|
||||
dummy_avatar.weapon = new_weapon
|
||||
|
||||
old_weapon = create_test_weapon("旧铁剑", Realm.Qi_Refinement, weapon_id=999)
|
||||
old_weapon.price = 100
|
||||
target_avatar.weapon = old_weapon
|
||||
target_avatar.magic_stone = 0
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=new_weapon.name, amount=1)
|
||||
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert target_avatar.weapon == new_weapon
|
||||
# 50% refund
|
||||
assert target_avatar.magic_stone == 50
|
||||
|
||||
# --- 4. 上下文与描述 ---
|
||||
|
||||
def test_prompt_info_description(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""验证传给 LLM 的 prompt 中包含具体物品描述"""
|
||||
test_weapon = mock_item_data["obj_weapon"]
|
||||
dummy_avatar.weapon = test_weapon
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_weapon.name)
|
||||
|
||||
infos = gift_action._build_prompt_infos(target_avatar)
|
||||
|
||||
assert f"[{test_weapon.name}]" in infos["action_info"]
|
||||
assert "赠送" in infos["action_info"]
|
||||
|
||||
def test_prompt_info_description_stones(self, gift_action, dummy_avatar, target_avatar):
|
||||
dummy_avatar.magic_stone = 1000
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name="灵石", amount=500)
|
||||
|
||||
infos = gift_action._build_prompt_infos(target_avatar)
|
||||
assert "500 灵石" in infos["action_info"]
|
||||
Reference in New Issue
Block a user