* feat: improve save/load interface with custom names and metadata - Add custom save name support with input validation - Extend save metadata with avatar counts, protagonist info, and event count - Add quick save button alongside named save option - Enhance save list display with richer information - Add sanitize_save_name and find_protagonist_name helpers - Update API endpoints to support new features - Add i18n translations for new UI elements Closes #95 * test: add comprehensive tests for save custom name feature - Add 37 tests for sanitize_save_name, find_protagonist_name - Add tests for custom name API endpoints - Add tests for enhanced metadata - Fix unused NIcon import in SaveLoadPanel - Add zh-TW translations for new save_load keys * test(frontend): add SaveLoadPanel component tests - Add 21 tests for SaveLoadPanel component - Cover save mode, load mode, display, validation - Mock naive-ui components, stores, and API
681 lines
24 KiB
Python
681 lines
24 KiB
Python
"""
|
|
Tests for custom save name and enhanced metadata features (Issue #95).
|
|
"""
|
|
import pytest
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from src.classes.world import World
|
|
from src.classes.map import Map
|
|
from src.classes.tile import TileType
|
|
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.classes.persona import personas_by_id
|
|
from src.sim.simulator import Simulator
|
|
from src.sim.save.save_game import (
|
|
save_game,
|
|
sanitize_save_name,
|
|
find_protagonist_name,
|
|
PROTAGONIST_PERSONA_IDS,
|
|
)
|
|
from src.sim.load.load_game import load_game, get_events_db_path
|
|
from src.utils.id_generator import get_avatar_id
|
|
|
|
|
|
def create_test_map():
|
|
"""Create a simple test map."""
|
|
m = Map(width=10, height=10)
|
|
for x in range(10):
|
|
for y in range(10):
|
|
m.create_tile(x, y, TileType.PLAIN)
|
|
return m
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_save_dir(tmp_path):
|
|
d = tmp_path / "saves"
|
|
d.mkdir()
|
|
return d
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for sanitize_save_name function
|
|
# =============================================================================
|
|
|
|
|
|
class TestSanitizeSaveName:
|
|
"""Tests for the sanitize_save_name helper function."""
|
|
|
|
def test_chinese_characters_allowed(self):
|
|
"""Test that Chinese characters are preserved."""
|
|
result = sanitize_save_name("我的存档")
|
|
assert result == "我的存档"
|
|
|
|
def test_english_characters_allowed(self):
|
|
"""Test that English characters are preserved."""
|
|
result = sanitize_save_name("MyFirstSave")
|
|
assert result == "MyFirstSave"
|
|
|
|
def test_numbers_allowed(self):
|
|
"""Test that numbers are preserved."""
|
|
result = sanitize_save_name("Save123")
|
|
assert result == "Save123"
|
|
|
|
def test_underscores_allowed(self):
|
|
"""Test that underscores are preserved."""
|
|
result = sanitize_save_name("my_save_file")
|
|
assert result == "my_save_file"
|
|
|
|
def test_mixed_content(self):
|
|
"""Test mixed Chinese, English, and numbers."""
|
|
result = sanitize_save_name("我的Save存档_123")
|
|
assert result == "我的Save存档_123"
|
|
|
|
def test_special_characters_replaced(self):
|
|
"""Test that special characters are replaced with underscores."""
|
|
result = sanitize_save_name("Save!@#$%^&*()")
|
|
assert "!" not in result
|
|
assert "@" not in result
|
|
assert result.replace("_", "").isalnum() or result == "Save__________"
|
|
|
|
def test_path_separators_removed(self):
|
|
"""Test that path separators are removed."""
|
|
result = sanitize_save_name("path/to\\save")
|
|
assert "/" not in result
|
|
assert "\\" not in result
|
|
|
|
def test_dangerous_chars_removed(self):
|
|
"""Test that dangerous filesystem characters are removed."""
|
|
result = sanitize_save_name('save:*?"<>|name')
|
|
assert ":" not in result
|
|
assert "*" not in result
|
|
assert "?" not in result
|
|
assert '"' not in result
|
|
assert "<" not in result
|
|
assert ">" not in result
|
|
assert "|" not in result
|
|
|
|
def test_length_limit(self):
|
|
"""Test that names are truncated to 50 characters."""
|
|
long_name = "a" * 100
|
|
result = sanitize_save_name(long_name)
|
|
assert len(result) <= 50
|
|
|
|
def test_empty_string_returns_default(self):
|
|
"""Test that empty string returns 'save'."""
|
|
result = sanitize_save_name("")
|
|
assert result == "save"
|
|
|
|
def test_only_special_chars_returns_default(self):
|
|
"""Test that a name with only special chars returns 'save'."""
|
|
# After replacing all special chars with underscores, if nothing left, return 'save'.
|
|
# But underscores are kept, so "!!!" becomes "___" which is not empty.
|
|
result = sanitize_save_name("!!!")
|
|
# Should be "___" or similar, not "save".
|
|
assert len(result) > 0
|
|
|
|
def test_spaces_replaced(self):
|
|
"""Test that spaces are replaced with underscores."""
|
|
result = sanitize_save_name("my save file")
|
|
assert " " not in result
|
|
assert "_" in result
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for find_protagonist_name function
|
|
# =============================================================================
|
|
|
|
|
|
class TestFindProtagonistName:
|
|
"""Tests for the find_protagonist_name helper function."""
|
|
|
|
def test_no_protagonists(self, temp_save_dir):
|
|
"""Test with no protagonist avatars."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add regular avatar without protagonist traits.
|
|
avatar = Avatar(
|
|
world=world,
|
|
name="RegularAvatar",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
avatar.personas = []
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
result = find_protagonist_name(world)
|
|
assert result is None
|
|
|
|
def test_finds_protagonist_with_trait_30(self, temp_save_dir):
|
|
"""Test finding protagonist with trait ID 30 (穿越者)."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add protagonist avatar with trait 30.
|
|
avatar = Avatar(
|
|
world=world,
|
|
name="穿越者主角",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
# Mock persona with ID 30.
|
|
mock_persona = MagicMock()
|
|
mock_persona.id = 30
|
|
avatar.personas = [mock_persona]
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
result = find_protagonist_name(world)
|
|
assert result == "穿越者主角"
|
|
|
|
def test_finds_protagonist_with_trait_31(self, temp_save_dir):
|
|
"""Test finding protagonist with trait ID 31 (气运之子)."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add protagonist avatar with trait 31.
|
|
avatar = Avatar(
|
|
world=world,
|
|
name="气运之子",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
mock_persona = MagicMock()
|
|
mock_persona.id = 31
|
|
avatar.personas = [mock_persona]
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
result = find_protagonist_name(world)
|
|
assert result == "气运之子"
|
|
|
|
def test_finds_first_protagonist_when_multiple(self, temp_save_dir):
|
|
"""Test that it returns one protagonist when multiple exist."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add two protagonist avatars.
|
|
for i, name in enumerate(["主角一", "主角二"]):
|
|
avatar = Avatar(
|
|
world=world,
|
|
name=name,
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
mock_persona = MagicMock()
|
|
mock_persona.id = 30
|
|
avatar.personas = [mock_persona]
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
result = find_protagonist_name(world)
|
|
assert result in ["主角一", "主角二"]
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for save_game with custom name
|
|
# =============================================================================
|
|
|
|
|
|
class TestSaveGameWithCustomName:
|
|
"""Tests for save_game function with custom_name parameter."""
|
|
|
|
def test_save_with_custom_name(self, temp_save_dir):
|
|
"""Test that custom name is used in filename."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
success, filename = save_game(
|
|
world, sim, [],
|
|
save_path=None,
|
|
custom_name="我的测试存档"
|
|
)
|
|
|
|
# Check save succeeded.
|
|
assert success
|
|
|
|
# Filename should start with the sanitized custom name.
|
|
assert filename.startswith("我的测试存档_")
|
|
assert filename.endswith(".json")
|
|
|
|
def test_save_without_custom_name(self, temp_save_dir):
|
|
"""Test that default naming is used when no custom name provided."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
# Patch CONFIG.paths.saves to use temp dir.
|
|
with patch.object(__import__('src.utils.config', fromlist=['CONFIG']).CONFIG.paths, 'saves', temp_save_dir):
|
|
success, filename = save_game(
|
|
world, sim, [],
|
|
save_path=None,
|
|
custom_name=None
|
|
)
|
|
|
|
assert success
|
|
# Default filename format: YYYYMMDD_HHMMSS_Y{year}M{month}.json.
|
|
assert "_Y100M1.json" in filename or filename.endswith(".json")
|
|
|
|
def test_custom_name_stored_in_meta(self, temp_save_dir):
|
|
"""Test that custom_name is stored in save metadata."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
save_path = temp_save_dir / "test_custom_meta.json"
|
|
success, _ = save_game(
|
|
world, sim, [],
|
|
save_path=save_path,
|
|
custom_name="我的存档"
|
|
)
|
|
|
|
assert success
|
|
|
|
# Read and verify meta.
|
|
with open(save_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
assert data["meta"]["custom_name"] == "我的存档"
|
|
|
|
def test_null_custom_name_in_meta(self, temp_save_dir):
|
|
"""Test that null custom_name is stored when not provided."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
save_path = temp_save_dir / "test_null_meta.json"
|
|
success, _ = save_game(
|
|
world, sim, [],
|
|
save_path=save_path,
|
|
custom_name=None
|
|
)
|
|
|
|
assert success
|
|
|
|
with open(save_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
assert data["meta"]["custom_name"] is None
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for enhanced metadata
|
|
# =============================================================================
|
|
|
|
|
|
class TestEnhancedMetadata:
|
|
"""Tests for enhanced save metadata (avatar counts, protagonist)."""
|
|
|
|
def test_avatar_counts_in_meta(self, temp_save_dir):
|
|
"""Test that avatar counts are correctly stored."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add living avatars.
|
|
for i in range(5):
|
|
avatar = Avatar(
|
|
world=world,
|
|
name=f"Avatar{i}",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
# Add dead avatars.
|
|
for i in range(3):
|
|
avatar = Avatar(
|
|
world=world,
|
|
name=f"DeadAvatar{i}",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
world.avatar_manager.dead_avatars[avatar.id] = avatar
|
|
|
|
sim = Simulator(world)
|
|
save_path = temp_save_dir / "test_counts.json"
|
|
success, _ = save_game(world, sim, [], save_path)
|
|
|
|
assert success
|
|
|
|
with open(save_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
meta = data["meta"]
|
|
assert meta["alive_count"] == 5
|
|
assert meta["dead_count"] == 3
|
|
assert meta["avatar_count"] == 8 # total
|
|
|
|
def test_protagonist_name_in_meta(self, temp_save_dir):
|
|
"""Test that protagonist name is stored in metadata."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add protagonist avatar.
|
|
avatar = Avatar(
|
|
world=world,
|
|
name="林动",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
mock_persona = MagicMock()
|
|
mock_persona.id = 31 # 气运之子
|
|
avatar.personas = [mock_persona]
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
sim = Simulator(world)
|
|
save_path = temp_save_dir / "test_protagonist.json"
|
|
success, _ = save_game(world, sim, [], save_path)
|
|
|
|
assert success
|
|
|
|
with open(save_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
assert data["meta"]["protagonist_name"] == "林动"
|
|
|
|
def test_no_protagonist_in_meta(self, temp_save_dir):
|
|
"""Test that protagonist_name is None when no protagonist exists."""
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add regular avatar.
|
|
avatar = Avatar(
|
|
world=world,
|
|
name="普通人",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
avatar.personas = []
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
sim = Simulator(world)
|
|
save_path = temp_save_dir / "test_no_prot.json"
|
|
success, _ = save_game(world, sim, [], save_path)
|
|
|
|
assert success
|
|
|
|
with open(save_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
assert data["meta"]["protagonist_name"] is None
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for API endpoints
|
|
# =============================================================================
|
|
|
|
|
|
class TestSaveApiWithCustomName:
|
|
"""Tests for /api/game/save endpoint with custom name."""
|
|
|
|
def test_api_save_with_custom_name(self, temp_save_dir):
|
|
"""Test API save endpoint with custom name."""
|
|
from fastapi.testclient import TestClient
|
|
from src.server import main
|
|
from src.utils.config import CONFIG
|
|
|
|
# Setup game instance.
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
original_state = main.game_instance.copy()
|
|
main.game_instance["world"] = world
|
|
main.game_instance["sim"] = sim
|
|
|
|
with patch.object(CONFIG.paths, "saves", temp_save_dir):
|
|
client = TestClient(main.app)
|
|
response = client.post(
|
|
"/api/game/save",
|
|
json={"custom_name": "我的API存档"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["status"] == "ok"
|
|
assert "我的API存档" in data["filename"]
|
|
|
|
# Cleanup.
|
|
main.game_instance.update(original_state)
|
|
|
|
def test_api_save_without_custom_name(self, temp_save_dir):
|
|
"""Test API save endpoint without custom name."""
|
|
from fastapi.testclient import TestClient
|
|
from src.server import main
|
|
from src.utils.config import CONFIG
|
|
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
original_state = main.game_instance.copy()
|
|
main.game_instance["world"] = world
|
|
main.game_instance["sim"] = sim
|
|
|
|
with patch.object(CONFIG.paths, "saves", temp_save_dir):
|
|
client = TestClient(main.app)
|
|
response = client.post(
|
|
"/api/game/save",
|
|
json={}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["status"] == "ok"
|
|
assert data["filename"].endswith(".json")
|
|
|
|
main.game_instance.update(original_state)
|
|
|
|
def test_api_save_invalid_name_rejected(self, temp_save_dir):
|
|
"""Test that invalid save names are rejected."""
|
|
from fastapi.testclient import TestClient
|
|
from src.server import main
|
|
from src.utils.config import CONFIG
|
|
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
original_state = main.game_instance.copy()
|
|
main.game_instance["world"] = world
|
|
main.game_instance["sim"] = sim
|
|
|
|
with patch.object(CONFIG.paths, "saves", temp_save_dir):
|
|
client = TestClient(main.app)
|
|
# Name with only special characters - should be rejected.
|
|
response = client.post(
|
|
"/api/game/save",
|
|
json={"custom_name": "!!!@@@###"}
|
|
)
|
|
|
|
# Should be rejected with 400.
|
|
assert response.status_code == 400
|
|
|
|
main.game_instance.update(original_state)
|
|
|
|
def test_api_save_name_too_long_rejected(self, temp_save_dir):
|
|
"""Test that names over 50 chars are rejected."""
|
|
from fastapi.testclient import TestClient
|
|
from src.server import main
|
|
from src.utils.config import CONFIG
|
|
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
sim = Simulator(world)
|
|
|
|
original_state = main.game_instance.copy()
|
|
main.game_instance["world"] = world
|
|
main.game_instance["sim"] = sim
|
|
|
|
with patch.object(CONFIG.paths, "saves", temp_save_dir):
|
|
client = TestClient(main.app)
|
|
long_name = "a" * 51
|
|
response = client.post(
|
|
"/api/game/save",
|
|
json={"custom_name": long_name}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
main.game_instance.update(original_state)
|
|
|
|
|
|
class TestSavesListApiWithMetadata:
|
|
"""Tests for /api/saves endpoint returning enhanced metadata."""
|
|
|
|
def test_api_saves_returns_new_fields(self, temp_save_dir):
|
|
"""Test that /api/saves returns new metadata fields."""
|
|
from fastapi.testclient import TestClient
|
|
from src.server import main
|
|
from src.utils.config import CONFIG
|
|
|
|
# Create a save file with metadata.
|
|
game_map = create_test_map()
|
|
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
|
|
|
# Add avatars.
|
|
for i in range(3):
|
|
avatar = Avatar(
|
|
world=world,
|
|
name=f"Avatar{i}",
|
|
id=get_avatar_id(),
|
|
birth_month_stamp=create_month_stamp(Year(80), Month.JANUARY),
|
|
age=Age(20, Realm.Qi_Refinement),
|
|
gender=Gender.MALE,
|
|
)
|
|
world.avatar_manager.avatars[avatar.id] = avatar
|
|
|
|
sim = Simulator(world)
|
|
save_path = temp_save_dir / "test_list.json"
|
|
save_game(world, sim, [], save_path, custom_name="列表测试")
|
|
|
|
with patch.object(CONFIG.paths, "saves", temp_save_dir):
|
|
client = TestClient(main.app)
|
|
response = client.get("/api/saves")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert len(data["saves"]) >= 1
|
|
save_item = data["saves"][0]
|
|
|
|
# Verify new fields are present.
|
|
assert "avatar_count" in save_item
|
|
assert "alive_count" in save_item
|
|
assert "dead_count" in save_item
|
|
assert "protagonist_name" in save_item
|
|
assert "custom_name" in save_item
|
|
assert "event_count" in save_item
|
|
assert "language" in save_item
|
|
|
|
# Verify values.
|
|
assert save_item["custom_name"] == "列表测试"
|
|
assert save_item["alive_count"] == 3
|
|
assert save_item["avatar_count"] == 3
|
|
|
|
def test_api_saves_old_save_compatibility(self, temp_save_dir):
|
|
"""Test that old saves without new fields return defaults."""
|
|
from fastapi.testclient import TestClient
|
|
from src.server import main
|
|
from src.utils.config import CONFIG
|
|
|
|
# Create a "legacy" save file without new metadata fields.
|
|
legacy_save = {
|
|
"meta": {
|
|
"version": "1.0",
|
|
"save_time": "2026-01-01T12:00:00",
|
|
"game_time": "100年1月",
|
|
},
|
|
"world": {"month_stamp": 1200},
|
|
"avatars": [],
|
|
"events": [],
|
|
"simulator": {},
|
|
}
|
|
|
|
save_path = temp_save_dir / "legacy.json"
|
|
with open(save_path, "w", encoding="utf-8") as f:
|
|
json.dump(legacy_save, f)
|
|
|
|
with patch.object(CONFIG.paths, "saves", temp_save_dir):
|
|
client = TestClient(main.app)
|
|
response = client.get("/api/saves")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Find our legacy save.
|
|
legacy_item = None
|
|
for save in data["saves"]:
|
|
if save["filename"] == "legacy.json":
|
|
legacy_item = save
|
|
break
|
|
|
|
assert legacy_item is not None
|
|
|
|
# New fields should have default values.
|
|
assert legacy_item["avatar_count"] == 0
|
|
assert legacy_item["alive_count"] == 0
|
|
assert legacy_item["dead_count"] == 0
|
|
assert legacy_item["protagonist_name"] is None
|
|
assert legacy_item["custom_name"] is None
|
|
assert legacy_item["event_count"] == 0
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for validate_save_name function
|
|
# =============================================================================
|
|
|
|
|
|
class TestValidateSaveName:
|
|
"""Tests for the validate_save_name function in main.py."""
|
|
|
|
def test_valid_chinese_name(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("我的存档") is True
|
|
|
|
def test_valid_english_name(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("MySave") is True
|
|
|
|
def test_valid_mixed_name(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("我的Save_123") is True
|
|
|
|
def test_empty_name_invalid(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("") is False
|
|
|
|
def test_too_long_name_invalid(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("a" * 51) is False
|
|
|
|
def test_special_chars_invalid(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("save!@#") is False
|
|
|
|
def test_space_invalid(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("my save") is False
|
|
|
|
def test_exactly_50_chars_valid(self):
|
|
from src.server.main import validate_save_name
|
|
assert validate_save_name("a" * 50) is True
|