2026-07-24 12:53:36 +08:00

108 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using BuildFlow.Platform.Domain.Entities.User;
using BuildFlow.Platform.Infrastructure.Persistence;
using BuildFlow.Shared.Domain.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BuildFlow.Platform.Tests;
/// <summary>
/// Platform 模块测试基础设施:InMemory PlatformDbContext 工厂 + JWT 配置构造 + 审计字段填充 helper。
/// </summary>
/// <remarks>
/// 不挂 AuditInterceptor(拦截器依赖 ICurrentUserContext,测试上下文里没有);
/// 约定:测试 seed 实体时调用 <see cref="WithAudit"/> 填充必填审计字段。
/// </remarks>
internal static class PlatformTestDb
{
/// <summary>构造一个独立的 InMemory PlatformDbContext(每个测试唯一库名,避免串扰)。</summary>
/// <remarks>
/// 挂载 SavingChanges 回调自动填充 IAuditable/IHasOperatorIP 字段(替代生产环境的 AuditInterceptor),
/// 这样 handler 内部 new 出来的 RefreshTokenEntity/UserRoleEntity/RolePermissionEntity 等实体也能通过非空校验。
/// </remarks>
public static PlatformDbContext CreateDbContext([System.Runtime.CompilerServices.CallerMemberName] string testName = "")
{
var services = new ServiceCollection();
services.AddDbContext<PlatformDbContext>(o => o.UseInMemoryDatabase($"platform-{testName}-{Guid.NewGuid()}"));
var db = services.BuildServiceProvider().GetRequiredService<PlatformDbContext>();
db.SavingChanges += AutoFillAuditFields;
return db;
}
/// <summary>SavingChanges 回调:自动填充审计字段,模拟生产 AuditInterceptor 行为。</summary>
private static void AutoFillAuditFields(object? sender, EventArgs _)
{
if (sender is not DbContext ctx) return;
foreach (var entry in ctx.ChangeTracker.Entries())
{
if (entry.Entity is IAuditable a)
{
a.CreatedBy ??= "test";
a.UpdatedBy ??= "test";
if (a.CreatedAt == default) a.CreatedAt = DateTime.UtcNow;
if (a.UpdatedAt == default) a.UpdatedAt = DateTime.UtcNow;
}
if (entry.Entity is IHasOperatorIP o)
o.OperatorIP ??= "127.0.0.1";
}
}
/// <summary>构造测试用 IConfiguration(提供 JWT 签发所需的 SigningKey/Issuer/Audience)。</summary>
/// 用字典实现而非 NSubstitute mock:因为 <c>GetValue&lt;T&gt;</c> 是扩展方法,
/// 内部走 ConfigurationBinder 对 this[key] 做类型转换,mock 对未设置的 key 返回空串会导致 Int32 解析失败。
/// 字典实现让未设置的 key 返回 null,使扩展方法回退到默认值。
public static IConfiguration CreateConfig() => new DictionaryConfiguration
{
["Jwt:SigningKey"] = "TestSigningKeyMustBeAtLeast32CharsLong!!",
["Jwt:Issuer"] = "test-issuer",
["Jwt:Audience"] = "test-audience",
["Jwt:AccessTokenMinutes"] = "240",
["Jwt:RefreshTokenDays"] = "7",
};
/// <summary>填充 IFullAudit 实体的必填审计字段,避免 EF 保存时非空校验失败。</summary>
public static T WithAudit<T>(T entity) where T : class
{
var createdBy = entity.GetType().GetProperty("CreatedBy");
var updatedBy = entity.GetType().GetProperty("UpdatedBy");
var operatorIP = entity.GetType().GetProperty("OperatorIP");
var createdAt = entity.GetType().GetProperty("CreatedAt");
var updatedAt = entity.GetType().GetProperty("UpdatedAt");
if (createdBy is not null && createdBy.GetValue(entity) is null or "")
createdBy.SetValue(entity, "test");
if (updatedBy is not null && updatedBy.GetValue(entity) is null or "")
updatedBy.SetValue(entity, "test");
if (operatorIP is not null && operatorIP.GetValue(entity) is null or "")
operatorIP.SetValue(entity, "127.0.0.1");
if (createdAt is not null && (DateTime)createdAt.GetValue(entity)! == default)
createdAt.SetValue(entity, DateTime.UtcNow);
if (updatedAt is not null && (DateTime)updatedAt.GetValue(entity)! == default)
updatedAt.SetValue(entity, DateTime.UtcNow);
return entity;
}
/// <summary>构造一个已持久化的测试用户(含 BCrypt 密码哈希)。</summary>
public static async Task<UserEntity> SeedUserAsync(
PlatformDbContext db,
Guid id,
string username,
string password = "Admin@123",
bool isActive = true,
int permissionVersion = 0)
{
var user = WithAudit(new UserEntity
{
Id = id,
Username = username,
Email = $"{username}@test.local",
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
IsActive = isActive,
PermissionVersion = permissionVersion,
});
db.Users.Add(user);
await db.SaveChangesAsync();
return user;
}
}