126 lines
4.8 KiB
Python
126 lines
4.8 KiB
Python
import asyncio
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock, AsyncMock
|
|
|
|
from src.sim.simulator import Simulator
|
|
from src.classes.action.move_to_direction import MoveToDirection
|
|
from src.classes.tile import TileType
|
|
from src.classes.action_runtime import ActionInstance
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_simulator_step_moves_avatar_and_sets_tile(base_world, dummy_avatar, mock_llm_managers):
|
|
# Set initial position
|
|
dummy_avatar.pos_x = 1
|
|
dummy_avatar.pos_y = 1
|
|
# Ensure tile is updated to initial position (fixture puts it at 0,0)
|
|
dummy_avatar.tile = base_world.map.get_tile(1, 1)
|
|
|
|
sim = Simulator(base_world)
|
|
base_world.avatar_manager.avatars[dummy_avatar.id] = dummy_avatar
|
|
|
|
# Manually assign a MoveToDirection action to avoid relying on LLM
|
|
action = MoveToDirection(dummy_avatar, base_world)
|
|
# "East" means x + 1
|
|
direction = "East"
|
|
action.start(direction=direction) # Initialize start_monthstamp etc.
|
|
|
|
# Wrap in ActionInstance
|
|
dummy_avatar.current_action = ActionInstance(action=action, params={"direction": direction})
|
|
|
|
# Mock LLM to avoid external calls or errors (Handled by mock_llm_managers fixture)
|
|
|
|
print(f"DEBUG: Before step: pos_x={dummy_avatar.pos_x}")
|
|
# Run step
|
|
await sim.step()
|
|
print(f"DEBUG: After step: pos_x={dummy_avatar.pos_x}")
|
|
print(f"DEBUG: move_step_length={getattr(dummy_avatar, 'move_step_length', 'Not set')}")
|
|
print(f"DEBUG: effects={dummy_avatar.effects}")
|
|
|
|
# Assert moved East (x increased by move_step_length)
|
|
# Current move step for Qi Refinement is 2
|
|
assert dummy_avatar.pos_x == 3
|
|
assert dummy_avatar.pos_y == 1
|
|
|
|
# Assert tile is updated
|
|
assert dummy_avatar.tile is not None
|
|
assert dummy_avatar.tile.x == 3
|
|
assert dummy_avatar.tile.y == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_simulator_interaction_counting(base_world, dummy_avatar, mock_llm_managers):
|
|
"""测试交互计数在 step 中被正确处理且去重"""
|
|
from src.classes.event import Event
|
|
from src.classes.avatar import Avatar, Gender
|
|
from src.classes.age import Age
|
|
from src.classes.cultivation import Realm
|
|
from src.classes.calendar import create_month_stamp, Year, Month
|
|
from src.classes.root import Root
|
|
from src.classes.alignment import Alignment
|
|
|
|
sim = Simulator(base_world)
|
|
|
|
# 创建另一个角色用于交互
|
|
other_avatar = Avatar(
|
|
world=base_world,
|
|
name="OtherAvatar",
|
|
id="other_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,
|
|
root=Root.WOOD,
|
|
alignment=Alignment.NEUTRAL
|
|
)
|
|
|
|
base_world.avatar_manager.register_avatar(dummy_avatar)
|
|
base_world.avatar_manager.register_avatar(other_avatar)
|
|
|
|
# 1. 模拟第一阶段(执行动作)产生的事件
|
|
ev1 = Event(base_world.month_stamp, "阶段1交互", related_avatars=[dummy_avatar.id, other_avatar.id])
|
|
|
|
# 2. 模拟第二阶段(被动效果/奇遇)产生的事件
|
|
ev2 = Event(base_world.month_stamp, "阶段2交互", related_avatars=[dummy_avatar.id, other_avatar.id])
|
|
|
|
with patch.object(sim, '_phase_execute_actions', new_callable=AsyncMock) as mock_exec, \
|
|
patch.object(sim, '_phase_passive_effects', new_callable=AsyncMock) as mock_passive:
|
|
|
|
mock_exec.return_value = [ev1]
|
|
mock_passive.return_value = [ev2]
|
|
|
|
await sim.step()
|
|
|
|
# 验证交互计数是否增加了 2 (两个独立事件)
|
|
count = dummy_avatar.relation_interaction_states[other_avatar.id]["count"]
|
|
assert count == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_simulator_event_deduplication(base_world, dummy_avatar, mock_llm_managers):
|
|
"""测试事件去重逻辑:同一个事件对象在多个阶段被返回时,入库应只有一次"""
|
|
from src.classes.event import Event
|
|
sim = Simulator(base_world)
|
|
base_world.avatar_manager.register_avatar(dummy_avatar)
|
|
|
|
ev = Event(base_world.month_stamp, "重复事件")
|
|
ev_id = ev.id
|
|
|
|
# 模拟两个不同的阶段返回了同一个 ID 的事件对象
|
|
with patch.object(sim, '_phase_execute_actions', new_callable=AsyncMock) as mock_exec, \
|
|
patch.object(sim, '_phase_passive_effects', new_callable=AsyncMock) as mock_passive:
|
|
|
|
mock_exec.return_value = [ev]
|
|
mock_passive.return_value = [ev]
|
|
|
|
# 拦截 event_manager.add_event
|
|
base_world.event_manager.add_event = MagicMock()
|
|
|
|
await sim.step()
|
|
|
|
# 验证该 ID 的事件只被添加了一次
|
|
# 我们过滤出 ID 匹配的调用
|
|
target_calls = [
|
|
call for call in base_world.event_manager.add_event.call_args_list
|
|
if call.args[0].id == ev_id
|
|
]
|
|
assert len(target_calls) == 1
|