Files
cultivation-world-simulator/tests/test_i18n_realm_display.py
Zihao Xu aaa636a08e fix: use str() instead of .value for realm i18n display (#98)
* 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
2026-01-25 18:44:13 +08:00

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}"
)