Files
cultivation-world-simulator/tests/test_csv_loading.py
bridge 4f377551e8 feat(avatar): implement region ownership management in AvatarManager and Avatar classes
- Added `owned_regions` attribute to the Avatar class to track regions owned by avatars.
- Introduced `occupy_region` and `release_region` methods for managing region ownership and ensuring proper relationship handling.
- Updated AvatarManager to clear relationships when an avatar is released, ensuring no lingering references.
- Refactored region ownership logic in the Occupy action and Simulator to utilize the new methods for better clarity and maintainability.
- Enhanced game loading process to establish ownership relationships correctly during game state restoration.
2026-02-02 21:34:02 +08:00

241 lines
8.8 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.
"""
测试 CSV 数据加载的正确性。
验证代码中使用的列名与 CSV 文件中的实际列名匹配。
采用动态多语言测试方案,不再硬编码特定语言的预期字符串。
"""
import pytest
import csv
from pathlib import Path
from src.classes.sect import sects_by_id, sects_by_name, Sect, reload as reload_sects
from src.classes.technique import techniques_by_id, techniques_by_name, Technique, reload as reload_techniques
from src.classes.elixir import elixirs_by_id
from src.utils.config import CONFIG
from src.i18n import t, reload_translations
from src.classes.language import language_manager
# --- Helpers ---
def read_raw_csv_as_dict(file_path):
"""读取原始 CSV 文件,跳过描述行"""
if not file_path.exists():
return []
with open(file_path, 'r', encoding='utf-8-sig') as f:
lines = list(csv.reader(f))
if len(lines) < 1:
return []
headers = lines[0]
data = []
# Start from index 2 if there's a description row
start_index = 2 if len(lines) > 1 else 1
for row_values in lines[start_index:]:
if not row_values: continue
row_dict = {}
for i, h in enumerate(headers):
if i < len(row_values):
row_dict[h] = row_values[i]
else:
row_dict[h] = None
data.append(row_dict)
return data
@pytest.fixture(params=["zh-CN", "zh-TW", "en-US"])
def game_lang(request):
"""
参数化 Fixture切换语言并重载游戏数据。
测试结束后自动恢复回 zh-CN 环境。
"""
lang = request.param
# 1. Switch Language
language_manager.set_language(lang)
reload_translations()
# 2. Force Reload Game Data
from src.utils.config import update_paths_for_language
update_paths_for_language(lang)
from src.utils.df import reload_game_configs
reload_game_configs()
reload_techniques()
reload_sects()
yield lang
# Teardown: Restore to zh-CN for other tests
language_manager.set_language("zh-CN")
reload_translations()
update_paths_for_language("zh-CN")
reload_game_configs()
reload_techniques()
reload_sects()
class TestSectLoading:
"""测试宗门数据加载 (多语言动态验证)"""
def test_sect_headquarter_name_loaded(self, game_lang):
"""测试宗门驻地名称正确加载"""
# Read RAW Sect CSV
sect_csv_path = CONFIG.paths.shared_game_configs / "sect.csv"
raw_sects = read_raw_csv_as_dict(sect_csv_path)
# Read RAW Sect Region CSV (Source of HQ names)
region_csv_path = CONFIG.paths.shared_game_configs / "sect_region.csv"
raw_regions = read_raw_csv_as_dict(region_csv_path)
sect_region_map = {int(r['sect_id']): r for r in raw_regions if r.get('sect_id')}
# Verify specific Sect (ID=12, 不夜城)
target_id = 12
sect = sects_by_id.get(target_id)
assert sect is not None
# 1. Verify Sect Name
sect_row = next((r for r in raw_sects if int(r['id']) == target_id), None)
assert sect_row
expected_sect_name = sect_row.get('name')
if sect_row.get('name_id'):
trans = t(sect_row['name_id'])
if trans and trans != sect_row['name_id']:
expected_sect_name = trans
assert sect.name == expected_sect_name, f"Sect name mismatch in {game_lang}"
# 2. Verify HQ Name
region_row = sect_region_map.get(target_id)
assert region_row
expected_hq_name = region_row.get('name')
if region_row.get('name_id'):
trans = t(region_row['name_id'])
if trans and trans != region_row['name_id']:
expected_hq_name = trans
assert sect.headquarter.name == expected_hq_name, f"HQ name mismatch in {game_lang}"
def test_sect_headquarter_desc_loaded(self, game_lang):
"""测试宗门驻地描述正确加载"""
target_id = 12
sect = sects_by_id.get(target_id)
assert sect is not None
# Read RAW Sect Region CSV
region_csv_path = CONFIG.paths.shared_game_configs / "sect_region.csv"
raw_regions = read_raw_csv_as_dict(region_csv_path)
region_row = next((r for r in raw_regions if int(r.get('sect_id', -1)) == target_id), None)
assert region_row
expected_desc = region_row.get('desc')
if region_row.get('desc_id'):
trans = t(region_row['desc_id'])
if trans and trans != region_row['desc_id']:
expected_desc = trans
# Normalize newlines/spaces for comparison if needed
assert sect.headquarter.desc == expected_desc, f"HQ desc mismatch in {game_lang}"
def test_all_sects_have_headquarters(self, game_lang):
"""测试所有宗门都有驻地信息"""
for sect_id, sect in sects_by_id.items():
assert sect.headquarter is not None
assert sect.headquarter.name, f"宗门 {sect.name} 的驻地名称不应为空"
def test_sect_techniques_loaded(self, game_lang):
"""测试宗门功法列表正确加载"""
sect = sects_by_id.get(1) # 明心剑宗
assert sect is not None
assert len(sect.technique_names) > 0
def test_sect_without_techniques(self, game_lang):
"""测试没有配置功法的宗门"""
sect = sects_by_id.get(12) # 不夜城
assert sect is not None
assert sect.technique_names == []
class TestTechniqueLoading:
"""测试功法数据加载"""
def test_technique_sect_id_loaded(self, game_lang):
"""测试功法的 sect_id 正确加载"""
tech_id = 30 # 草字剑诀
technique = techniques_by_id.get(tech_id)
assert technique is not None
# Verify Name using Dynamic Logic
tech_csv_path = CONFIG.paths.shared_game_configs / "technique.csv"
raw_techs = read_raw_csv_as_dict(tech_csv_path)
row = next((r for r in raw_techs if int(r['id']) == tech_id), None)
expected_name = row.get('name')
if row.get('name_id'):
trans = t(row['name_id'])
if trans and trans != row['name_id']:
expected_name = trans
assert technique.name == expected_name, f"Technique name mismatch in {game_lang}"
assert technique.sect_id == 1
def test_technique_without_sect(self, game_lang):
"""测试散修功法"""
technique = techniques_by_id.get(1)
assert technique is not None
assert technique.sect_id is None
def test_sect_techniques_match(self, game_lang):
"""测试宗门功法和功法的宗门ID相互匹配"""
for sect_id, sect in sects_by_id.items():
for tech_name in sect.technique_names:
technique = techniques_by_name.get(tech_name)
# 注意technique_names 是 string list如果 names 不匹配(翻译问题)这里会取不到
# 但我们的系统设计是sect.technique_names 是直接从 technique.csv 加载的
# 所以只要 reload 顺序正确(先 technique 后 sect名字应该是一致的
assert technique is not None, f"功法 '{tech_name}' 应该存在 (Lang: {game_lang})"
assert technique.sect_id == sect_id
class TestElixirLoading:
"""丹药加载测试 (ID check, less dependent on lang but good to verify integrity)"""
def test_elixir_loaded_with_item_id(self):
# 丹药目前没有专门的 reload 和 translation key 绑定逻辑验证需求
# 保持原样即可,不需要 parametrizing unless needed
assert len(elixirs_by_id) > 0
for elixir_id, elixir in elixirs_by_id.items():
assert elixir_id > 0
assert elixir.id == elixir_id
class TestGameDataAPI:
"""测试 API (API 测试通常在固定环境下运行,这里不使用多语言参数化以免影响 Server 状态)"""
@pytest.fixture
def client(self):
from fastapi.testclient import TestClient
from src.server.main import app
return TestClient(app)
def test_game_data_techniques_have_sect_id(self, client):
response = client.get("/api/meta/game_data")
assert response.status_code == 200
data = response.json()
assert len(data["techniques"]) > 0
assert "sect_id" in data["techniques"][0]
def test_game_data_sects_structure(self, client):
response = client.get("/api/meta/game_data")
assert response.status_code == 200
data = response.json()
assert len(data["sects"]) > 0
assert "id" in data["sects"][0]
if __name__ == "__main__":
pytest.main([__file__, "-v"])