Files
StoreWeb/Tests/Store.Users.Tests/UserTestContext.cs
T

104 lines
4.8 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 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;
}