using System.Collections.Generic;
using NUnit.Framework;
using Newtonsoft.Json;
using BaseGames.Core.Save;
namespace BaseGames.Tests.EditMode
{
///
/// 存档系统单元测试(EditMode,纯 C# 逻辑,无 MonoBehaviour 依赖)。
/// 覆盖:SaveMigrator 迁移链、SaveData 序列化/反序列化往返、字段完整性。
///
[TestFixture]
public class SaveSystemTests
{
// ── SaveMigrator · 边界输入 ──────────────────────────────────────────
[Test]
public void Migrate_NullInput_ReturnsNull()
{
var result = SaveMigrator.Migrate(null);
Assert.IsNull(result, "null 输入应安全返回 null");
}
[Test]
public void Migrate_NullMeta_ReturnsSameObject()
{
var data = new SaveData { Meta = null };
var result = SaveMigrator.Migrate(data);
Assert.AreSame(data, result, "Meta 为 null 时应返回原对象");
}
// ── SaveMigrator · 版本迁移 ──────────────────────────────────────────
[Test]
public void Migrate_EmptyVersion_MigratesTo_2_1()
{
var data = new SaveData();
data.Meta.Version = "";
var result = SaveMigrator.Migrate(data);
Assert.AreEqual(SaveMigrator.CurrentVersion, result.Meta.Version);
}
[Test]
public void Migrate_OldVersion_FillsMissingSubObjects()
{
var data = new SaveData();
data.Meta.Version = "1.0";
data.Tutorial = null;
data.Settings = null;
data.EventChains = null;
data.ChallengeRooms = null;
var result = SaveMigrator.Migrate(data);
Assert.IsNotNull(result.Tutorial, "迁移后 Tutorial 不应为 null");
Assert.IsNotNull(result.Settings, "迁移后 Settings 不应为 null");
Assert.IsNotNull(result.EventChains, "迁移后 EventChains 不应为 null");
Assert.IsNotNull(result.ChallengeRooms, "迁移后 ChallengeRooms 不应为 null");
Assert.IsNull(result.NGPlus, "迁移后 NGPlus 应为 null(非 NG+ 模式)");
}
[Test]
public void Migrate_OldVersion_DeathShade_RemainsNull()
{
var data = new SaveData();
data.Meta.Version = "1.0";
data.Player.DeathShade = null;
var result = SaveMigrator.Migrate(data);
Assert.IsNull(result.Player.DeathShade,
"无遗骸时 DeathShade 应保持 null,由 DeathShadeManager 按需创建");
}
[Test]
public void Migrate_Version2_0_FillsMapPins()
{
var data = new SaveData();
data.Meta.Version = "2.0";
data.Map.Pins = null;
var result = SaveMigrator.Migrate(data);
Assert.IsNotNull(result.Map.Pins, "从 2.0 迁移后 Map.Pins 不应为 null");
Assert.IsEmpty(result.Map.Pins, "Map.Pins 应初始化为空列表");
}
[Test]
public void Migrate_CurrentVersion_NoChange()
{
var data = new SaveData();
data.Meta.Version = SaveMigrator.CurrentVersion;
var result = SaveMigrator.Migrate(data);
Assert.AreEqual(SaveMigrator.CurrentVersion, result.Meta.Version);
}
// ── SaveData · JSON 序列化往返 ───────────────────────────────────────
[Test]
public void SaveData_SerializeDeserialize_PreservesPlayerData()
{
var original = new SaveData();
original.Player.CurrentHP = 55;
original.Player.MaxHP = 100;
original.Player.CurrentLingZhu = 1234;
original.Player.Scene = "TestRoom";
string json = JsonConvert.SerializeObject(original, Formatting.None);
var restored = JsonConvert.DeserializeObject(json);
Assert.AreEqual(original.Player.CurrentHP, restored.Player.CurrentHP);
Assert.AreEqual(original.Player.MaxHP, restored.Player.MaxHP);
Assert.AreEqual(original.Player.CurrentLingZhu, restored.Player.CurrentLingZhu);
Assert.AreEqual(original.Player.Scene, restored.Player.Scene);
}
[Test]
public void SaveData_SerializeDeserialize_PreservesAbilityFlags()
{
var original = new SaveData();
original.Player.AbilityFlags = 0xDEADBEEFu;
string json = JsonConvert.SerializeObject(original, Formatting.None);
var restored = JsonConvert.DeserializeObject(json);
Assert.AreEqual(original.Player.AbilityFlags, restored.Player.AbilityFlags,
"AbilityFlags uint 不应因符号扩展而错误");
}
[Test]
public void SaveData_SerializeDeserialize_NGPlusNull_StaysNull()
{
var original = new SaveData();
original.NGPlus = null;
string json = JsonConvert.SerializeObject(original, Formatting.None);
var restored = JsonConvert.DeserializeObject(json);
Assert.IsNull(restored.NGPlus, "NGPlus null 应序列化/反序列化保持 null");
}
[Test]
public void SaveData_SerializeDeserialize_MetaVersionPreserved()
{
var original = new SaveData();
original.Meta.Version = SaveMigrator.CurrentVersion;
original.Meta.SlotIndex = 2;
original.Meta.SaveCount = 42;
string json = JsonConvert.SerializeObject(original, Formatting.None);
var restored = JsonConvert.DeserializeObject(json);
Assert.AreEqual(SaveMigrator.CurrentVersion, restored.Meta.Version);
Assert.AreEqual(2, restored.Meta.SlotIndex);
Assert.AreEqual(42, restored.Meta.SaveCount);
}
[Test]
public void SaveData_ExtensionData_PreservesUnknownFields()
{
// 模拟未来版本存档包含当前版本未知的字段
string futureJson = @"{
""Meta"": { ""Version"": ""3.0"", ""SlotIndex"": 0 },
""Player"": { ""CurrentHP"": 80 },
""FutureFeature"": { ""SomeValue"": 123 }
}";
var restored = JsonConvert.DeserializeObject(futureJson);
Assert.IsNotNull(restored);
Assert.AreEqual(80, restored.Player.CurrentHP);
Assert.IsTrue(restored.ExtensionData.ContainsKey("FutureFeature"),
"ExtensionData 应保留未知字段,确保向前兼容");
}
// ── SaveMeta · Checksum 字段 ─────────────────────────────────────────
[Test]
public void SaveMeta_Checksum_DefaultIsNull()
{
var meta = new SaveMeta();
Assert.IsNull(meta.Checksum, "新建 SaveMeta 的 Checksum 默认应为 null");
}
[Test]
public void SaveData_Serialize_WithNullChecksum_NotThrow()
{
var data = new SaveData();
data.Meta.Checksum = null;
Assert.DoesNotThrow(
() => JsonConvert.SerializeObject(data, Formatting.None));
}
// ── SaveMeta · IsSteelSoul ────────────────────────────────────────────
[Test]
public void SaveMeta_IsSteelSoul_DefaultIsFalse()
{
var meta = new SaveMeta();
Assert.IsFalse(meta.IsSteelSoul, "IsSteelSoul 默认应为 false");
}
}
}