81 lines
4.5 KiB
C#
81 lines
4.5 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using System.Net;
|
|
using System.Text;
|
|
using Store.Management.Api;
|
|
using Store.Management.Api.Authentication;
|
|
using Store.Management.Api.Transport;
|
|
using Store.Management.Api.Users;
|
|
|
|
static void Check(bool condition, string message)
|
|
{
|
|
if (!condition) throw new Exception(message);
|
|
}
|
|
static async Task Error(Func<Task> action, ApiErrorKind kind)
|
|
{
|
|
try { await action(); }
|
|
catch (ApiException ex) { Check(ex.Kind == kind, $"预期 {kind},实际 {ex.Kind}"); return; }
|
|
throw new Exception($"未抛出 {kind}");
|
|
}
|
|
var handler = new StubHandler();
|
|
using var http = new HttpClient(handler);
|
|
using var api = new StoreApiClient(new ApiClientOptions { BaseUrl = new Uri("https://test.invalid/root/") }, http);
|
|
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.Unauthorized);
|
|
Check(handler.Count == 0, "未登录不应发送请求");
|
|
handler.Json = """{"code":200,"data":{"token":"session-one","tokenType":"Bearer","expiresInMinutes":100}}""";
|
|
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
|
Check(handler.Path == "/root/v1/Auth/Login" && handler.Authorization is null, "登录路由或匿名认证错误");
|
|
handler.Json = """{"code":200,"data":{"total":1,"items":[{"id":3,"userName":"alice"}]}}""";
|
|
var page = await api.Users.SearchAsync(new UserSearchRequest { Page = 2, PageSize = 10 });
|
|
Check(page.Total == 1 && page.Items[0].UserName == "alice", "分页解析失败");
|
|
Check(handler.Authorization == "Bearer session-one" && handler.Body!.Contains("\"page\":2"), "Token 或分页请求体错误");
|
|
var count = handler.Count;
|
|
try { await api.Users.SearchAsync(new UserSearchRequest { Page = 0 }); throw new Exception("未校验分页"); }
|
|
catch (ValidationException) { Check(handler.Count == count, "无效参数不应发送"); }
|
|
handler.Json = """{"code":2001,"msg":"业务失败","traceId":"trace-1","data":null}""";
|
|
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.Business);
|
|
Check(api.Session.IsAuthenticated, "业务失败不应退出会话");
|
|
handler.Json = "not-json";
|
|
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.InvalidResponse);
|
|
handler.Status = HttpStatusCode.Forbidden;
|
|
handler.Json = """{"code":403,"msg":"无权限","data":null}""";
|
|
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.Http);
|
|
Check(api.Session.IsAuthenticated, "403 不应清除会话");
|
|
handler.Status = HttpStatusCode.OK;
|
|
handler.Json = """{"code":401,"msg":"已失效","data":null}""";
|
|
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.Unauthorized);
|
|
Check(!api.Session.IsAuthenticated, "业务401应清理会话");
|
|
handler.Json = """{"code":200,"data":{"token":"session-two"}}""";
|
|
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
|
handler.Status = HttpStatusCode.Unauthorized;
|
|
await Error(async () => await api.Auth.MeAsync(), ApiErrorKind.Unauthorized);
|
|
Check(!api.Session.IsAuthenticated, "HTTP401应清理会话");
|
|
handler.Status = HttpStatusCode.OK;
|
|
handler.Json = """{"code":200,"data":{"token":"session-three"}}""";
|
|
await api.Auth.LoginAsync(new LoginRequest { UserName = "admin", Password = "secret" });
|
|
handler.Json = """{"code":200,"data":null}""";
|
|
await api.Auth.LogoutAsync();
|
|
Check(!api.Session.IsAuthenticated && handler.Path.EndsWith("Auth/Logout"), "退出失败");
|
|
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、退出、取消。");
|
|
|
|
sealed class StubHandler : HttpMessageHandler
|
|
{
|
|
public string Json { get; set; } = "";
|
|
public HttpStatusCode Status { get; set; } = HttpStatusCode.OK;
|
|
public int Count { get; private set; }
|
|
public string Path { get; private set; } = "";
|
|
public string? Authorization { get; private set; }
|
|
public string? Body { get; private 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") };
|
|
}
|
|
}
|