160 lines
6.4 KiB
C#
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));
|
|
}
|
|
}
|