Files
yunhai.netcore.tool/BasePlatformDotnet/tests/Yh.SchClass.OpenApi.Tests/SchClassResponseParsingTests.cs
T
2026-09-02 09:38:45 +08:00

193 lines
6.6 KiB
C#

namespace Yh.SchClass.OpenApi.Tests;
/// <summary>
/// 响应解析与状态码归一(教务 OA 兼容端点约定):
/// 成功返回裸数据;404 / 204 表示"无数据"归一为 null / 空列表;400 及其它非 2xx 抛异常。
/// </summary>
public class SchClassResponseParsingTests
{
private static readonly SchClassOpenApiOptions Options = new() { BaseHost = "http://base.yunhaixingtong.com" };
private static SchClassVClassClient VClass(FakeHandler handler) => new(new HttpClient(handler), Options);
private static SchClassTeaInfoClient Tea(FakeHandler handler) => new(new HttpClient(handler), Options);
private static SchClassSchoolingClient Schooling(FakeHandler handler) => new(new HttpClient(handler), Options);
[Fact]
public async Task 裸数组_解析为兼容行列表()
{
var handler = FakeHandler.Json(HttpStatusCode.OK, """
[{"Id":1,"Name":"高一(1)班","ClassID":1001,"SchSerID":"S001"},
{"Id":2,"Name":"高一(2)班","ClassID":1002,"SchSerID":"S001"}]
""");
var rows = await VClass(handler).GetVClassListAsync("S001");
Assert.Equal(2, rows.Count);
Assert.Equal("高一(1)班", rows[0]["Name"].GetString());
Assert.Equal(1001, rows[0]["ClassID"].GetInt32());
Assert.Equal("S001", rows[1]["SchSerID"].GetString());
}
[Fact]
public async Task 单对象_归一为单项列表()
{
var handler = FakeHandler.Json(HttpStatusCode.OK, """{"Id":9,"Name":"高三(7)班"}""");
var rows = await VClass(handler).GetVClassListAsync("S001");
Assert.Single(rows);
Assert.Equal(9, rows[0]["Id"].GetInt32());
}
[Fact]
public async Task 裸字符串数组_解析为List_string()
{
var handler = FakeHandler.Json(HttpStatusCode.OK, """["语文","数学"]""");
var subs = await new SchClassSubInfoClient(new HttpClient(handler), Options)
.GetSchTeachSubListAsync("S001");
Assert.Equal(new[] { "语文", "数学" }, subs);
}
[Fact]
public async Task HTTP404_列表归一为空列表()
{
var rows = await VClass(FakeHandler.Empty(HttpStatusCode.NotFound)).GetVClassListAsync("S001");
Assert.Empty(rows);
}
[Fact]
public async Task HTTP404_单对象归一为null()
{
var row = await VClass(FakeHandler.Empty(HttpStatusCode.NotFound)).GetVClassFirstAsync("1001");
Assert.Null(row);
}
[Fact]
public async Task HTTP204_归一为空列表()
{
var handler = FakeHandler.Json(HttpStatusCode.NoContent, "");
var list = await Tea(handler).GetArrTeaInfoAsync("1,2,3");
Assert.Empty(list);
}
[Fact]
public async Task HTTP400_抛异常并携带状态码与响应体()
{
var handler = FakeHandler.Text(HttpStatusCode.BadRequest, "Required String parameter 'userIds' is not present");
var ex = await Assert.ThrowsAsync<SchClassOpenApiException>(() => Tea(handler).GetArrTeaInfoAsync(null!));
Assert.Equal(400, ex.StatusCode);
Assert.Contains("400", ex.Message);
Assert.Contains("userIds", ex.ResponseBody!);
}
[Fact]
public async Task HTTP500_抛异常()
{
var handler = FakeHandler.Text(HttpStatusCode.InternalServerError, "boom");
var ex = await Assert.ThrowsAsync<SchClassOpenApiException>(() => VClass(handler).GetVClassListAsync("S001"));
Assert.Equal(500, ex.StatusCode);
Assert.Contains("500", ex.Message);
}
[Fact]
public async Task 空响应体_返回空列表不抛()
{
var handler = new FakeHandler((_, _) => new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("", Encoding.UTF8, "application/json"),
});
Assert.Empty(await VClass(handler).GetVClassListAsync("S001"));
Assert.Null(await VClass(handler).GetVClassFirstAsync("1"));
}
[Fact]
public async Task JSON字面量null_返回空列表()
{
var handler = FakeHandler.Json(HttpStatusCode.OK, "null");
Assert.Empty(await VClass(handler).GetVClassListAsync("S001"));
}
[Fact]
public async Task 信封code0_提取data节点()
{
var handler = FakeHandler.Json(HttpStatusCode.OK,
"""{"code":0,"msg":"","data":[{"Id":1,"Name":"高一(1)班"}]}""");
var rows = await VClass(handler).GetVClassListAsync("S001");
Assert.Single(rows);
Assert.Equal("高一(1)班", rows[0]["Name"].GetString());
}
[Fact]
public async Task 信封code0且data为null_归一为空()
{
var handler = FakeHandler.Json(HttpStatusCode.OK, """{"code":0,"msg":"ok","data":null}""");
Assert.Empty(await VClass(handler).GetVClassListAsync("S001"));
}
[Fact]
public async Task 信封业务失败_抛异常并携带msg()
{
var handler = FakeHandler.Json(HttpStatusCode.OK,
"""{"code":3,"msg":"学校不存在","data":null}""");
var ex = await Assert.ThrowsAsync<SchClassOpenApiException>(() => VClass(handler).GetVClassListAsync("S001"));
Assert.Equal("学校不存在", ex.Message);
Assert.Equal(0, ex.StatusCode);
Assert.Contains("学校不存在", ex.ResponseBody!);
}
[Fact]
public async Task 业务行含code字段_不被误判为信封()
{
// 学制等级行自身带 code=gradeId 字段,只有 code 而没有 data/msg,必须原样返回
var handler = FakeHandler.Json(HttpStatusCode.OK,
"""[{"Id":1,"Name":"一年级","OwnerId":2,"Levell":1,"code":11,"sx":1}]""");
var rows = await Schooling(handler).GetSchLenLevelListAsync("S001");
Assert.Single(rows);
Assert.Equal(11, rows[0]["code"].GetInt32());
Assert.Equal("一年级", rows[0]["Name"].GetString());
}
[Fact]
public async Task 非法JSON_抛解析异常()
{
var handler = FakeHandler.Json(HttpStatusCode.OK, "<html>502 Bad Gateway</html>");
var ex = await Assert.ThrowsAsync<SchClassOpenApiException>(() => VClass(handler).GetVClassListAsync("S001"));
Assert.Contains("解析失败", ex.Message);
}
[Fact]
public async Task 兼容行键名_大小写与Java侧put顺序一致()
{
var handler = FakeHandler.Json(HttpStatusCode.OK,
"""[{"SchSerID":"S001","TeaTel":"13800000000","TeaName":"王老师","UserTeaSerID":77}]""");
var rows = await new SchClassDbClassClient(new HttpClient(handler), Options).GetSchteaListAsync(1);
Assert.Equal(new[] { "SchSerID", "TeaTel", "TeaName", "UserTeaSerID" }, rows[0].Keys);
Assert.Equal("王老师", rows[0]["TeaName"].GetString());
}
}