增加测试
This commit is contained in:
1 parent
6d4629e4ac
commit
497e9f1c34
14 files changed
+700
-74
No files matched your search
@@ -59,7 +59,93 @@ using var cancelled = new CancellationTokenSource();
|
||||
cancelled.Cancel();
|
||||
try { await api.Auth.LoginAsync(new LoginRequest(), cancelled.Token); throw new Exception("取消未生效"); }
|
||||
catch (OperationCanceledException) { }
|
||||
Console.WriteLine("PASS: 登录、路由、Bearer、分页、校验、业务错误、格式错误、403、HTTP/业务401、退出、取消。");
|
||||
// 错误信封的 data 不必符合成功 DTO,仍须保留错误类型与追踪信息。
|
||||
handler.Json = """{"code":200,"data":{"token":"session-four"}}""";
|
||||
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
||||
handler.Json = """{"code":400,"msg":"账号错误","traceId":"error-data","data":[]} """;
|
||||
try
|
||||
{
|
||||
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "wrong" });
|
||||
throw new Exception("错误登录应失败");
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
Check(ex.Kind == ApiErrorKind.Business && ex.Code == 400 && ex.TraceId == "error-data"
|
||||
&& ex.Message == "账号错误", "错误 data 不应掩盖业务错误");
|
||||
}
|
||||
Check(api.Session.IsAuthenticated, "失败登录不应替换原会话");
|
||||
handler.Json = """{"code":200,"data":{"token":"","tokenType":"Bearer"}}""";
|
||||
await Error(async () => await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" }), ApiErrorKind.InvalidResponse);
|
||||
Check(api.Session.IsAuthenticated, "无效 Token 不应替换原会话");
|
||||
handler.Json = """{"code":200,"traceId":"bad-data","data":[]} """;
|
||||
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.InvalidResponse);
|
||||
handler.Json = """{"code":200,"data":null}""";
|
||||
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.InvalidResponse);
|
||||
handler.Json = """{"code":401,"traceId":"expired","data":[]} """;
|
||||
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.Unauthorized);
|
||||
Check(!api.Session.IsAuthenticated, "data 类型不匹配时业务 401 仍应清理会话");
|
||||
|
||||
handler.Json = """{"code":200,"data":{"token":"old-token"}}""";
|
||||
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
||||
TaskCompletionSource arrived = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
TaskCompletionSource release = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
handler.BeforeResponse = async cancellationToken =>
|
||||
{
|
||||
arrived.SetResult();
|
||||
await release.Task.WaitAsync(cancellationToken);
|
||||
};
|
||||
handler.Json = """{"code":401,"data":[]} """;
|
||||
Task<CurrentUser> oldRequest = api.Auth.MeAsync();
|
||||
await arrived.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
handler.BeforeResponse = null;
|
||||
handler.Json = """{"code":200,"data":{"token":"new-token"}}""";
|
||||
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
||||
release.SetResult();
|
||||
await Error(async () => await oldRequest, ApiErrorKind.Unauthorized);
|
||||
Check(api.Session.IsAuthenticated, "旧请求 401 不得清理新会话");
|
||||
handler.Json = """{"code":200,"data":{"userName":"admin"}}""";
|
||||
await api.Auth.MeAsync();
|
||||
Check(handler.Authorization == "Bearer new-token", "重新登录应使用新 Token");
|
||||
|
||||
arrived = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
release = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
handler.BeforeResponse = async cancellationToken =>
|
||||
{
|
||||
arrived.SetResult();
|
||||
await release.Task.WaitAsync(cancellationToken);
|
||||
};
|
||||
handler.Json = """{"code":200,"data":{"token":"last-token"}}""";
|
||||
Task<LoginResult> pendingLogin = api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
||||
await arrived.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
int beforeLogout = handler.Count;
|
||||
Task pendingLogout = api.Auth.LogoutAsync();
|
||||
Check(handler.Count == beforeLogout && !pendingLogout.IsCompleted, "退出必须等待登录完成");
|
||||
handler.BeforeResponse = null;
|
||||
handler.Json = """{"code":200,"data":null}""";
|
||||
release.SetResult();
|
||||
await Task.WhenAll(pendingLogin, pendingLogout).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
Check(!api.Session.IsAuthenticated && handler.Authorization == "Bearer last-token", "退出应撤销刚完成的登录");
|
||||
|
||||
handler.Json = """{"code":200,"data":{"token":"logout-token"}}""";
|
||||
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
||||
handler.Status = HttpStatusCode.InternalServerError;
|
||||
handler.Json = "server-error";
|
||||
await Error(() => api.Auth.LogoutAsync(), ApiErrorKind.Http);
|
||||
Check(!api.Session.IsAuthenticated, "退出失败也必须清理本地会话");
|
||||
handler.Status = HttpStatusCode.OK;
|
||||
handler.BeforeResponse = cancellationToken => Task.Delay(Timeout.Infinite, cancellationToken);
|
||||
using CancellationTokenSource inFlight = new(TimeSpan.FromMilliseconds(100));
|
||||
try
|
||||
{
|
||||
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" }, inFlight.Token);
|
||||
throw new Exception("请求中取消未生效");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Check(!api.Session.IsAuthenticated, "取消登录不得保存会话");
|
||||
}
|
||||
handler.BeforeResponse = null;
|
||||
Console.WriteLine("PASS: 登录、路由、Bearer、分页、校验、错误分类、无效 Token/data、401 会话隔离、登录退出串行、退出失败清理、取消。");
|
||||
|
||||
sealed class StubHandler : HttpMessageHandler
|
||||
{
|
||||
@@ -69,12 +155,19 @@ sealed class StubHandler : HttpMessageHandler
|
||||
public string Path { get; private set; } = "";
|
||||
public string? Authorization { get; private set; }
|
||||
public string? Body { get; private set; }
|
||||
public Func<CancellationToken, Task>? BeforeResponse { get; set; }
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
Count++;
|
||||
Path = request.RequestUri!.AbsolutePath;
|
||||
Authorization = request.Headers.Authorization?.ToString();
|
||||
Body = request.Content is null ? null : await request.Content.ReadAsStringAsync(cancellationToken);
|
||||
return new HttpResponseMessage(Status) { Content = new StringContent(Json, Encoding.UTF8, "application/json") };
|
||||
HttpStatusCode status = Status;
|
||||
string json = Json;
|
||||
if (BeforeResponse is not null)
|
||||
{
|
||||
await BeforeResponse(cancellationToken);
|
||||
}
|
||||
return new HttpResponseMessage(status) { Content = new StringContent(json, Encoding.UTF8, "application/json") };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user