using System.Text.Json; using FluentAssertions; using Workflow.Api.Serialization; using Xunit; namespace Workflow.Tests.Serialization; /// /// 验证统一数据规范:DateTime / DateTimeOffset 序列化为 UTC 毫秒时间戳(long)。 /// 这是第二期"后端数据规范统一"的核心契约 —— 后端只输出毫秒时间戳,时区/格式化交给前端。 /// public class TimestampJsonConverterTests { private static JsonSerializerOptions BuildOptions() => new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new TimestampDateTimeConverter(), new TimestampDateTimeOffsetConverter() } }; private class SampleDto { public Guid Id { get; set; } public DateTime CreatedAt { get; set; } public DateTimeOffset? UpdatedAt { get; set; } } [Fact] public void DateTime_serializes_to_millisecond_epoch_number() { // 固定时刻:2026-06-14T03:30:00Z var utc = new DateTime(2026, 6, 14, 3, 30, 0, DateTimeKind.Utc); var expectedMs = new DateTimeOffset(utc, TimeSpan.Zero).ToUnixTimeMilliseconds(); var dto = new SampleDto { Id = Guid.NewGuid(), CreatedAt = utc }; var json = JsonSerializer.Serialize(dto, BuildOptions()); using var doc = JsonDocument.Parse(json); doc.RootElement.GetProperty("createdAt").ValueKind.Should().Be(JsonValueKind.Number); doc.RootElement.GetProperty("createdAt").GetInt64().Should().Be(expectedMs); } [Fact] public void DateTimeOffset_serializes_to_millisecond_epoch_number() { var dto = new DateTimeOffset(2026, 1, 2, 3, 4, 5, TimeSpan.Zero); var expectedMs = dto.ToUnixTimeMilliseconds(); var json = JsonSerializer.Serialize(new { t = dto }, BuildOptions()); using var doc = JsonDocument.Parse(json); doc.RootElement.GetProperty("t").GetInt64().Should().Be(expectedMs); } [Fact] public void Guid_serializes_as_string() { // 验证 ID 统一为字符串(Guid 天然序列化为字符串,无需额外转换器) var id = Guid.NewGuid(); var dto = new SampleDto { Id = id }; var json = JsonSerializer.Serialize(dto, BuildOptions()); using var doc = JsonDocument.Parse(json); doc.RootElement.GetProperty("id").ValueKind.Should().Be(JsonValueKind.String); doc.RootElement.GetProperty("id").GetGuid().Should().Be(id); } [Fact] public void Deserializes_epoch_number_back_to_utc_dateTime() { var utc = new DateTime(2026, 6, 14, 3, 30, 0, DateTimeKind.Utc); var ms = new DateTimeOffset(utc, TimeSpan.Zero).ToUnixTimeMilliseconds(); var json = $$"""{"id":"00000000-0000-0000-0000-000000000000","createdAt":{{ms}}}"""; var dto = JsonSerializer.Deserialize(json, BuildOptions())!; dto.CreatedAt.Should().Be(utc); dto.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); } [Fact] public void Deserializes_legacy_iso_string_for_backward_compatibility() { // 旧客户端/旧数据可能仍是 ISO 字符串,必须能读回(向后兼容) var json = """{"id":"00000000-0000-0000-0000-000000000000","createdAt":"2026-06-14T03:30:00Z"}"""; var dto = JsonSerializer.Deserialize(json, BuildOptions())!; dto.CreatedAt.Should().Be(new DateTime(2026, 6, 14, 3, 30, 0, DateTimeKind.Utc)); } }