@@ -1,17 +1,40 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from pathlib import Path
|
||||
from src.classes.history import HistoryManager
|
||||
from src.classes.region import CityRegion, NormalRegion, CultivateRegion
|
||||
from src.classes.region import CityRegion, NormalRegion, CultivateRegion, Region
|
||||
from src.classes.sect_region import SectRegion
|
||||
from src.classes.technique import Technique, TechniqueAttribute, TechniqueGrade
|
||||
from src.classes.weapon import Weapon, WeaponType
|
||||
from src.classes.auxiliary import Auxiliary
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.item_registry import ItemRegistry
|
||||
from src.classes.sect import Sect, SectHeadQuarter
|
||||
from src.classes.alignment import Alignment
|
||||
|
||||
# 假设这些全局字典在模块层级
|
||||
from src.classes import technique as technique_module
|
||||
from src.classes import weapon as weapon_module
|
||||
# auxiliary 模块没有导出全局字典,所以这里不需要特别处理它的全局字典,只需要处理 ItemRegistry
|
||||
from src.classes import sect as sect_module
|
||||
|
||||
def test_world_set_history(base_world):
|
||||
"""测试 world.set_history 方法和 static_info 中的历史显示"""
|
||||
# 初始状态:无历史
|
||||
assert base_world.history == ""
|
||||
static_info = base_world.static_info
|
||||
assert "历史" not in static_info
|
||||
|
||||
# 设置历史
|
||||
history_text = "这是一段测试历史文本:修仙界曾发生大战,许多宗门覆灭。"
|
||||
base_world.set_history(history_text)
|
||||
|
||||
# 验证历史已设置
|
||||
assert base_world.history == history_text
|
||||
|
||||
# 验证 static_info 包含历史
|
||||
static_info = base_world.static_info
|
||||
assert "历史" in static_info
|
||||
assert static_info["历史"] == history_text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_history_influence(base_world):
|
||||
@@ -21,14 +44,28 @@ async def test_history_influence(base_world):
|
||||
city_region = CityRegion(id=1, name="OldCity", desc="Old Desc")
|
||||
normal_region = NormalRegion(id=2, name="OldWild", desc="Old Wild Desc")
|
||||
cult_region = CultivateRegion(id=3, name="OldCave", desc="Old Cave Desc")
|
||||
# 假设 ID 4 是宗门驻地在地图上的区域对象
|
||||
sect_region_obj = SectRegion(id=4, name="OldSectHQ", desc="Old Sect HQ Desc", sect_name="OldSect", sect_id=1)
|
||||
|
||||
base_world.map.regions = {
|
||||
1: city_region,
|
||||
2: normal_region,
|
||||
3: cult_region
|
||||
3: cult_region,
|
||||
4: sect_region_obj
|
||||
}
|
||||
|
||||
# 2. Techniques
|
||||
# 2. Sects
|
||||
sect = Sect(
|
||||
id=1,
|
||||
name="OldSect",
|
||||
desc="Old Sect Desc",
|
||||
member_act_style="Old Style",
|
||||
alignment=Alignment.RIGHTEOUS,
|
||||
headquarter=SectHeadQuarter(name="OldHQ", desc="Old HQ Desc", image=None),
|
||||
technique_names=[]
|
||||
)
|
||||
|
||||
# 3. Techniques
|
||||
tech = Technique(
|
||||
id=101,
|
||||
name="OldTech",
|
||||
@@ -39,7 +76,7 @@ async def test_history_influence(base_world):
|
||||
condition=""
|
||||
)
|
||||
|
||||
# 3. Weapons & Auxiliaries
|
||||
# 4. Weapons & Auxiliaries
|
||||
weapon = Weapon(
|
||||
id=201,
|
||||
name="OldSword",
|
||||
@@ -55,53 +92,66 @@ async def test_history_influence(base_world):
|
||||
)
|
||||
|
||||
# --- Patch Global Registries ---
|
||||
# 使用 patch.dict 来隔离对全局字典的修改
|
||||
with patch.dict(technique_module.techniques_by_id, {101: tech}, clear=True), \
|
||||
patch.dict(technique_module.techniques_by_name, {"OldTech": tech}, clear=True), \
|
||||
patch.dict(weapon_module.weapons_by_name, {"OldSword": weapon}, clear=True), \
|
||||
patch.object(ItemRegistry, "_items_by_id", {201: weapon, 301: aux}): # ItemRegistry 是类属性
|
||||
patch.dict(sect_module.sects_by_id, {1: sect}, clear=True), \
|
||||
patch.dict(sect_module.sects_by_name, {"OldSect": sect}, clear=True), \
|
||||
patch.object(ItemRegistry, "_items_by_id", {201: weapon, 301: aux}):
|
||||
|
||||
# --- Prepare LLM Mock Response ---
|
||||
mock_response = {
|
||||
"city_regions_change": {
|
||||
"1": {"name": "NewCity", "desc": "New Desc"}
|
||||
},
|
||||
"normal_regions_change": {
|
||||
"2": {"name": "NewWild", "desc": "New Wild Desc"}
|
||||
},
|
||||
"cultivate_regions_change": {
|
||||
"3": {"name": "NewCave", "desc": "New Cave Desc"}
|
||||
},
|
||||
"techniques_change": {
|
||||
"101": {"name": "NewTech", "desc": "New Tech Desc"}
|
||||
},
|
||||
"weapons_change": {
|
||||
"201": {"name": "NewSword", "desc": "New Sword Desc"}
|
||||
},
|
||||
"auxiliarys_change": {
|
||||
"301": {"name": "NewOrb", "desc": "New Orb Desc"}
|
||||
}
|
||||
# --- Prepare LLM Mock Responses ---
|
||||
# Map Task Response
|
||||
map_response = {
|
||||
"city_regions_change": {"1": {"name": "NewCity", "desc": "New Desc"}},
|
||||
"normal_regions_change": {"2": {"name": "NewWild", "desc": "New Wild Desc"}},
|
||||
"cultivate_regions_change": {"3": {"name": "NewCave", "desc": "New Cave Desc"}}
|
||||
}
|
||||
|
||||
# Sect Task Response
|
||||
sect_response = {
|
||||
"sects_change": {"1": {"name": "NewSect", "desc": "New Sect Desc"}},
|
||||
"sect_regions_change": {"4": {"name": "NewSectHQ", "desc": "New Sect HQ Desc"}}
|
||||
}
|
||||
|
||||
# Item Task Response
|
||||
item_response = {
|
||||
"techniques_change": {"101": {"name": "NewTech", "desc": "New Tech Desc"}},
|
||||
"weapons_change": {"201": {"name": "NewSword", "desc": "New Sword Desc"}},
|
||||
"auxiliarys_change": {"301": {"name": "NewOrb", "desc": "New Orb Desc"}}
|
||||
}
|
||||
|
||||
def side_effect(**kwargs):
|
||||
task_name = kwargs.get("task_name")
|
||||
if task_name == "history_influence_map":
|
||||
return map_response
|
||||
elif task_name == "history_influence_sect":
|
||||
return sect_response
|
||||
elif task_name == "history_influence_item":
|
||||
return item_response
|
||||
return {}
|
||||
|
||||
# --- Instantiate Manager & Mock Internal Methods ---
|
||||
manager = HistoryManager(base_world)
|
||||
|
||||
# Mock _read_csv to return dummy string
|
||||
manager._read_csv = MagicMock(return_value="dummy,csv,content")
|
||||
|
||||
# Mock call_llm_with_task_name
|
||||
with patch("src.classes.history.call_llm_with_task_name", new_callable=AsyncMock) as mock_llm:
|
||||
mock_llm.return_value = mock_response
|
||||
mock_llm.side_effect = side_effect
|
||||
|
||||
# --- Execute ---
|
||||
await manager.apply_history_influence("Some history text")
|
||||
history_text = "Some history text"
|
||||
await manager.apply_history_influence(history_text)
|
||||
|
||||
# --- Assertions ---
|
||||
|
||||
# 1. LLM Called
|
||||
mock_llm.assert_called_once()
|
||||
# 0. World history 未自动设置(需要外部调用 set_history)
|
||||
# 注意:apply_history_influence 只应用影响,不设置 history 属性
|
||||
# history 属性应该在调用前由外部设置
|
||||
|
||||
# 2. Regions Updated
|
||||
# 1. LLM Called 3 times
|
||||
assert mock_llm.call_count == 3
|
||||
|
||||
# 2. Map Regions Updated
|
||||
assert city_region.name == "NewCity"
|
||||
assert city_region.desc == "New Desc"
|
||||
assert normal_region.name == "NewWild"
|
||||
@@ -109,21 +159,120 @@ async def test_history_influence(base_world):
|
||||
assert cult_region.name == "NewCave"
|
||||
assert cult_region.desc == "New Cave Desc"
|
||||
|
||||
# 3. Technique Updated & Index Synced
|
||||
# 3. Sect & Sect Region Updated
|
||||
assert sect.name == "NewSect"
|
||||
assert sect.desc == "New Sect Desc"
|
||||
assert sect_region_obj.name == "NewSectHQ" # 地图上的对象被更新
|
||||
assert sect_region_obj.desc == "New Sect HQ Desc"
|
||||
|
||||
# 4. Sect Index Synced
|
||||
assert "NewSect" in sect_module.sects_by_name
|
||||
assert "OldSect" not in sect_module.sects_by_name
|
||||
assert sect_module.sects_by_name["NewSect"] == sect
|
||||
|
||||
# 5. Technique Updated & Index Synced
|
||||
assert tech.name == "NewTech"
|
||||
assert tech.desc == "New Tech Desc"
|
||||
assert "NewTech" in technique_module.techniques_by_name
|
||||
assert "OldTech" not in technique_module.techniques_by_name
|
||||
assert technique_module.techniques_by_name["NewTech"] == tech
|
||||
|
||||
# 4. Weapon Updated & Index Synced
|
||||
# 6. Weapon Updated & Index Synced
|
||||
assert weapon.name == "NewSword"
|
||||
assert weapon.desc == "New Sword Desc"
|
||||
assert "NewSword" in weapon_module.weapons_by_name
|
||||
assert "OldSword" not in weapon_module.weapons_by_name
|
||||
assert weapon_module.weapons_by_name["NewSword"] == weapon
|
||||
|
||||
# 5. Auxiliary Updated
|
||||
# 7. Auxiliary Updated
|
||||
assert aux.name == "NewOrb"
|
||||
assert aux.desc == "New Orb Desc"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_history_workflow_integration(base_world):
|
||||
"""测试完整的历史工作流程:设置历史 -> 应用影响"""
|
||||
# 准备测试数据
|
||||
city_region = CityRegion(id=1, name="测试城", desc="旧描述")
|
||||
base_world.map.regions = {1: city_region}
|
||||
|
||||
# 模拟初始化时的完整流程
|
||||
history_text = "这片大陆曾经历过灵气复苏,修仙宗门林立。"
|
||||
|
||||
# 1. 先设置 history(模拟 init_game_async 中的调用)
|
||||
base_world.set_history(history_text)
|
||||
assert base_world.history == history_text
|
||||
|
||||
# 2. 验证 static_info 中包含历史
|
||||
static_info = base_world.static_info
|
||||
assert "历史" in static_info
|
||||
assert static_info["历史"] == history_text
|
||||
|
||||
# 3. 应用历史影响(模拟 HistoryManager.apply_history_influence)
|
||||
manager = HistoryManager(base_world)
|
||||
manager._read_csv = MagicMock(return_value="dummy,csv,content")
|
||||
|
||||
map_response = {
|
||||
"city_regions_change": {"1": {"name": "灵气城", "desc": "充满灵气的城市"}},
|
||||
}
|
||||
|
||||
def side_effect(**kwargs):
|
||||
task_name = kwargs.get("task_name")
|
||||
if task_name == "history_influence_map":
|
||||
return map_response
|
||||
return {}
|
||||
|
||||
with patch("src.classes.history.call_llm_with_task_name", new_callable=AsyncMock) as mock_llm:
|
||||
mock_llm.side_effect = side_effect
|
||||
|
||||
await manager.apply_history_influence(history_text)
|
||||
|
||||
# 4. 验证影响已应用
|
||||
assert city_region.name == "灵气城"
|
||||
assert city_region.desc == "充满灵气的城市"
|
||||
|
||||
# 5. 验证 history 仍然保留
|
||||
assert base_world.history == history_text
|
||||
|
||||
# 6. 验证 static_info 中仍包含历史
|
||||
static_info = base_world.static_info
|
||||
assert "历史" in static_info
|
||||
assert static_info["历史"] == history_text
|
||||
|
||||
def test_history_persistence_in_save_load(base_world, tmp_path):
|
||||
"""测试 history 在保存和加载时的持久化"""
|
||||
from src.sim.save.save_game import save_game
|
||||
from src.sim.load.load_game import load_game
|
||||
from src.sim.simulator import Simulator
|
||||
|
||||
# 设置历史
|
||||
history_text = "修仙界的远古历史:曾有强者飞升,留下诸多传承。"
|
||||
base_world.set_history(history_text)
|
||||
|
||||
# 创建模拟器和宗门列表
|
||||
simulator = Simulator(base_world)
|
||||
existed_sects = []
|
||||
|
||||
# 保存游戏
|
||||
save_path = tmp_path / "test_history_save.json"
|
||||
success, _ = save_game(base_world, simulator, existed_sects, save_path)
|
||||
assert success, "保存游戏应该成功"
|
||||
|
||||
# 验证保存文件中包含历史
|
||||
import json
|
||||
with open(save_path, "r", encoding="utf-8") as f:
|
||||
save_data = json.load(f)
|
||||
|
||||
world_data = save_data.get("world", {})
|
||||
assert "history" in world_data, "保存数据应该包含 history 字段"
|
||||
assert world_data["history"] == history_text, "保存的历史文本应该正确"
|
||||
|
||||
# 加载游戏
|
||||
loaded_world, loaded_sim, loaded_sects = load_game(save_path)
|
||||
|
||||
# 验证历史被正确恢复
|
||||
assert loaded_world.history == history_text, "加载的世界应该包含历史"
|
||||
|
||||
# 验证 static_info 中包含历史
|
||||
static_info = loaded_world.static_info
|
||||
assert "历史" in static_info, "加载后的 static_info 应该包含历史"
|
||||
assert static_info["历史"] == history_text, "加载后的历史文本应该正确"
|
||||
|
||||
@@ -102,6 +102,9 @@ def test_store_init_mixed_ids_and_names(mock_item_data):
|
||||
original_registry = ItemRegistry._items_by_id.copy()
|
||||
ItemRegistry._items_by_id.clear()
|
||||
|
||||
from src.classes.weapon import weapons_by_name
|
||||
original_weapons_by_name = weapons_by_name.copy()
|
||||
|
||||
try:
|
||||
# We need `resolve_query` to work for names, which relies on
|
||||
# weapons_by_name etc. populated in conftest or manually here.
|
||||
@@ -119,7 +122,6 @@ def test_store_init_mixed_ids_and_names(mock_item_data):
|
||||
# We need to mock `src.utils.resolution.resolve_query` or make `resolve_query` find it.
|
||||
# Ideally, `StoreMixin` uses `resolve_query` for strings.
|
||||
|
||||
from src.classes.weapon import weapons_by_name
|
||||
weapons_by_name[weapon.name] = weapon
|
||||
|
||||
shop = MockShop()
|
||||
@@ -133,6 +135,8 @@ def test_store_init_mixed_ids_and_names(mock_item_data):
|
||||
|
||||
finally:
|
||||
ItemRegistry._items_by_id = original_registry
|
||||
weapons_by_name.clear()
|
||||
weapons_by_name.update(original_weapons_by_name)
|
||||
|
||||
# --- Test Item Instantiation ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user