gewuyou 5aefd77ad0 fix(game-tests): 收敛配置与序列化测试告警
- 修复架构配置、启动流程与序列化测试中的异步等待和 invariant 解析告警

- 补充 AllOf 与 persistence 测试的残余状态校验与 ConfigureAwait 修正,继续压低 Game.Tests warning

- 更新 analyzer-warning-reduction 跟踪与 trace,纠正 RP-054 的 stop-condition 口径并记录 RP-055 指标
2026-04-24 18:29:17 +08:00

192 lines
5.8 KiB
C#

using System.Globalization;
using Newtonsoft.Json;
using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer;
namespace GFramework.Game.Tests.Serializer;
[TestFixture]
public sealed class JsonSerializerTests
{
[Test]
public void Serialize_And_Deserialize_Should_RoundTrip_Object()
{
var serializer = new GameJsonSerializer();
var original = new PlayerStateStub
{
Name = "Player1",
Level = 7
};
var json = serializer.Serialize(original);
var restored = serializer.Deserialize<PlayerStateStub>(json);
Assert.Multiple(() =>
{
Assert.That(restored.Name, Is.EqualTo("Player1"));
Assert.That(restored.Level, Is.EqualTo(7));
});
}
[Test]
public void Serialize_Should_Honor_Injected_Settings()
{
var serializer = new GameJsonSerializer(new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
});
var json = serializer.Serialize(new OptionalStateStub
{
Name = "Configured",
Description = null
});
Assert.Multiple(() =>
{
Assert.That(json, Does.Contain(Environment.NewLine));
Assert.That(json, Does.Contain("\"Name\": \"Configured\""));
Assert.That(json, Does.Not.Contain("Description"));
});
}
[Test]
public void Settings_And_Converters_Should_Expose_Live_Configuration_Instance()
{
var settings = new JsonSerializerSettings();
var serializer = new GameJsonSerializer(settings);
Assert.Multiple(() =>
{
Assert.That(serializer.Settings, Is.SameAs(settings));
Assert.That(serializer.Converters, Is.SameAs(settings.Converters));
});
}
[Test]
public void Converters_Should_Be_Used_For_Serialization_And_Deserialization()
{
var serializer = new GameJsonSerializer();
serializer.Converters.Add(new CoordinateStubConverter());
var json = serializer.Serialize(new CoordinateStub { X = 3, Y = 9 });
var restored = serializer.Deserialize<CoordinateStub>(json);
Assert.Multiple(() =>
{
Assert.That(json, Is.EqualTo("\"3:9\""));
Assert.That(restored.X, Is.EqualTo(3));
Assert.That(restored.Y, Is.EqualTo(9));
});
}
[Test]
public void Deserialize_Should_Throw_With_Target_Type_Context_When_Json_Is_Invalid()
{
var serializer = new GameJsonSerializer();
var exception =
Assert.Throws<InvalidOperationException>(() => serializer.Deserialize<PlayerStateStub>("{invalid json}"));
Assert.Multiple(() =>
{
Assert.That(exception, Is.Not.Null);
Assert.That(exception!.Message, Does.Contain(typeof(PlayerStateStub).FullName));
Assert.That(exception.InnerException, Is.Not.Null);
});
}
[Test]
public void Deserialize_With_Runtime_Type_Should_Throw_With_Target_Type_Context_When_Json_Is_Invalid()
{
var serializer = new GameJsonSerializer();
var exception =
Assert.Throws<InvalidOperationException>(() =>
serializer.Deserialize("{invalid json}", typeof(PlayerStateStub)));
Assert.Multiple(() =>
{
Assert.That(exception, Is.Not.Null);
Assert.That(exception!.Message, Does.Contain(typeof(PlayerStateStub).FullName));
Assert.That(exception.InnerException, Is.Not.Null);
});
}
[Test]
public void Deserialize_With_Runtime_Type_Should_Return_Target_Runtime_Type()
{
var serializer = new GameJsonSerializer();
var restored = serializer.Deserialize("{\"Name\":\"Runtime\",\"Level\":11}", typeof(PlayerStateStub));
Assert.That(restored, Is.TypeOf<PlayerStateStub>());
Assert.That(((PlayerStateStub)restored).Level, Is.EqualTo(11));
}
[Test]
public void Serialize_With_Runtime_Type_Should_Allow_Null_Object()
{
var serializer = new GameJsonSerializer();
var json = serializer.Serialize(null!, typeof(PlayerStateStub));
Assert.That(json, Is.EqualTo("null"));
}
[Test]
public void Deserialize_Should_Preserve_ArgumentException_For_Invalid_Input_Arguments()
{
var serializer = new GameJsonSerializer();
var exception = Assert.Throws<ArgumentException>(() => serializer.Deserialize<PlayerStateStub>(string.Empty));
Assert.That(exception, Is.Not.Null);
}
private sealed class PlayerStateStub
{
public string Name { get; set; } = string.Empty;
public int Level { get; set; }
}
private sealed class OptionalStateStub
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
}
private sealed class CoordinateStub
{
public int X { get; set; }
public int Y { get; set; }
}
private sealed class CoordinateStubConverter : JsonConverter<CoordinateStub>
{
public override void WriteJson(JsonWriter writer, CoordinateStub? value, JsonSerializer serializer)
{
writer.WriteValue($"{value?.X}:{value?.Y}");
}
public override CoordinateStub ReadJson(
JsonReader reader,
Type objectType,
CoordinateStub? existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
var raw = (string?)reader.Value ?? throw new JsonSerializationException("Coordinate value cannot be null.");
var parts = raw.Split(':');
return new CoordinateStub
{
X = int.Parse(parts[0], CultureInfo.InvariantCulture),
Y = int.Parse(parts[1], CultureInfo.InvariantCulture)
};
}
}
}