Files
pos-system/services/membership-service-net/tests/MembershipService.FunctionalTests/CustomWebApplicationFactory.cs

160 lines
6.4 KiB
C#

using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MembershipService.Domain.AggregatesModel.ExperienceAggregate;
using MembershipService.Domain.AggregatesModel.LevelAggregate;
using MembershipService.Domain.AggregatesModel.MemberAggregate;
using MembershipService.Infrastructure;
using MembershipService.Infrastructure.Repositories;
namespace MembershipService.FunctionalTests;
/// <summary>
/// EN: Custom WebApplicationFactory for functional tests with mock auth.
/// VI: WebApplicationFactory tùy chỉnh cho functional tests với mock auth.
/// </summary>
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private bool _seeded;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
builder.ConfigureServices(services =>
{
// EN: Remove ALL DbContext and EF registrations
// VI: Xóa TẤT CẢ các đăng ký DbContext và EF
var descriptorsToRemove = services.Where(
d => d.ServiceType == typeof(DbContextOptions<MembershipServiceContext>) ||
d.ServiceType == typeof(MembershipServiceContext) ||
d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true ||
d.ServiceType.FullName?.Contains("Npgsql") == true ||
d.ServiceType.FullName?.Contains("DbContext") == true)
.ToList();
foreach (var descriptor in descriptorsToRemove)
{
services.Remove(descriptor);
}
// EN: Add in-memory database for testing with unique name per factory
// VI: Thêm in-memory database để test với tên duy nhất cho mỗi factory
var databaseName = $"TestDb_{Guid.NewGuid()}";
services.AddDbContext<MembershipServiceContext>(options =>
{
options.UseInMemoryDatabase(databaseName);
// EN: Suppress transaction warning since in-memory doesn't support transactions
// VI: Bỏ qua cảnh báo transaction vì in-memory không hỗ trợ transactions
options.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.InMemoryEventId.TransactionIgnoredWarning));
});
// EN: Re-register repositories
// VI: Đăng ký lại repositories
services.AddScoped<IMemberRepository, MemberRepository>();
services.AddScoped<ILevelDefinitionRepository, LevelDefinitionRepository>();
services.AddScoped<IExperienceTransactionRepository, ExperienceTransactionRepository>();
});
builder.ConfigureTestServices(services =>
{
// EN: Override authentication with test scheme
// VI: Override authentication với test scheme
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test";
options.DefaultChallengeScheme = "Test";
options.DefaultScheme = "Test";
})
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });
});
}
/// <summary>
/// EN: Create client with authenticated user.
/// VI: Tạo client với user đã xác thực.
/// </summary>
public HttpClient CreateAuthenticatedClient(Guid? userId = null)
{
var client = CreateClient();
client.DefaultRequestHeaders.Add("X-Test-User-Id", (userId ?? Guid.NewGuid()).ToString());
return client;
}
/// <summary>
/// EN: Seed level definitions into database.
/// VI: Seed level definitions vào database.
/// </summary>
public async Task SeedLevelDefinitionsAsync()
{
if (_seeded) return;
using var scope = Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<MembershipServiceContext>();
if (!await db.LevelDefinitions.AnyAsync())
{
db.LevelDefinitions.AddRange(
new LevelDefinition(1, "Bronze", 0, "Starting level", null, "#CD7F32"),
new LevelDefinition(2, "Silver", 100, "Reach 100 EXP", null, "#C0C0C0"),
new LevelDefinition(3, "Gold", 300, "Reach 300 EXP", null, "#FFD700"),
new LevelDefinition(4, "Platinum", 600, "Reach 600 EXP", null, "#E5E4E2"),
new LevelDefinition(5, "Diamond", 1000, "Reach 1000 EXP", null, "#B9F2FF")
);
await db.SaveChangesAsync();
}
_seeded = true;
}
}
/// <summary>
/// EN: Test authentication handler for functional tests.
/// VI: Test authentication handler cho functional tests.
/// </summary>
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder) : base(options, logger, encoder)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// EN: Check for test user ID header
// VI: Kiểm tra header test user ID
if (!Request.Headers.TryGetValue("X-Test-User-Id", out var userIdValues))
{
return Task.FromResult(AuthenticateResult.Fail("Missing X-Test-User-Id header"));
}
var userId = userIdValues.FirstOrDefault() ?? Guid.NewGuid().ToString();
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim("sub", userId),
new Claim("id", userId),
new Claim(ClaimTypes.Name, "Test User"),
new Claim(ClaimTypes.Email, "test@example.com"),
new Claim(ClaimTypes.Role, "Admin")
};
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}