This commit is contained in:
wanghu
2026-09-02 09:38:45 +08:00
commit 8774b6cc44
128 changed files with 9382 additions and 0 deletions
@@ -0,0 +1,43 @@
using Xunit;
using Yh.Platform.Abstractions;
namespace Yh.Platform.Abstractions.Tests;
public class ErrorCodeAndExceptionTests
{
[Fact]
public void ErrorCode_常量值与Java一比一()
{
Assert.Equal(0, ErrorCode.Success.Code);
Assert.Equal(1, ErrorCode.Error.Code);
Assert.Equal(2, ErrorCode.ParamError.Code);
Assert.Equal(3, ErrorCode.Forbidden.Code);
Assert.Equal(4, ErrorCode.Unauthorized.Code);
Assert.Equal(5, ErrorCode.Custom.Code);
Assert.Equal(6, ErrorCode.InternalServerError.Code);
Assert.Equal(405, ErrorCode.HttpMethodNotAllowed.Code);
Assert.Equal("登录已经过期,请重新登录", ErrorCode.Unauthorized.Msg);
}
[Fact]
public void DException_便捷构造_默认Custom且message为纯msg()
{
var e = new DException("业务失败");
Assert.Equal(5, e.Code!.Code);
Assert.Equal("业务失败", e.Message);
}
[Fact]
public void DException_携带code_msg_data_等价Java语义()
{
var code = new OpenApiLike(5, "组下存在子组或用户,禁止删除");
var data = new { groupId = 9 };
var e = new DException(code, code.Msg, data);
Assert.Equal(5, e.Code!.Code);
Assert.Equal("组下存在子组或用户,禁止删除", e.Message);
Assert.Same(data, e.EnvelopeData);
}
private sealed record OpenApiLike(int Code, string Msg) : IErrorCode;
}
@@ -0,0 +1,6 @@
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Threading;
global using System.Threading.Tasks;
global using Yh.Platform.Abstractions;
@@ -0,0 +1,130 @@
using System.IO;
using System.Text.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using JsonSerializer = System.Text.Json.JsonSerializer;
using Xunit;
using Yh.Platform.Abstractions;
namespace Yh.Platform.Abstractions.Tests;
public class ModelSerializationTests
{
private static JObject LoadFixtureBareData()
{
var path = Path.Combine(AppContext.BaseDirectory, "fixtures", "envelope-success.json");
return JObject.Parse(File.ReadAllText(path))["bareData"]!.Value<JObject>()!;
}
private static JObject LoadFixtureWrapped()
{
var path = Path.Combine(AppContext.BaseDirectory, "fixtures", "envelope-success.json");
return JObject.Parse(File.ReadAllText(path))["wrappedData"]!.Value<JObject>()!;
}
[Fact]
public void Newtonsoft_反序列化fixture_含currentRole嵌套()
{
var user = LoadFixtureBareData().ToObject<BoaUser>(new Newtonsoft.Json.JsonSerializer
{
// net5 消费方形态:ASP.NET Core AddNewtonsoftJson 默认大小写不敏感、保留属性名
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
});
Assert.Equal(1001, user!.Id);
Assert.Equal("zhangsan", user.Account);
Assert.Equal("张三", user.Name);
Assert.Equal(2, user.School!.Id);
Assert.Equal("云海第一中学", user.School.Name);
Assert.Equal(3, user.CurrentRole!.RoleId);
Assert.Equal("教师", user.CurrentRole.RoleName);
Assert.Equal("云海第一中学", user.CurrentRole.SchName);
Assert.Equal(21, user.CurrentRole.ChildId);
Assert.Equal(2, user.Schools!.Count);
Assert.Equal(new List<int> { 3, 5 }, user.RoleIds);
Assert.Equal(3, user.Groups!.Count);
}
[Fact]
public void Newtonsoft_默认设置_大小写不敏感匹配camelCase()
{
var user = LoadFixtureBareData().ToObject<BoaUser>();
Assert.Equal("zhangsan", user!.Account);
Assert.Equal("教师", user.CurrentRole!.RoleName);
}
[Fact]
public void SystemTextJson_Web序列化_与fixture逐节点一致()
{
var user = LoadFixtureBareData().ToObject<BoaUser>();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
// 对齐 Java 侧 NON_NULL 语义
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
};
var json = JsonSerializer.Serialize(user, options);
// 语义等价:键序无关、null 节点差异容忍(fixture 中显式 null 与省略 null 视为一致)
Assert.True(JToken.DeepEquals(JObject.Parse(json), StripNulls(LoadFixtureBareData())),
"STJ 序列化结果应与 fixture 语义一致\n" + json);
}
private static JObject StripNulls(JObject obj)
{
var clone = (JObject)obj.DeepClone();
var toRemove = new List<JProperty>();
CollectNullProps(clone, toRemove);
foreach (var p in toRemove)
{
p.Remove();
}
return clone;
}
private static void CollectNullProps(JToken token, List<JProperty> collected)
{
foreach (var child in token.Children())
{
if (child is JProperty p)
{
if (p.Value.Type == JTokenType.Null)
{
collected.Add(p);
continue;
}
CollectNullProps(p.Value, collected);
}
else if (child.Type is JTokenType.Object or JTokenType.Array)
{
CollectNullProps(child, collected);
}
}
}
[Fact]
public void SystemTextJson_反序列化信封包裹成功样例()
{
var wrapped = LoadFixtureWrapped();
var envelope = wrapped.ToObject<ApiEnvelope<BoaUser>>(new Newtonsoft.Json.JsonSerializer
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
});
Assert.True(envelope!.IsSuccess);
Assert.Equal(0, envelope.Code);
Assert.Equal(1001, envelope.Data!.Id);
}
[Fact]
public void BoaApp_Models_双序列化往返()
{
var app = new BoaApp { Id = 7, Name = "作业提交", Clients = (int)ClientEnum.PcBrowser | (int)ClientEnum.MobileApp, IsPublic = false, BuiltInTag = 0 };
var stj = JsonSerializer.Deserialize<BoaApp>(JsonSerializer.Serialize(app, new JsonSerializerOptions(JsonSerializerDefaults.Web)), new JsonSerializerOptions(JsonSerializerDefaults.Web));
var nf = JObject.Parse(JsonConvert.SerializeObject(app, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() })).ToObject<BoaApp>();
Assert.Equal(app.Name, stj!.Name);
Assert.Equal(app.Clients, stj.Clients);
Assert.Equal(app.Name, nf!.Name);
Assert.Equal(false, nf.IsPublic);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Yh.Platform.Abstractions\Yh.Platform.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\contract\fixtures\**\*.*" CopyToOutputDirectory="PreserveNewest" LinkBase="fixtures" />
</ItemGroup>
</Project>