using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using BuildFlow.Platform.Domain.Entities;
using BuildFlow.Platform.Infrastructure.Persistence;
namespace BuildFlow.Platform.Tests;
///
/// Platform 模块测试基础设施:InMemory PlatformDbContext 工厂 + JWT 配置构造 + 审计字段填充 helper。
///
///
/// 照抄 MenuSortTests.BuildInMemoryDb 的 DI 方式(不挂 AuditInterceptor,手动填审计字段)。
/// 不挂拦截器是因为拦截器依赖 ICurrentUserContext/sp.GetRequiredService,测试上下文里没有;
/// 故约定:测试 seed 实体时调用 填充必填审计字段。
///
internal static class PlatformTestDb
{
/// 构造一个独立的 InMemory PlatformDbContext(每个测试唯一库名,避免串扰)。
public static PlatformDbContext CreateDbContext([System.Runtime.CompilerServices.CallerMemberName] string testName = "")
{
var services = new ServiceCollection();
services.AddDbContext(o => o.UseInMemoryDatabase($"platform-{testName}-{Guid.NewGuid()}"));
return services.BuildServiceProvider().GetRequiredService();
}
/// 构造测试用 IConfiguration(提供 JWT 签发所需的 SigningKey/Issuer/Audience)。
public static IConfiguration CreateConfig()
{
var config = Substitute.For();
config["Jwt:SigningKey"] = "TestSigningKeyMustBeAtLeast32CharsLong!!";
config["Jwt:Issuer"] = "test-issuer";
config["Jwt:Audience"] = "test-audience";
config["Jwt:AccessTokenMinutes"] = "240";
config["Jwt:RefreshTokenDays"] = "7";
// GetValue 在 IConfiguration 扩展方法里,NSubstitute 无法直接 mock,LoginCommandHandler 用的是 config.GetValue("Jwt:AccessTokenMinutes", 240)
// 但由于 LoginCommandHandler 实际调用 GetValue 是扩展方法(内部走 this[key] 再 Convert),mock this[key] 即可生效
return config;
}
/// 填充 IFullAudit 实体的必填审计字段,避免 EF 保存时非空校验失败。
public static T WithAudit(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;
}
/// 构造一个已持久化的测试用户(含 BCrypt 密码哈希)。
public static async Task SeedUserAsync(
PlatformDbContext db,
Guid id,
string username,
string password = "Admin@123",
bool isActive = true,
int permissionVersion = 0)
{
var user = WithAudit(new User
{
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;
}
}