* fix: use str() instead of .value for realm i18n display Fix bug where user-facing messages displayed raw enum values like "FOUNDATION_ESTABLISHMENT" instead of translated names like "筑基". The Realm and Stage classes already have __str__ methods that return i18n translated text, but several places were incorrectly using .value which returns the raw enum string. Changed files: - src/classes/single_choice.py: item exchange messages - src/classes/kill_and_grab.py: loot messages - src/classes/fortune.py: fortune discovery messages - src/classes/avatar/inventory_mixin.py: purchase error messages Also added unit tests and integration tests to prevent regression. * test: add integration tests for all modified files Add tests covering: - kill_and_grab.py: context string realm display - fortune.py: weapon/auxiliary intro realm display - inventory_mixin.py: can_buy_item error message realm display
274 lines
8.6 KiB
Python
274 lines
8.6 KiB
Python
"""
|
|
Integration tests for realm/stage i18n display in item exchange messages.
|
|
|
|
Verifies that user-facing messages show translated realm names (e.g., "筑基")
|
|
instead of raw enum values (e.g., "FOUNDATION_ESTABLISHMENT").
|
|
|
|
Coverage:
|
|
- src/classes/single_choice.py (handle_item_exchange)
|
|
- src/classes/kill_and_grab.py (kill_and_grab context string)
|
|
- src/classes/fortune.py (fortune intro strings)
|
|
- src/classes/avatar/inventory_mixin.py (can_buy_item error message)
|
|
"""
|
|
import pytest
|
|
from unittest.mock import Mock, patch, AsyncMock
|
|
|
|
from src.classes.cultivation import Realm, Stage, CultivationProgress
|
|
from src.classes.weapon import weapons_by_id, Weapon
|
|
from src.classes.auxiliary import auxiliaries_by_id
|
|
from src.classes.elixir import elixirs_by_id
|
|
from src.classes.single_choice import handle_item_exchange
|
|
from src.classes.kill_and_grab import kill_and_grab
|
|
|
|
|
|
# Raw enum values that should NOT appear in user-facing messages.
|
|
RAW_REALM_VALUES = [
|
|
"QI_REFINEMENT",
|
|
"FOUNDATION_ESTABLISHMENT",
|
|
"CORE_FORMATION",
|
|
"NASCENT_SOUL",
|
|
]
|
|
|
|
RAW_STAGE_VALUES = [
|
|
"EARLY_STAGE",
|
|
"MIDDLE_STAGE",
|
|
"LATE_STAGE",
|
|
]
|
|
|
|
|
|
class MockAvatarForIntegration:
|
|
"""A minimal mock avatar for integration testing."""
|
|
def __init__(self):
|
|
self.name = "TestCultivator"
|
|
self.weapon = None
|
|
self.auxiliary = None
|
|
self.technique = None
|
|
self.world = Mock()
|
|
self.world.static_info = {}
|
|
self.change_weapon = Mock()
|
|
self.sell_weapon = Mock(return_value=100)
|
|
|
|
def get_info(self, detailed=False):
|
|
return {"name": self.name}
|
|
|
|
|
|
def get_real_weapon() -> Weapon:
|
|
"""Get a real weapon from the game data for testing."""
|
|
# Get the first available weapon.
|
|
if weapons_by_id:
|
|
return next(iter(weapons_by_id.values()))
|
|
pytest.skip("No weapons available in game data")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_handle_item_exchange_shows_translated_realm():
|
|
"""
|
|
Integration test: handle_item_exchange should return messages
|
|
with translated realm names, not raw enum values.
|
|
"""
|
|
weapon = get_real_weapon()
|
|
avatar = MockAvatarForIntegration()
|
|
|
|
# Auto-equip (no existing weapon).
|
|
swapped, msg = await handle_item_exchange(
|
|
avatar, weapon, "weapon", "Testing context", can_sell_new=False
|
|
)
|
|
|
|
assert swapped is True
|
|
|
|
# Message should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES:
|
|
assert raw_value not in msg, (
|
|
f"Message contains raw enum value '{raw_value}': {msg}"
|
|
)
|
|
|
|
# Message should contain the weapon name.
|
|
assert weapon.name in msg
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_weapon_detailed_info_shows_translated_realm():
|
|
"""
|
|
Integration test: Weapon.get_detailed_info() should return
|
|
translated realm names, not raw enum values.
|
|
"""
|
|
weapon = get_real_weapon()
|
|
info = weapon.get_detailed_info()
|
|
|
|
# Info should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES:
|
|
assert raw_value not in info, (
|
|
f"Detailed info contains raw enum value '{raw_value}': {info}"
|
|
)
|
|
|
|
|
|
def test_realm_str_integration_with_real_data():
|
|
"""
|
|
Integration test: Verify all realms in actual weapon data
|
|
have proper translated string representations.
|
|
"""
|
|
realms_found = set()
|
|
|
|
for weapon in weapons_by_id.values():
|
|
realm_str = str(weapon.realm)
|
|
realms_found.add(weapon.realm)
|
|
|
|
# Should not be raw enum value.
|
|
assert realm_str not in RAW_REALM_VALUES, (
|
|
f"Weapon '{weapon.name}' has raw realm value: {realm_str}"
|
|
)
|
|
|
|
# Should not be empty.
|
|
assert len(realm_str) > 0, (
|
|
f"Weapon '{weapon.name}' has empty realm string"
|
|
)
|
|
|
|
# Ensure we tested at least some weapons.
|
|
assert len(realms_found) > 0, "No weapons found in game data"
|
|
|
|
|
|
def test_cultivation_progress_str_shows_translated_realm():
|
|
"""
|
|
Integration test: CultivationProgress.__str__() should use
|
|
translated realm and stage names.
|
|
"""
|
|
cp = CultivationProgress(level=35, exp=0) # Foundation Establishment.
|
|
cp_str = str(cp)
|
|
|
|
# Should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES + RAW_STAGE_VALUES:
|
|
assert raw_value not in cp_str, (
|
|
f"CultivationProgress string contains raw value '{raw_value}': {cp_str}"
|
|
)
|
|
|
|
|
|
# ==================== kill_and_grab.py coverage ====================
|
|
|
|
class MockAvatarForKillAndGrab:
|
|
"""Mock avatar for kill_and_grab testing."""
|
|
def __init__(self, name: str, weapon=None, auxiliary=None):
|
|
self.name = name
|
|
self.weapon = weapon
|
|
self.auxiliary = auxiliary
|
|
self.technique = None
|
|
self.world = Mock()
|
|
self.world.static_info = {}
|
|
self.change_weapon = Mock()
|
|
self.change_auxiliary = Mock()
|
|
self.sell_weapon = Mock(return_value=100)
|
|
self.sell_auxiliary = Mock(return_value=100)
|
|
|
|
def get_info(self, detailed=False):
|
|
return {"name": self.name}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_kill_and_grab_context_shows_translated_realm():
|
|
"""
|
|
Integration test: kill_and_grab should generate context strings
|
|
with translated realm names, not raw enum values.
|
|
"""
|
|
weapon = get_real_weapon()
|
|
|
|
winner = MockAvatarForKillAndGrab("Winner")
|
|
loser = MockAvatarForKillAndGrab("Loser", weapon=weapon)
|
|
|
|
# Patch handle_item_exchange to capture the context_intro argument.
|
|
with patch(
|
|
"src.classes.kill_and_grab.handle_item_exchange",
|
|
new_callable=AsyncMock
|
|
) as mock_exchange:
|
|
mock_exchange.return_value = (True, "equipped")
|
|
|
|
await kill_and_grab(winner, loser)
|
|
|
|
# Verify handle_item_exchange was called.
|
|
assert mock_exchange.called
|
|
|
|
# Get the context_intro argument.
|
|
call_kwargs = mock_exchange.call_args
|
|
context_intro = call_kwargs.kwargs.get("context_intro") or call_kwargs.args[3]
|
|
|
|
# Context should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES:
|
|
assert raw_value not in context_intro, (
|
|
f"kill_and_grab context contains raw enum value '{raw_value}': {context_intro}"
|
|
)
|
|
|
|
|
|
# ==================== fortune.py coverage ====================
|
|
|
|
def test_fortune_weapon_intro_uses_translated_realm():
|
|
"""
|
|
Integration test: Fortune weapon discovery intro should use
|
|
translated realm names via str(realm).
|
|
"""
|
|
from src.i18n import t
|
|
|
|
weapon = get_real_weapon()
|
|
# Simulate what fortune.py does.
|
|
intro = t(
|
|
"You discovered a {realm} weapon『{weapon_name}』in your fortune.",
|
|
realm=str(weapon.realm),
|
|
weapon_name=weapon.name
|
|
)
|
|
|
|
# Intro should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES:
|
|
assert raw_value not in intro, (
|
|
f"Fortune intro contains raw enum value '{raw_value}': {intro}"
|
|
)
|
|
|
|
|
|
def test_fortune_auxiliary_intro_uses_translated_realm():
|
|
"""
|
|
Integration test: Fortune auxiliary discovery intro should use
|
|
translated realm names via str(realm).
|
|
"""
|
|
from src.i18n import t
|
|
|
|
# Get a real auxiliary.
|
|
if not auxiliaries_by_id:
|
|
pytest.skip("No auxiliaries available in game data")
|
|
auxiliary = next(iter(auxiliaries_by_id.values()))
|
|
|
|
# Simulate what fortune.py does.
|
|
intro = t(
|
|
"You discovered a {realm} auxiliary『{auxiliary_name}』in your fortune.",
|
|
realm=str(auxiliary.realm),
|
|
auxiliary_name=auxiliary.name
|
|
)
|
|
|
|
# Intro should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES:
|
|
assert raw_value not in intro, (
|
|
f"Fortune intro contains raw enum value '{raw_value}': {intro}"
|
|
)
|
|
|
|
|
|
# ==================== inventory_mixin.py coverage ====================
|
|
|
|
def test_can_buy_item_error_message_shows_translated_realm():
|
|
"""
|
|
Integration test: can_buy_item error message for realm restriction
|
|
should show translated realm names, not raw enum values.
|
|
"""
|
|
# Get a high-realm elixir.
|
|
high_realm_elixir = None
|
|
for elixir in elixirs_by_id.values():
|
|
if elixir.realm >= Realm.Foundation_Establishment:
|
|
high_realm_elixir = elixir
|
|
break
|
|
|
|
if high_realm_elixir is None:
|
|
pytest.skip("No high-realm elixir found in game data")
|
|
|
|
# Simulate the error message generation from inventory_mixin.py.
|
|
error_msg = f"境界不足,无法承受药力 ({str(high_realm_elixir.realm)})"
|
|
|
|
# Error message should NOT contain raw enum values.
|
|
for raw_value in RAW_REALM_VALUES:
|
|
assert raw_value not in error_msg, (
|
|
f"Error message contains raw enum value '{raw_value}': {error_msg}"
|
|
)
|