Files
cultivation-world-simulator/tests/conftest.py
4thfever 7630174820 Feat/relation (#139)
* update relation

* feat: add relation_type to avatar info structure and update related components

- Added `relation_type` to the avatar structured info in `info_presenter.py`.
- Updated `AvatarDetail.vue` to utilize the new `relation_type` for displaying avatar relationships.
- Modified `RelationRow.vue` to accept `type` as a prop for enhanced relationship representation.
- Updated `core.ts` to include `relation_type` in the `RelationInfo` interface.

Closes #
2026-02-05 22:14:44 +08:00

255 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pytest
import random
from unittest.mock import MagicMock, AsyncMock, patch
from src.classes.map import Map
@pytest.fixture(scope="session", autouse=True)
def mock_saves_dir(tmp_path_factory):
"""
Redirect save path to temp dir for all tests to avoid polluting assets/saves/
"""
from src.utils.config import CONFIG
# Create temp dir for saves
temp_saves = tmp_path_factory.mktemp("saves")
# Backup original path
original_path = CONFIG.paths.saves
# Redirect
CONFIG.paths.saves = temp_saves
print(f"\n[Test Setup] Redirecting saves to: {temp_saves}")
yield temp_saves
# Restore
CONFIG.paths.saves = original_path
@pytest.fixture(autouse=True)
def fixed_random_seed():
"""
Ensure all tests have deterministic random behavior.
This fixture is automatically applied to all tests.
"""
random.seed(42)
yield
@pytest.fixture(scope="session", autouse=True)
def force_chinese_language():
"""
Force language to Chinese for all tests to match expected string outputs.
"""
from src.classes.language import language_manager
from src.utils.config import update_paths_for_language
# Force language to Chinese
language_manager.set_language("zh-CN")
# Ensure game configs are reloaded (in case set_language skipped it due to circular import protection)
from src.utils.df import reload_game_configs
reload_game_configs()
yield
from src.classes.tile import TileType, Tile
from src.classes.world import World
from src.classes.calendar import Month, Year, create_month_stamp
from src.classes.avatar import Avatar, Gender
from src.classes.age import Age
from src.classes.cultivation import Realm
from src.utils.id_generator import get_avatar_id
from src.classes.name import get_random_name
from src.classes.root import Root
from src.classes.alignment import Alignment
# Action related imports
from src.classes.elixir import Elixir, ElixirType
from src.classes.material import Material
from src.classes.weapon import Weapon
from src.classes.weapon_type import WeaponType
from src.classes.auxiliary import Auxiliary
from src.classes.region import CityRegion
@pytest.fixture
def base_map():
"""创建一个 10x10 的全平原地图"""
width, height = 10, 10
game_map = Map(width=width, height=height)
for x in range(width):
for y in range(height):
game_map.create_tile(x, y, TileType.PLAIN)
return game_map
@pytest.fixture
def base_world(base_map):
"""创建一个基于 base_map 的世界,时间为 Year 1, Jan"""
return World(map=base_map, month_stamp=create_month_stamp(Year(1), Month.JANUARY))
@pytest.fixture
def dummy_avatar(base_world):
"""创建一个位于 (0,0) 的标准男性练气期角色"""
# 确保ID生成器重置或不冲突 (get_avatar_id 是随机UUID通常没问题)
av = Avatar(
world=base_world,
name="TestDummy",
id=get_avatar_id(),
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
age=Age(20, Realm.Qi_Refinement),
gender=Gender.MALE,
pos_x=0,
pos_y=0,
root=Root.GOLD, # 固定灵根
personas=[], # 清空特质,避免随机效果
alignment=Alignment.RIGHTEOUS # 固定阵营
)
# 赋予一个 Mock 武器,防止 get_avatar_info 报错
av.weapon = MagicMock()
av.weapon.get_detailed_info.return_value = "测试木剑(练气)"
av.weapon_proficiency = 0.0
# 强制清空特质(因为 __post_init__ 会在 personas 为空时自动随机生成)
av.personas = []
# 强制清空功法,防止随机出的功法带有移动步长加成(如逍遥游)
av.technique = None
av.recalc_effects()
return av
@pytest.fixture(autouse=True)
def mock_llm_managers():
"""
Mock 所有涉及 LLM 调用的管理器和函数,防止测试中意外调用 LLM。
"""
# 创建 mock LLM 配置
mock_llm_config = MagicMock()
mock_llm_config.api_key = "test_key"
mock_llm_config.base_url = "http://test.api/v1"
mock_llm_config.model_name = "test-model"
with patch("src.sim.simulator.llm_ai") as mock_ai, \
patch("src.sim.simulator.process_avatar_long_term_objective", new_callable=AsyncMock) as mock_lto, \
patch("src.classes.nickname.process_avatar_nickname", new_callable=AsyncMock) as mock_nick, \
patch("src.classes.relation.relation_resolver.RelationResolver.run_batch", new_callable=AsyncMock) as mock_rr, \
patch("src.classes.history.HistoryManager.apply_history_influence", new_callable=AsyncMock) as mock_hist, \
patch("src.classes.story_teller.StoryTeller.tell_story", new_callable=AsyncMock) as mock_story, \
patch("src.classes.story_teller.StoryTeller.tell_gathering_story", new_callable=AsyncMock) as mock_gathering_story, \
patch("src.utils.llm.config.LLMConfig.from_mode", return_value=mock_llm_config) as mock_config:
mock_ai.decide = AsyncMock(return_value={})
mock_lto.return_value = None
mock_nick.return_value = None
mock_rr.return_value = []
mock_hist.return_value = None
mock_story.return_value = "测试故事"
mock_gathering_story.return_value = "秘境测试故事"
yield {
"ai": mock_ai,
"lto": mock_lto,
"nick": mock_nick,
"rr": mock_rr,
"hist": mock_hist,
"story": mock_story,
"gathering_story": mock_gathering_story,
"config": mock_config
}
# --- Shared Helpers for Item Creation ---
def create_test_elixir(name, realm, price=100, elixir_id=1, effects=None):
if effects is None:
effects = {"max_hp": 10}
return Elixir(
id=elixir_id,
name=name,
realm=realm,
type=ElixirType.Breakthrough,
desc="测试丹药",
price=price,
effects=effects
)
def create_test_material(name, realm, material_id=101):
return Material(
id=material_id,
name=name,
desc="测试物品",
realm=realm
)
def create_test_weapon(name, realm, weapon_id=201):
return Weapon(
id=weapon_id,
name=name,
weapon_type=WeaponType.SWORD,
realm=realm,
desc="测试兵器",
effects={},
effect_desc=""
)
def create_test_auxiliary(name, realm, aux_id=301):
return Auxiliary(
id=aux_id,
name=name,
realm=realm,
desc="测试法宝",
effects={},
effect_desc=""
)
@pytest.fixture
def avatar_in_city(dummy_avatar):
"""
修改 dummy_avatar使其位于城市中并给予初始资金
"""
city_region = CityRegion(id=1, name="TestCity", desc="测试城市")
tile = Tile(0, 0, TileType.CITY)
tile.region = city_region
dummy_avatar.tile = tile
dummy_avatar.magic_stone = 1000
dummy_avatar.cultivation_progress.realm = Realm.Qi_Refinement
dummy_avatar.elixirs = []
dummy_avatar.materials = {} # 确保背包为空
dummy_avatar.weapon = None
dummy_avatar.auxiliary = None
return dummy_avatar
@pytest.fixture
def mock_item_data():
"""
提供标准的一组测试物品,包括材料、丹药、兵器、法宝。
返回一个包含这些对象的字典,方便后续 mock 使用。
"""
test_elixir = create_test_elixir("聚气丹", Realm.Qi_Refinement, price=100)
high_level_elixir = create_test_elixir("筑基丹", Realm.Foundation_Establishment, price=1000, elixir_id=2)
test_material = create_test_material("铁矿石", Realm.Qi_Refinement)
test_weapon = create_test_weapon("青云剑", Realm.Qi_Refinement)
test_auxiliary = create_test_auxiliary("聚灵珠", Realm.Qi_Refinement)
return {
"elixirs": {
"聚气丹": [test_elixir],
"筑基丹": [high_level_elixir]
},
"materials": {
"铁矿石": test_material
},
"weapons": {
"青云剑": test_weapon
},
"auxiliaries": {
"聚灵珠": test_auxiliary
},
# Direct access
"obj_elixir": test_elixir,
"obj_high_elixir": high_level_elixir,
"obj_material": test_material,
"obj_weapon": test_weapon,
"obj_auxiliary": test_auxiliary
}