105 lines
4.9 KiB
C#
105 lines
4.9 KiB
C#
using FreeSql;
|
|
using MAX.Framework.Security;
|
|
using Store.Users.Contracts;
|
|
using Store.Users.Entities;
|
|
using Store.Users.Services;
|
|
using System.Security.Claims;
|
|
using YourNamespace;
|
|
|
|
namespace Store.Users.Tests;
|
|
|
|
/// <summary>每个测试独享数据库和身份上下文,不读取宿主连接配置,可并行运行。</summary>
|
|
internal sealed class UserTestContext : IDisposable
|
|
{
|
|
private readonly string _path = Path.Combine(Path.GetTempPath(), $"store-user-test-{Guid.NewGuid():N}.db");
|
|
public IFreeSql Db { get; }
|
|
public TestCurrentUser CurrentUser { get; } = new();
|
|
public TestOnlineUsers Sessions { get; } = new();
|
|
public IUserQueryService Query { get; }
|
|
public IUserAuthenticationService Authentication { get; }
|
|
public UserManagementService Users { get; }
|
|
public UserFundsService Funds { get; }
|
|
public UserEntity Admin { get; }
|
|
public UserEntity Main { get; }
|
|
public UserEntity Other { get; }
|
|
public UserEntity Child { get; }
|
|
|
|
public UserTestContext()
|
|
{
|
|
Db = new FreeSqlBuilder()
|
|
.UseMappingPriority(FreeSql.Internal.MappingPriorityType.Attribute,
|
|
FreeSql.Internal.MappingPriorityType.Aop, FreeSql.Internal.MappingPriorityType.FluentApi)
|
|
.UseConnectionString(DataType.Sqlite, $"Data Source={_path};Pooling=False")
|
|
.Build();
|
|
// 覆盖业务库 schema,只在临时库建立实体测试表。
|
|
Db.CodeFirst.ConfigEntity<UserEntity>(e => e.Name("users"));
|
|
Db.CodeFirst.ConfigEntity<UserPointsRecordEntity>(e => e.Name("points"));
|
|
Db.CodeFirst.ConfigEntity<UserPaymentRecordEntity>(e => e.Name("payments"));
|
|
Db.CodeFirst.ConfigEntity<ShopEntity>(e => e.Name("shops"));
|
|
Db.CodeFirst.ConfigEntity<OEMEntity>(e => e.Name("oems"));
|
|
Db.CodeFirst.ConfigEntity<UserTopUpMessageEntity>(e => e.Name("messages"));
|
|
Db.CodeFirst.ConfigEntity<UserTopUpQrcodeEntity>(e => e.Name("qrcodes"));
|
|
Db.CodeFirst.SyncStructure(typeof(UserEntity), typeof(UserPointsRecordEntity), typeof(UserPaymentRecordEntity),
|
|
typeof(ShopEntity), typeof(OEMEntity), typeof(UserTopUpMessageEntity), typeof(UserTopUpQrcodeEntity));
|
|
// 模拟部署脚本提供的唯一索引。
|
|
Db.Ado.ExecuteNonQuery("CREATE UNIQUE INDEX ux_user_name ON users(user_name)");
|
|
Admin = Seed("admin", 1);
|
|
Main = Seed("main");
|
|
Other = Seed("other");
|
|
Child = Seed("child", parent: Main.UserName);
|
|
CurrentUser.UserName = Admin.UserName;
|
|
|
|
// 生产实现为 internal;通过反射构造真实仓储和查询服务,无需放宽业务代码可见性。
|
|
var assembly = typeof(UserManagementService).Assembly;
|
|
var repository = Activator.CreateInstance(assembly.GetType("Store.Users.Repositories.UserRepository", true)!, Db)!;
|
|
Query = (IUserQueryService)Activator.CreateInstance(assembly.GetType("Store.Users.Services.UserQueryService", true)!, repository)!;
|
|
Authentication = (IUserAuthenticationService)Activator.CreateInstance(
|
|
assembly.GetType("Store.Users.Services.UserAuthenticationService", true)!, Db, Query)!;
|
|
Users = new(Db, CurrentUser, Query, Sessions);
|
|
Funds = new(Db, CurrentUser);
|
|
}
|
|
|
|
public UserEntity Seed(string name, short level = 0, string parent = "")
|
|
{
|
|
var user = new UserEntity
|
|
{
|
|
UserName = name, NickName = name, Password = "legacy-password", ParentUserName = parent,
|
|
GroupId = string.IsNullOrEmpty(parent) ? (byte)3 : (byte)4, SaLevel = level,
|
|
Mobile = "", CompanyName = "", Notes = "", CardId = "", Authority = "积分用户,订单",
|
|
ForbiddenPage = "", OemId = "", Balance = 0, Points = 0, ShopPointsPerMonth = 30,
|
|
PostagePerOrder = 1m, RefundPostagePerOrder = 1m
|
|
};
|
|
user.Id = checked((int)Db.Insert(user).ExecuteIdentity());
|
|
return user;
|
|
}
|
|
|
|
public Task<UserEntity> Read(string name) => Db.Select<UserEntity>().Where(u => u.UserName == name).FirstAsync();
|
|
|
|
public void Dispose()
|
|
{
|
|
Db.Dispose();
|
|
if (File.Exists(_path)) File.Delete(_path);
|
|
}
|
|
}
|
|
|
|
internal sealed class TestCurrentUser : ICurrentUser
|
|
{
|
|
public bool IsAuthenticated { get; set; } = true;
|
|
public string? UserName { get; set; }
|
|
public string? NickName => UserName;
|
|
public IEnumerable<Claim> Claims => [];
|
|
public string? GetClaimValue(string claimType) => null;
|
|
public T? GetClaimValue<T>(string claimType) => default;
|
|
}
|
|
|
|
internal sealed class TestOnlineUsers : IOnlineUserStore
|
|
{
|
|
public HashSet<string> SignedOut { get; } = [];
|
|
public void SignOutSession(string userName, string jti) => SignedOut.Add(userName);
|
|
public void SignOut(string userName) => SignedOut.Add(userName);
|
|
public void SignIn(OnlineUserInfo info) { }
|
|
public bool IsTokenActive(string userName, string jti) => false;
|
|
public IReadOnlyList<OnlineUserInfo> GetOnlineUsers() => [];
|
|
public int Count => 0;
|
|
}
|