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; } }