From 6747ca39df1ed4600bc1b64d2391c1af59049883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E7=93=9C=C2=B0?= Date: Mon, 8 Jun 2026 14:56:17 +0800 Subject: [PATCH] 1 --- backend/tests/smoke/test_calc_availability.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 backend/tests/smoke/test_calc_availability.py diff --git a/backend/tests/smoke/test_calc_availability.py b/backend/tests/smoke/test_calc_availability.py new file mode 100644 index 00000000..3d4b124d --- /dev/null +++ b/backend/tests/smoke/test_calc_availability.py @@ -0,0 +1,109 @@ +from pathlib import Path + +import pytest + +from core.basic.character import createCharacter +from core.basic.equipment import get_equipment + + +CHARACTER_ROOT = Path(__file__).resolve().parents[2] / "core" / "character" / "GF" +DEFAULT_ALTER = "GF.swordman_male.weapon_master" +RESULT_KEYS = {"uuid", "skills", "info", "suits", "buffer"} +EQUIP_PARTS = { + "武器", + "副武器", + "称号", + "上衣", + "头肩", + "下装", + "腰带", + "鞋", + "手镯", + "项链", + "戒指", + "辅助装备", + "魔法石", + "耳环", +} +KNOWN_BROKEN_ALTERS = { + "GF.extra.creator": "基础属性表缺少 外传1", + "GF.extra.dark_knight": "基础属性表缺少 外传2", + "GF.fighter_female.brawler_female": "输出类型未匹配到伤害系数", + "GF.imperialknight.breaker": "基础属性表缺少 帝国骑士", + "GF.knight.elven_knight": "职业模块导入时报 data 未初始化", + "GF.priest_female.infighter_female": "基础属性表缺少 蓝拳圣使-圣职者(女)", +} + + +def _discover_alters() -> list[str]: + alters = [] + for path in sorted(CHARACTER_ROOT.rglob("*.py")): + if path.name == "__init__.py": + continue + text = path.read_text(encoding="utf-8") + if "classChange" not in text: + continue + relative = path.relative_to(CHARACTER_ROOT).with_suffix("") + alters.append("GF." + ".".join(relative.parts)) + return alters + + +def _character_params(): + params = [] + for alter in _discover_alters(): + marks = [] + if alter in KNOWN_BROKEN_ALTERS: + marks.append(pytest.mark.xfail(reason=KNOWN_BROKEN_ALTERS[alter], strict=True)) + params.append(pytest.param(alter, id=alter, marks=marks)) + return params + + +def _equipment_part(equ) -> str | None: + if equ.itemType == "武器": + return "武器" + if equ.itemDetailType in EQUIP_PARTS: + return equ.itemDetailType + return None + + +def _equipment_params(): + params = [] + for equ in get_equipment("0").equs: + part = _equipment_part(equ) + if part is None: + continue + params.append(pytest.param(equ, part, id=f"{part}:{equ.id}:{equ.name}")) + return params + + +def _assert_calc_result_shape(result: dict): + assert isinstance(result, dict) + assert RESULT_KEYS.issubset(result) + assert isinstance(result["skills"], list) + assert isinstance(result["info"], list) + + +@pytest.mark.parametrize("alter", _character_params()) +def test_all_gf_characters_can_calculate_with_minimal_config(alter, minimal_calc_config): + character = createCharacter(alter, "0") + + assert character is not None, f"createCharacter returned None for alter={alter}" + _assert_calc_result_shape(character.calc(minimal_calc_config)) + + +@pytest.mark.parametrize(("equ", "part"), _equipment_params()) +def test_all_equipment_can_calculate_when_equipped_once(equ, part, minimal_calc_config): + config = minimal_calc_config.copy() + config["equips"] = { + part: value.copy() + for part, value in minimal_calc_config["equips"].items() + } + config["equips"][part] = { + **config["equips"][part], + "id": equ.id, + } + + character = createCharacter(DEFAULT_ALTER, "0") + + assert character is not None + _assert_calc_result_shape(character.calc(config))