149 lines
6.9 KiB
C#
149 lines
6.9 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Security.Claims;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Json;
|
|
using Asp.Versioning;
|
|
using MAX.Framework.Commons;
|
|
using MAX.Framework.Conventions;
|
|
using MAX.Framework.Dtos;
|
|
using MAX.Framework.Security;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.TestHost;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Store.Shops.Controllers;
|
|
using Store.Shops.Entities;
|
|
using Store.Shops.Services;
|
|
using Store.Users.Contracts;
|
|
|
|
namespace Store.Shops.Tests;
|
|
|
|
public sealed class ShopHttpTests : IAsyncLifetime
|
|
{
|
|
private readonly ShopTestContext context = new();
|
|
private WebApplication app = null!;
|
|
private HttpClient client = null!;
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = "Testing" });
|
|
builder.WebHost.UseTestServer();
|
|
builder.Logging.ClearProviders();
|
|
builder.Services.AddControllers(options =>
|
|
{
|
|
options.Conventions.Add(new ApiRoutePrefixConvention());
|
|
options.Filters.Add<GlobalExceptionFilter>();
|
|
options.Filters.Add<UnifyResultFilter>();
|
|
}).AddApplicationPart(typeof(ShopController).Assembly).ConfigureApiBehaviorOptions(options =>
|
|
{
|
|
options.InvalidModelStateResponseFactory = _ =>
|
|
new ObjectResult(ApiResult.Fail("参数错误", ResultCode.ValidationError)) { StatusCode = 200 };
|
|
});
|
|
builder.Services.AddApiVersioning(options =>
|
|
{
|
|
options.DefaultApiVersion = new ApiVersion(1, 0);
|
|
options.ApiVersionReader = new UrlSegmentApiVersionReader();
|
|
}).AddMvc();
|
|
builder.Services.AddAuthentication("Test").AddScheme<AuthenticationSchemeOptions, ShopTestAuthentication>("Test", _ => { });
|
|
builder.Services.AddAuthorization();
|
|
builder.Services.AddHttpContextAccessor();
|
|
builder.Services.AddScoped<ICurrentUser, CurrentUser>();
|
|
builder.Services.AddSingleton(context.Db);
|
|
builder.Services.AddSingleton(context.Repository);
|
|
builder.Services.AddSingleton<IUserQueryService>(context);
|
|
builder.Services.AddSingleton(context.Configuration);
|
|
builder.Services.AddSingleton(context.Gateway);
|
|
builder.Services.AddTransient<ShopManagementService>();
|
|
builder.Services.AddTransient<ShopService>();
|
|
app = builder.Build();
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.MapControllers();
|
|
await app.StartAsync();
|
|
client = app.GetTestClient();
|
|
client.DefaultRequestHeaders.Add("X-Test-User", "owner");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("GetPddClients")]
|
|
[InlineData("GetPddShopClients")]
|
|
[InlineData("GetPddShopToken")]
|
|
[InlineData("GetAllPddShops")]
|
|
[InlineData("AddWeidianShop")]
|
|
[InlineData("UpdateWeidianShop")]
|
|
[InlineData("UpdateWeidianShopLogin")]
|
|
[InlineData("DeleteWeidianShop")]
|
|
public async Task ActionsOnlyAcceptPost(string action)
|
|
{
|
|
using var response = await client.GetAsync("/api/v1/shop/" + action);
|
|
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
|
|
Assert.Contains("POST", response.Content.Headers.Allow);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ActionsBindDtoAndReturnSingleApiResultEnvelope()
|
|
{
|
|
using var create = await client.PostAsJsonAsync("/api/v1/shop/AddWeidianShop", new { loginName = "login", shopId = "shop", notes = "old" });
|
|
Assert.Equal(HttpStatusCode.OK, create.StatusCode);
|
|
var json = await create.Content.ReadFromJsonAsync<JsonElement>();
|
|
Assert.Equal(200, json.GetProperty("code").GetInt32());
|
|
Assert.Equal(JsonValueKind.Null, json.GetProperty("data").ValueKind);
|
|
using var update = await client.PostAsJsonAsync("/api/v1/shop/UpdateWeidianShop", new { loginName = "login", shopId = "shop", notes = "new" });
|
|
Assert.Equal(200, (await update.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
|
using var cookie = await client.PostAsJsonAsync("/api/v1/shop/UpdateWeidianShopLogin", new { shopId = "shop", cookie = "session" });
|
|
Assert.Equal(200, (await cookie.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
|
var row = Assert.Single(await context.Db.Select<WeidianShopEntity>().ToListAsync());
|
|
Assert.Equal("new", row.Notes);
|
|
Assert.Equal("session", row.Cookie);
|
|
Assert.Equal("owner", row.UserName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidationAndBusinessErrorsReturnHttp200WithFailureCode()
|
|
{
|
|
using var invalid = await client.PostAsJsonAsync("/api/v1/shop/AddWeidianShop", new { });
|
|
Assert.Equal(HttpStatusCode.OK, invalid.StatusCode);
|
|
Assert.Equal(ResultCode.ValidationError, (await invalid.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
|
using var denied = await client.PostAsJsonAsync("/api/v1/shop/GetAllPddShops", new { });
|
|
Assert.Equal(HttpStatusCode.OK, denied.StatusCode);
|
|
Assert.Equal(ResultCode.BusinessError, (await denied.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CredentialResponseIsWrappedAndNotCached_AnonymousCannotRead()
|
|
{
|
|
using var response = await client.PostAsJsonAsync("/api/v1/shop/GetPddClients", new { });
|
|
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
|
|
Assert.Equal(200, result.GetProperty("code").GetInt32());
|
|
Assert.Equal("test-client", result.GetProperty("data")[0].GetProperty("clientId").GetString());
|
|
Assert.True(response.Headers.CacheControl!.NoStore);
|
|
client.DefaultRequestHeaders.Remove("X-Test-User");
|
|
using var anonymous = await client.PostAsJsonAsync("/api/v1/shop/GetPddClients", new { });
|
|
Assert.Equal(HttpStatusCode.Unauthorized, anonymous.StatusCode);
|
|
}
|
|
|
|
public async Task DisposeAsync()
|
|
{
|
|
client.Dispose();
|
|
await app.DisposeAsync();
|
|
context.Dispose();
|
|
}
|
|
}
|
|
|
|
internal sealed class ShopTestAuthentication(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
|
ILoggerFactory logger, UrlEncoder encoder) : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
|
|
{
|
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
|
{
|
|
if (!Request.Headers.TryGetValue("X-Test-User", out var name))
|
|
return Task.FromResult(AuthenticateResult.NoResult());
|
|
var principal = new ClaimsPrincipal(new ClaimsIdentity([new Claim(JwtClaimTypes.UserName, name.ToString())], Scheme.Name));
|
|
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name)));
|
|
}
|
|
}
|