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