190 lines
8.9 KiB
C#
190 lines
8.9 KiB
C#
using MAX.Framework.Commons;
|
|
using MAX.Framework.DependencyInjection;
|
|
using MAX.Framework.Mediation;
|
|
using MediatR;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Store.Users.Commands;
|
|
using Store.Users.Contracts.Dtos;
|
|
using Store.Users.Entities;
|
|
using YourNamespace;
|
|
|
|
namespace Store.Users.Tests;
|
|
|
|
/// <summary>管理员、主账号及子账号的管理权限矩阵;资金特权单独验证。</summary>
|
|
public sealed class UserAccessTests
|
|
{
|
|
[Theory]
|
|
[InlineData("admin", "other", false)]
|
|
[InlineData("admin", "child", true)]
|
|
[InlineData("admin", "child", false)]
|
|
[InlineData("main", "main", false)]
|
|
[InlineData("main", "child", false)]
|
|
[InlineData("main", "child", true)]
|
|
public async Task AuthorizedActor_CanUpdateAccount(string actor, string targetName, bool subRoute)
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = actor;
|
|
var target = await context.Read(targetName);
|
|
await context.Users.Update(target.Id, new() { Notes = "updated" }, subRoute, default);
|
|
Assert.Equal("updated", (await context.Read(targetName)).Notes);
|
|
Assert.Contains(targetName, context.Sessions.SignedOut);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("main", "other")]
|
|
[InlineData("child", "main")]
|
|
[InlineData("child", "child")]
|
|
public async Task UnauthorizedActor_CannotUpdateAccount(string actor, string targetName)
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = actor;
|
|
var target = await context.Read(targetName);
|
|
var error = await Assert.ThrowsAsync<AppException>(() => context.Users.Update(target.Id, new() { Notes = "bad" }, false, default));
|
|
Assert.Equal(403, error.Code);
|
|
Assert.Equal("", (await context.Read(targetName)).Notes);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Admin_CanCreateChildForAnotherMainAccount()
|
|
{
|
|
using var context = new UserTestContext();
|
|
var created = await context.Users.Create(new()
|
|
{
|
|
UserName = "new-child", Password = "password", ParentUserName = "other"
|
|
}, true, default);
|
|
Assert.Equal("other", created.ParentUserName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Main_CannotCreateChildForAnotherAccount_OrElevateOwnRole()
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = "main";
|
|
var error = await Assert.ThrowsAsync<AppException>(() => context.Users.Create(new()
|
|
{
|
|
UserName = "new-child", Password = "password", ParentUserName = "other"
|
|
}, true, default));
|
|
Assert.Equal(403, error.Code);
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Users.Update(context.Main.Id,
|
|
new() { SaLevel = 1 }, false, default))).Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("admin", "other")]
|
|
[InlineData("admin", "child")]
|
|
[InlineData("main", "child")]
|
|
public async Task AuthorizedActor_CanResetManagedAccountPassword(string actor, string target)
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = actor;
|
|
await context.Users.ChangePassword(new() { UserName = target, NewPassword = "reset-password" }, default);
|
|
Assert.NotNull(await context.Authentication.Authenticate(target, "reset-password"));
|
|
Assert.Null(await context.Authentication.Authenticate(target, "legacy-password"));
|
|
Assert.Contains(target, context.Sessions.SignedOut);
|
|
Assert.DoesNotContain(actor, context.Sessions.SignedOut);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("main", "other")]
|
|
[InlineData("child", "main")]
|
|
public async Task UnauthorizedActor_CannotResetPassword(string actor, string target)
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = actor;
|
|
var error = await Assert.ThrowsAsync<AppException>(() => context.Users.ChangePassword(new()
|
|
{
|
|
UserName = target, NewPassword = "reset-password"
|
|
}, default));
|
|
Assert.Equal(403, error.Code);
|
|
Assert.True(Store.Users.Services.UserPassword.Verify(await context.Read(target), "legacy-password"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Main_CanCreateReadAndProcessOwnChildsTopUpMessage()
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = "main";
|
|
var id = await context.Users.AddTopUpMessage(new() { UserName = "child", Message = "pending" }, default);
|
|
await context.Users.UpdateTopUpMessage(new() { Id = id, Result = "done" }, default);
|
|
var message = Assert.Single((await context.Users.TopUpMessages(new(), default)).Items);
|
|
Assert.Equal("child", message.UserName);
|
|
Assert.Equal("done", message.Result);
|
|
Assert.Equal(0m, (await context.Read("child")).Points);
|
|
context.CurrentUser.UserName = "other";
|
|
Assert.Empty((await context.Users.TopUpMessages(new(), default)).Items);
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Users.UpdateTopUpMessage(new() { Id = id, Result = "bad" }, default))).Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Main_CannotTakeOverAnAdministratorAccountThroughChildRelationship()
|
|
{
|
|
using var context = new UserTestContext();
|
|
var privilegedChild = context.Seed("privileged-child", 1, "main");
|
|
context.CurrentUser.UserName = "main";
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Users.ChangePassword(new()
|
|
{
|
|
UserName = privilegedChild.UserName, NewPassword = "takeover-password"
|
|
}, default))).Code);
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Users.Update(privilegedChild.Id,
|
|
new() { Notes = "takeover" }, true, default))).Code);
|
|
Assert.True(Store.Users.Services.UserPassword.Verify(await context.Read(privilegedChild.UserName), "legacy-password"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Main_CannotRechargeOwnOrChildAccounts_OrModifyBillingConfiguration()
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = "main";
|
|
foreach (var name in new[] { "main", "child" })
|
|
{
|
|
foreach (bool points in new[] { true, false })
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Funds.Change(new()
|
|
{
|
|
UserName = name, Amount = 10m, Operation = "充值", RequestId = Guid.NewGuid().ToString("N")
|
|
}, points, default))).Code);
|
|
var target = await context.Read(name);
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Users.Update(target.Id,
|
|
new() { ShopPointsPerMonth = 45 }, false, default))).Code);
|
|
}
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => context.Funds.Change(new()
|
|
{
|
|
UserName = "other", Amount = 1m, Operation = "转移", RequestId = "outside"
|
|
}, true, default))).Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("admin", true)]
|
|
[InlineData("main", true)]
|
|
[InlineData("other", false)]
|
|
public async Task ShopRenew_UsesManagedAccountAndAuditsActualActor(string actor, bool allowed)
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.Db.CodeFirst.ConfigEntity<ShopExpireEntity>(e => e.Name("shop_expirations"));
|
|
context.Db.CodeFirst.SyncStructure<ShopExpireEntity>();
|
|
await context.Db.Update<UserEntity>().Set(u => u.Points, 100m).Where(u => u.Id == context.Child.Id).ExecuteAffrowsAsync();
|
|
await context.Db.Insert(new ShopEntity { ShopId = "shop", ShopAccount = "shop-account", UserName = "child" }).ExecuteAffrowsAsync();
|
|
var services = new ServiceCollection();
|
|
services.AddSingleton(context.Db);
|
|
services.AddSingleton<UnitOfWorkExecutionContext>();
|
|
services.AddMediatRAttributedBehaviors(typeof(ShopRenewCommand).Assembly);
|
|
services.AddTransient(typeof(IRequestHandler<ShopRenewCommand, ShopRenewResponse>),
|
|
typeof(ShopRenewCommand).Assembly.GetType("Store.Users.Commands.ShopRenewCommandHandler", true)!);
|
|
using var provider = services.BuildServiceProvider();
|
|
var mediator = provider.GetRequiredService<IMediator>();
|
|
var command = new ShopRenewCommand("child", "shop", 1, OperatorName: actor);
|
|
if (!allowed)
|
|
{
|
|
Assert.Equal(403, (await Assert.ThrowsAsync<AppException>(() => mediator.Send(command))).Code);
|
|
Assert.Equal(100m, (await context.Read("child")).Points);
|
|
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().CountAsync());
|
|
return;
|
|
}
|
|
await mediator.Send(command);
|
|
Assert.Equal(70m, (await context.Read("child")).Points);
|
|
var record = Assert.Single(await context.Db.Select<UserPointsRecordEntity>().ToListAsync());
|
|
Assert.Equal(actor, record.OperatorName);
|
|
Assert.Equal("child", record.UserName);
|
|
Assert.Equal(actor == "admin" ? "管理员" : "用户", record.OperatorType);
|
|
}
|
|
}
|