fafafae5d1
- Update CustomWebApplicationFactory to use static database name - Ensure all tests share the same in-memory database instance - This fixes authentication flow tests where registration must persist for login - All 12 integration tests now pass
101 lines
3.4 KiB
C#
101 lines
3.4 KiB
C#
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using RacePlannerApi.Data;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using Xunit;
|
|
|
|
namespace backend.Tests.Integration;
|
|
|
|
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
|
{
|
|
// Use a static database name so all tests in the same process share the database
|
|
private static readonly string _databaseName = $"IntegrationTestDb_{Guid.NewGuid():N}";
|
|
|
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
// Remove all DbContextOptions registrations
|
|
var descriptors = services.Where(
|
|
d => d.ServiceType == typeof(DbContextOptions<RacePlannerDbContext>) ||
|
|
d.ServiceType.Name.Contains("DbContextOptions")).ToList();
|
|
|
|
foreach (var descriptor in descriptors)
|
|
{
|
|
services.Remove(descriptor);
|
|
}
|
|
|
|
// Add in-memory database with consistent name
|
|
services.AddDbContext<RacePlannerDbContext>(options =>
|
|
{
|
|
options.UseInMemoryDatabase(_databaseName);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
public abstract class IntegrationTestBase : IClassFixture<CustomWebApplicationFactory>, IDisposable
|
|
{
|
|
protected readonly CustomWebApplicationFactory _factory;
|
|
protected readonly HttpClient _client;
|
|
protected readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
|
protected readonly IServiceScope _scope;
|
|
protected readonly RacePlannerDbContext _dbContext;
|
|
|
|
protected IntegrationTestBase(CustomWebApplicationFactory factory)
|
|
{
|
|
_factory = factory;
|
|
_client = _factory.CreateClient();
|
|
_scope = _factory.Services.CreateScope();
|
|
_dbContext = _scope.ServiceProvider.GetRequiredService<RacePlannerDbContext>();
|
|
_dbContext.Database.EnsureCreated();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_scope.Dispose();
|
|
}
|
|
|
|
protected HttpClient CreateAuthenticatedClient(string token = "")
|
|
{
|
|
var client = _factory.CreateClient();
|
|
if (!string.IsNullOrEmpty(token))
|
|
{
|
|
client.DefaultRequestHeaders.Authorization =
|
|
new AuthenticationHeaderValue("Bearer", token);
|
|
}
|
|
return client;
|
|
}
|
|
|
|
protected async Task<T?> GetAsync<T>(string url)
|
|
{
|
|
var response = await _client.GetAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
|
}
|
|
|
|
protected async Task<TResponse?> PostAsync<TRequest, TResponse>(string url, TRequest data)
|
|
{
|
|
var response = await _client.PostAsJsonAsync(url, data, _jsonOptions);
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadFromJsonAsync<TResponse>(_jsonOptions);
|
|
}
|
|
|
|
protected async Task<TResponse?> PutAsync<TRequest, TResponse>(string url, TRequest data)
|
|
{
|
|
var response = await _client.PutAsJsonAsync(url, data, _jsonOptions);
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadFromJsonAsync<TResponse>(_jsonOptions);
|
|
}
|
|
|
|
protected async Task DeleteAsync(string url)
|
|
{
|
|
var response = await _client.DeleteAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
}
|