using System; using System.Net.Http; using System.Threading.Tasks; using BuildingBlocks.Core.Model; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; using BuildingBlocks.Web; using Grpc.Net.Client; using Identity.Data; using MassTransit; using MassTransit.Testing; using MediatR; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Mongo2Go; using NSubstitute; using Respawn; using Serilog; using Xunit; using Xunit.Abstractions; namespace Integration.Test; public class IntegrationTestFixture : IAsyncLifetime { private Checkpoint _checkpoint; private IConfiguration _configuration; private WebApplicationFactory _factory; private MongoDbRunner _mongoRunner; private IServiceProvider _serviceProvider; private Action? _testRegistrationServices; public ITestHarness TestHarness { get; private set; } public HttpClient HttpClient { get; private set; } public GrpcChannel Channel { get; private set; } public Task InitializeAsync() { _factory = new WebApplicationFactory() .WithWebHostBuilder(builder => { builder.UseEnvironment("test"); builder.ConfigureServices(services => { _testRegistrationServices?.Invoke(services); }); }); RegisterServices(services => { services.ReplaceSingleton(AddHttpContextAccessorMock); services.AddMassTransitTestHarness(x => { x.UsingRabbitMq((context, cfg) => { var rabbitMqOptions = services.GetOptions("RabbitMq"); var host = rabbitMqOptions.HostName; cfg.Host(host, h => { h.Username(rabbitMqOptions.UserName); h.Password(rabbitMqOptions.Password); }); cfg.ConfigureEndpoints(context); }); }); }); _serviceProvider = _factory.Services; _configuration = _factory.Services.GetRequiredService(); HttpClient = _factory.CreateClient(); Channel = CreateChannel(); TestHarness = CreateHarness(); _checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; _mongoRunner = MongoDbRunner.Start(); var mongoOptions = _factory.Services.GetRequiredService>(); if (mongoOptions.Value.ConnectionString != null) mongoOptions.Value.ConnectionString = _mongoRunner.ConnectionString; return Task.CompletedTask; } public async Task DisposeAsync() { await _checkpoint.Reset(_configuration?.GetConnectionString("DefaultConnection")); _mongoRunner.Dispose(); await _factory.DisposeAsync(); } public void RegisterServices(Action services) { _testRegistrationServices = services; } // ref: https://github.com/trbenning/serilog-sinks-xunit public ILogger CreateLogger(ITestOutputHelper output) { if (output != null) return new LoggerConfiguration() .WriteTo.TestOutput(output) .CreateLogger(); return null; } public async Task ExecuteScopeAsync(Func action) { using var scope = _serviceProvider.CreateScope(); await action(scope.ServiceProvider); } public async Task ExecuteScopeAsync(Func> action) { using var scope = _serviceProvider.CreateScope(); var result = await action(scope.ServiceProvider); return result; } public Task ExecuteDbContextAsync(Func action) { return ExecuteScopeAsync(sp => action(sp.GetService())); } public Task ExecuteDbContextAsync(Func action) { return ExecuteScopeAsync(sp => action(sp.GetService()).AsTask()); } public Task ExecuteDbContextAsync(Func action) { return ExecuteScopeAsync(sp => action(sp.GetService(), sp.GetService())); } public Task ExecuteDbContextAsync(Func> action) { return ExecuteScopeAsync(sp => action(sp.GetService())); } public Task ExecuteDbContextAsync(Func> action) { return ExecuteScopeAsync(sp => action(sp.GetService()).AsTask()); } public Task ExecuteDbContextAsync(Func> action) { return ExecuteScopeAsync(sp => action(sp.GetService(), sp.GetService())); } public Task InsertAsync(params T[] entities) where T : class { return ExecuteDbContextAsync(db => { foreach (var entity in entities) db.Set().Add(entity); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity) where TEntity : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity, TEntity2 entity2) where TEntity : class where TEntity2 : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); db.Set().Add(entity2); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity, TEntity2 entity2, TEntity3 entity3) where TEntity : class where TEntity2 : class where TEntity3 : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); db.Set().Add(entity2); db.Set().Add(entity3); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity, TEntity2 entity2, TEntity3 entity3, TEntity4 entity4) where TEntity : class where TEntity2 : class where TEntity3 : class where TEntity4 : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); db.Set().Add(entity2); db.Set().Add(entity3); db.Set().Add(entity4); return db.SaveChangesAsync(); }); } public Task FindAsync(long id) where T : class, IEntity { return ExecuteDbContextAsync(db => db.Set().FindAsync(id).AsTask()); } public Task SendAsync(IRequest request) { return ExecuteScopeAsync(sp => { var mediator = sp.GetRequiredService(); return mediator.Send(request); }); } public Task SendAsync(IRequest request) { return ExecuteScopeAsync(sp => { var mediator = sp.GetRequiredService(); return mediator.Send(request); }); } private ITestHarness CreateHarness() { var harness = _serviceProvider.GetTestHarness(); return harness; } private GrpcChannel CreateChannel() { return GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = HttpClient}); } private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider) { var httpContextAccessorMock = Substitute.For(); using var scope = serviceProvider.CreateScope(); httpContextAccessorMock.HttpContext = new DefaultHttpContext {RequestServices = scope.ServiceProvider}; httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 6012); httpContextAccessorMock.HttpContext.Request.Scheme = "http"; return httpContextAccessorMock; } }