[CLAUDE] Tests: Chunk E6 — 6 test 2-stage approval (PE) + IdentityFixture helper
Đóng "Tests Phase 3 mini cần UserManager DI helper" defer từ session 8. IdentityFixture (Common/IdentityFixture.cs): - Setup ServiceProvider với Identity stack đầy đủ: - DbContext SQLite shared connection - AddIdentityCore<User> + AddRoles<Role> + AddEntityFrameworkStores - Single shared scope cho fixture lifetime → DbContext + UserManager đồng instance - Helper CreateUserAsync(email, name, deptId, roles, canBypassReview) - Note: dùng Role custom (không phải IdentityRole<Guid>) để match ApplicationDbContext : IdentityDbContext<User, Role, Guid> 6 test PE 2-stage logic (Services/PeTwoStageApprovalTests.cs): - NV_Review_Blocks_Phase_Transition (đóng bug anh Kiệt — chính xác) - TPB_Confirm_After_NV_Review_Allows_Transition (happy path 2-stage) - NV_With_BypassReview_Allows_Transition_With_IsBypassed_True (bypass NV) - Admin_Skips_TwoStage_Logic_Entirely (admin bypass) - Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao (smart reject) - Resume_After_Reject_Jumps_Back_To_RejectedPhase (jump-back logic) Stub FakeNotificationService — best effort path không cần verify. Note: tests cho Contract + Budget 2-stage skip — logic identical PE, ROI thấp. Pattern PeTwoStageApprovalTests reusable nếu cần test riêng tương lai. Total: 54 Domain + 29 Infra (17 codegen + 6 PE WF Application + 6 PE 2-stage) = **83 test pass** (+6 mới). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
113
tests/SolutionErp.Infrastructure.Tests/Common/IdentityFixture.cs
Normal file
113
tests/SolutionErp.Infrastructure.Tests/Common/IdentityFixture.cs
Normal file
@ -0,0 +1,113 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.Identity;
|
||||
using SolutionErp.Infrastructure.Persistence;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Tests.Common;
|
||||
|
||||
// Identity-aware fixture cho tests cần UserManager + RoleManager.
|
||||
//
|
||||
// Tại sao tách khỏi SqliteDbFixture?
|
||||
// - SqliteDbFixture chỉ EF + DbContext (đủ cho code generator tests).
|
||||
// - Service tests cần Identity stack: UserManager.FindByIdAsync, GetRolesAsync,
|
||||
// CreateAsync, AddToRolesAsync — phụ thuộc IUserStore + IRoleStore + hashers
|
||||
// + validators registered qua AddIdentityCore + AddRoles + AddEntityFrameworkStores.
|
||||
// - Single connection mỗi fixture → DB persists across UserManager.Save calls.
|
||||
//
|
||||
// Pattern: ServiceProvider build từ AddIdentityCore. Test gọi GetRequired để
|
||||
// resolve UserManager, DbContext, etc. EnsureCreated() build schema từ model
|
||||
// (skip migrations vì test isolated).
|
||||
public sealed class IdentityFixture : IDisposable
|
||||
{
|
||||
private readonly SqliteConnection _connection;
|
||||
private readonly ServiceProvider _root;
|
||||
public IServiceProvider Services { get; } // scoped to single test scope (shared across tests in class)
|
||||
|
||||
public IdentityFixture()
|
||||
{
|
||||
_connection = new SqliteConnection("DataSource=:memory:");
|
||||
_connection.Open();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
// Manual options + factory để inject TestApplicationDbContext qua type
|
||||
// ApplicationDbContext (Identity EF stores expect base type).
|
||||
var connection = _connection;
|
||||
services.AddScoped<ApplicationDbContext>(_ =>
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseSqlite(connection)
|
||||
.EnableSensitiveDataLogging()
|
||||
.Options;
|
||||
return new TestApplicationDbContext(options);
|
||||
});
|
||||
services.AddScoped<TestApplicationDbContext>(sp =>
|
||||
(TestApplicationDbContext)sp.GetRequiredService<ApplicationDbContext>());
|
||||
services.AddScoped<IApplicationDbContext>(sp =>
|
||||
sp.GetRequiredService<TestApplicationDbContext>());
|
||||
|
||||
services.AddIdentityCore<User>(opt =>
|
||||
{
|
||||
opt.Password.RequireDigit = false;
|
||||
opt.Password.RequireLowercase = false;
|
||||
opt.Password.RequireUppercase = false;
|
||||
opt.Password.RequireNonAlphanumeric = false;
|
||||
opt.Password.RequiredLength = 4;
|
||||
})
|
||||
.AddRoles<Role>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
|
||||
_root = services.BuildServiceProvider();
|
||||
Services = _root.CreateScope().ServiceProvider;
|
||||
|
||||
var ctx = Services.GetRequiredService<TestApplicationDbContext>();
|
||||
ctx.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
// Helper: tạo user + assign roles + gán DepartmentId. Reuse trong nhiều test.
|
||||
public async Task<User> CreateUserAsync(
|
||||
string email,
|
||||
string fullName,
|
||||
Guid? departmentId,
|
||||
string[] roles,
|
||||
bool canBypassReview = false)
|
||||
{
|
||||
var um = Services.GetRequiredService<UserManager<User>>();
|
||||
var rm = Services.GetRequiredService<RoleManager<Role>>();
|
||||
|
||||
// Ensure roles exist (idempotent).
|
||||
foreach (var role in roles)
|
||||
{
|
||||
if (!await rm.RoleExistsAsync(role))
|
||||
await rm.CreateAsync(new Role { Id = Guid.NewGuid(), Name = role });
|
||||
}
|
||||
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserName = email,
|
||||
Email = email,
|
||||
EmailConfirmed = true,
|
||||
FullName = fullName,
|
||||
DepartmentId = departmentId,
|
||||
CanBypassReview = canBypassReview,
|
||||
IsActive = true,
|
||||
};
|
||||
var created = await um.CreateAsync(user, "Test@123");
|
||||
if (!created.Succeeded)
|
||||
throw new InvalidOperationException("CreateUserAsync failed: " + string.Join(",", created.Errors.Select(e => e.Description)));
|
||||
if (roles.Length > 0)
|
||||
await um.AddToRolesAsync(user, roles);
|
||||
return user;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_root.Dispose();
|
||||
_connection.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user