diff --git a/assets/microservices.drawio b/assets/microservices.drawio new file mode 100644 index 0000000..5a0fae2 --- /dev/null +++ b/assets/microservices.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/src/BuildingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BuildingBlocks.csproj index 6c4d765..542d6b7 100644 --- a/src/BuildingBlocks/BuildingBlocks.csproj +++ b/src/BuildingBlocks/BuildingBlocks.csproj @@ -25,6 +25,7 @@ + diff --git a/src/BuildingBlocks/Core/Event/EventType.cs b/src/BuildingBlocks/Core/Event/EventType.cs index 1ec808c..9c0de49 100644 --- a/src/BuildingBlocks/Core/Event/EventType.cs +++ b/src/BuildingBlocks/Core/Event/EventType.cs @@ -4,5 +4,5 @@ namespace BuildingBlocks.Core.Event; public enum EventType { IntegrationEvent = 1, - DomainEvent = 2, + DomainEvent = 2 } diff --git a/src/BuildingBlocks/Core/Event/IDomainEvent.cs b/src/BuildingBlocks/Core/Event/IDomainEvent.cs index 3f3afed..65bc80d 100644 --- a/src/BuildingBlocks/Core/Event/IDomainEvent.cs +++ b/src/BuildingBlocks/Core/Event/IDomainEvent.cs @@ -2,5 +2,4 @@ namespace BuildingBlocks.Core.Event; public interface IDomainEvent : IEvent { - } diff --git a/src/BuildingBlocks/Core/EventDispatcher.cs b/src/BuildingBlocks/Core/EventDispatcher.cs index fb23629..b41f3cb 100644 --- a/src/BuildingBlocks/Core/EventDispatcher.cs +++ b/src/BuildingBlocks/Core/EventDispatcher.cs @@ -30,36 +30,43 @@ public sealed class EventDispatcher : IEventDispatcher _httpContextAccessor = httpContextAccessor; } - public async Task SendAsync(IDomainEvent domainEvent, - CancellationToken cancellationToken = default) => await SendAsync(new[] {domainEvent}, cancellationToken); - public async Task SendAsync(IReadOnlyList domainEvents, CancellationToken cancellationToken = default) + public async Task SendAsync(IReadOnlyList events, CancellationToken cancellationToken = default) + where T : IEvent { - if (domainEvents is null) return; - - var integrationEvents = await MapDomainEventToIntegrationEventAsync(domainEvents).ConfigureAwait(false); - - if (integrationEvents.Count == 0) return; - - foreach (var integrationEvent in integrationEvents) + async Task PublishIntegrationEvent(IReadOnlyList integrationEvents) { - await _persistMessageProcessor.PublishMessageAsync(new MessageEnvelope(integrationEvent, SetHeaders()), - cancellationToken); + foreach (var integrationEvent in integrationEvents) + { + await _persistMessageProcessor.PublishMessageAsync(new MessageEnvelope(integrationEvent, SetHeaders()), + cancellationToken); + } + } + + if (events.Count > 0) + { + switch (events) + { + case IReadOnlyList domainEvents: + { + var integrationEvents = await MapDomainEventToIntegrationEventAsync(domainEvents) + .ConfigureAwait(false); + + await PublishIntegrationEvent(integrationEvents); + break; + } + + case IReadOnlyList integrationEvents: + await PublishIntegrationEvent(integrationEvents); + break; + } } } + public async Task SendAsync(T @event, CancellationToken cancellationToken = default) + where T : IEvent => + await SendAsync(new[] {@event}, cancellationToken); - public async Task SendAsync(IIntegrationEvent integrationEvent, - CancellationToken cancellationToken = default) => await SendAsync(new[] {integrationEvent}, cancellationToken); - - public async Task SendAsync(IReadOnlyList integrationEvents, - CancellationToken cancellationToken = default) - { - if (integrationEvents is null) return; - - await _persistMessageProcessor.PublishMessageAsync(new MessageEnvelope(integrationEvents, SetHeaders()), - cancellationToken); - } private Task> MapDomainEventToIntegrationEventAsync( IReadOnlyList events) diff --git a/src/BuildingBlocks/Core/IEventDispatcher.cs b/src/BuildingBlocks/Core/IEventDispatcher.cs index 348fd10..44a4f94 100644 --- a/src/BuildingBlocks/Core/IEventDispatcher.cs +++ b/src/BuildingBlocks/Core/IEventDispatcher.cs @@ -4,9 +4,8 @@ namespace BuildingBlocks.Core; public interface IEventDispatcher { - public Task SendAsync(IReadOnlyList domainEvents, CancellationToken cancellationToken = default); - public Task SendAsync(IDomainEvent domainEvent, CancellationToken cancellationToken = default); - - public Task SendAsync(IIntegrationEvent integrationEvent, CancellationToken cancellationToken = default); - public Task SendAsync(IReadOnlyList integrationEvents, CancellationToken cancellationToken = default); + public Task SendAsync(IReadOnlyList events, CancellationToken cancellationToken = default) + where T : IEvent; + public Task SendAsync(T @event, CancellationToken cancellationToken = default) + where T : IEvent; } diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index 4a84a39..5226651 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -1,6 +1,7 @@ using System.Data; using System.Text.Json; using BuildingBlocks.Core; +using BuildingBlocks.Core.Event; using MediatR; using Microsoft.Extensions.Logging; diff --git a/src/BuildingBlocks/MessageProcessor/Extensions.cs b/src/BuildingBlocks/MessageProcessor/Extensions.cs index d7329ae..f2c8fab 100644 --- a/src/BuildingBlocks/MessageProcessor/Extensions.cs +++ b/src/BuildingBlocks/MessageProcessor/Extensions.cs @@ -1,4 +1,5 @@ using BuildingBlocks.Core; +using BuildingBlocks.Mongo; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/src/BuildingBlocks/Mongo/MongoDbContext.cs b/src/BuildingBlocks/Mongo/MongoDbContext.cs index 1c59a41..4ef4e5b 100644 --- a/src/BuildingBlocks/Mongo/MongoDbContext.cs +++ b/src/BuildingBlocks/Mongo/MongoDbContext.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Bson.Serialization.Conventions; using MongoDB.Driver; @@ -13,12 +14,12 @@ public class MongoDbContext : IMongoDbContext public IMongoClient MongoClient { get; } protected readonly IList> _commands; - public MongoDbContext(MongoOptions options) + public MongoDbContext(IOptions options) { RegisterConventions(); - MongoClient = new MongoClient(options.ConnectionString); - var databaseName = options.DatabaseName; + MongoClient = new MongoClient(options.Value.ConnectionString); + var databaseName = options.Value.DatabaseName; Database = MongoClient.GetDatabase(databaseName); // Every command will be stored and it'll be processed at SaveChanges diff --git a/src/Services/Booking/src/Booking/Data/BookingDbContext.cs b/src/Services/Booking/src/Booking/Data/BookingDbContext.cs index 9a1c9b9..14b59b9 100644 --- a/src/Services/Booking/src/Booking/Data/BookingDbContext.cs +++ b/src/Services/Booking/src/Booking/Data/BookingDbContext.cs @@ -9,7 +9,7 @@ public class BookingDbContext : AppDbContextBase { public const string DefaultSchema = "dbo"; - public BookingDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor) : base(options, httpContextAccessor) + public BookingDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor) : base(options, httpContextAccessor) { } diff --git a/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs b/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs index 0c16d0b..89f3213 100644 --- a/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs +++ b/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs @@ -24,13 +24,6 @@ public class CreateBookingTests public CreateBookingTests(IntegrationTestFixture fixture) { _fixture = fixture; - - _fixture.RegisterTestServices(services => - { - MockFlightGrpcServices(services); - MockPassengerGrpcServices(services); - }); - _testHarness = fixture.TestHarness; _channel = fixture.Channel; } @@ -48,36 +41,4 @@ public class CreateBookingTests // Assert response.Should().BeGreaterOrEqualTo(0); } - - private void MockPassengerGrpcServices(IServiceCollection services) - { - services.Replace(ServiceDescriptor.Singleton(x => - { - var mock = Substitute.For(); - mock.GetById(Arg.Any()) - .Returns(new UnaryResult(new FakePassengerResponseDto().Generate())); - - return mock; - })); - } - - private void MockFlightGrpcServices(IServiceCollection services) - { - services.Replace(ServiceDescriptor.Singleton(x => - { - var mock = Substitute.For(); - - mock.GetById(Arg.Any()) - .Returns(new UnaryResult(Task.FromResult(new FakeFlightResponseDto().Generate()))); - - mock.GetAvailableSeats(Arg.Any()) - .Returns( - new UnaryResult>(Task.FromResult(FakeSeatsResponseDto.Generate()))); - - mock.ReserveSeat(new FakeReserveSeatRequestDto().Generate()) - .Returns(new UnaryResult(Task.FromResult(FakeSeatsResponseDto.Generate().First()))); - - return mock; - })); - } } diff --git a/src/Services/Booking/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Booking/tests/IntegrationTest/IntegrationTestFixture.cs index b1e8679..0ee813c 100644 --- a/src/Services/Booking/tests/IntegrationTest/IntegrationTestFixture.cs +++ b/src/Services/Booking/tests/IntegrationTest/IntegrationTestFixture.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Booking.Data; +using BuildingBlocks.Contracts.Grpc; using BuildingBlocks.Core.Model; using BuildingBlocks.MassTransit; +using BuildingBlocks.Mongo; using BuildingBlocks.Web; using Grpc.Net.Client; +using Integration.Test.Fakes; +using MagicOnion; using MassTransit; using MassTransit.Testing; using MediatR; @@ -16,6 +22,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Mongo2Go; using NSubstitute; using Respawn; using Serilog; @@ -32,10 +40,11 @@ public class FixtureCollection : ICollectionFixture public class IntegrationTestFixture : IAsyncLifetime { private WebApplicationFactory _factory; - public Checkpoint Checkpoint { get; set; } - public Action? TestRegistrationServices { get; set; } - public IServiceProvider ServiceProvider => _factory.Services; - public IConfiguration Configuration => _factory.Services.GetRequiredService(); + private Checkpoint _checkpoint; + private MongoDbRunner _mongoRunner; + private IServiceProvider _serviceProvider; + private Action? _testRegistrationServices; + private IConfiguration _configuration; public HttpClient HttpClient => _factory.CreateClient(); public ITestHarness TestHarness => CreateHarness(); public GrpcChannel Channel => CreateChannel(); @@ -48,66 +57,78 @@ public class IntegrationTestFixture : IAsyncLifetime builder.UseEnvironment("test"); builder.ConfigureServices(services => { - services.ReplaceSingleton(AddHttpContextAccessorMock); - TestRegistrationServices?.Invoke(services); - 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); - }); - }); - - Checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; - - TestRegistrationServices?.Invoke(services); + _testRegistrationServices?.Invoke(services); }); }); + RegisterServices(services => + { + MockFlightGrpcServices(services); + MockPassengerGrpcServices(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(); + + _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 void RegisterServices(Action services) => _testRegistrationServices = services; + public virtual async Task DisposeAsync() { - if (!string.IsNullOrEmpty(Configuration?.GetConnectionString("DefaultConnection"))) - await Checkpoint.Reset(Configuration?.GetConnectionString("DefaultConnection")); + if (!string.IsNullOrEmpty(_configuration?.GetConnectionString("DefaultConnection"))) + await _checkpoint.Reset(_configuration?.GetConnectionString("DefaultConnection")); await _factory.DisposeAsync(); + _mongoRunner.Dispose(); } // 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 void RegisterTestServices(Action services) - { - TestRegistrationServices = services; - } - public async Task ExecuteScopeAsync(Func action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); await action(scope.ServiceProvider); } public async Task ExecuteScopeAsync(Func> action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); var result = await action(scope.ServiceProvider); @@ -238,7 +259,7 @@ public class IntegrationTestFixture : IAsyncLifetime private ITestHarness CreateHarness() { - var harness = ServiceProvider.GetTestHarness(); + var harness = _serviceProvider.GetTestHarness(); harness.Start().GetAwaiter().GetResult(); return harness; } @@ -259,4 +280,36 @@ public class IntegrationTestFixture : IAsyncLifetime return httpContextAccessorMock; } + + private void MockPassengerGrpcServices(IServiceCollection services) + { + services.Replace(ServiceDescriptor.Singleton(x => + { + var mock = Substitute.For(); + mock.GetById(Arg.Any()) + .Returns(new UnaryResult(new FakePassengerResponseDto().Generate())); + + return mock; + })); + } + + private void MockFlightGrpcServices(IServiceCollection services) + { + services.Replace(ServiceDescriptor.Singleton(x => + { + var mock = Substitute.For(); + + mock.GetById(Arg.Any()) + .Returns(new UnaryResult(Task.FromResult(new FakeFlightResponseDto().Generate()))); + + mock.GetAvailableSeats(Arg.Any()) + .Returns( + new UnaryResult>(Task.FromResult(FakeSeatsResponseDto.Generate()))); + + mock.ReserveSeat(new FakeReserveSeatRequestDto().Generate()) + .Returns(new UnaryResult(Task.FromResult(FakeSeatsResponseDto.Generate().First()))); + + return mock; + })); + } } diff --git a/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs b/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs index 951394d..1b18e7b 100644 --- a/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs +++ b/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs @@ -8,7 +8,7 @@ namespace Flight.Data; public class FlightReadDbContext : MongoDbContext { - public FlightReadDbContext(IOptions options) : base(options.Value) + public FlightReadDbContext(IOptions options) : base(options) { Flight = GetCollection(nameof(Flight).Underscore()); } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs index aa92e58..e48549f 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs @@ -1,11 +1,15 @@ -using System.Threading; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using Flight.Data; +using Flight.Flights.Exceptions; using Flight.Flights.Models.Reads; using MapsterMapper; using MediatR; +using MongoDB.Driver; +using MongoDB.Driver.Linq; namespace Flight.Flights.Features.CreateFlight.Reads; @@ -28,6 +32,12 @@ public class CreateFlightMongoCommandHandler : ICommandHandler(command); + var flight = await _flightReadDbContext.Flight.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == command.Id, cancellationToken); + + if (flight is not null) + throw new FlightAlreadyExistException(); + await _flightReadDbContext.Flight.InsertOneAsync(flightReadModel, cancellationToken: cancellationToken); return Unit.Value; diff --git a/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs index 5010761..89ec2c1 100644 --- a/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs +++ b/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs @@ -2,12 +2,10 @@ using System.Net.Http; using System.Threading.Tasks; using BuildingBlocks.Core.Model; -using BuildingBlocks.EFCore; using BuildingBlocks.MassTransit; -using BuildingBlocks.MessageProcessor; +using BuildingBlocks.Mongo; using BuildingBlocks.Web; using Flight.Data; -using FluentAssertions.Common; using Grpc.Net.Client; using MassTransit; using MassTransit.Testing; @@ -15,11 +13,10 @@ using MediatR; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Mongo2Go; using NSubstitute; using Respawn; using Serilog; @@ -35,15 +32,76 @@ public class FixtureCollection : ICollectionFixture public class IntegrationTestFixture : IAsyncLifetime { + private Checkpoint _checkpoint; + private IConfiguration _configuration; private WebApplicationFactory _factory; - public Checkpoint Checkpoint { get; set; } - public Action? TestRegistrationServices { get; set; } - public IServiceProvider ServiceProvider => _factory.Services; - public IConfiguration Configuration => _factory.Services.GetRequiredService(); + private MongoDbRunner _mongoRunner; + private IServiceProvider _serviceProvider; + private Action? _testRegistrationServices; public HttpClient HttpClient => _factory.CreateClient(); public ITestHarness TestHarness => CreateHarness(); public GrpcChannel Channel => CreateChannel(); + + public virtual 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(); + + _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 virtual async Task DisposeAsync() + { + if (!string.IsNullOrEmpty(_configuration?.GetConnectionString("DefaultConnection"))) + await _checkpoint.Reset(_configuration?.GetConnectionString("DefaultConnection")); + + await _factory.DisposeAsync(); + _mongoRunner.Dispose(); + } + + public void RegisterServices(Action services) + { + _testRegistrationServices = services; + } + // ref: https://github.com/trbenning/serilog-sinks-xunit public ILogger CreateLogger(ITestOutputHelper output) { @@ -57,59 +115,15 @@ public class IntegrationTestFixture : IAsyncLifetime return null; } - public void RegisterTestServices(Action services) => TestRegistrationServices = services; - - public virtual Task InitializeAsync() - { - _factory = new WebApplicationFactory() - .WithWebHostBuilder(builder => - { - builder.UseEnvironment("test"); - builder.ConfigureServices(services => - { - TestRegistrationServices?.Invoke(services); - 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); - }); - }); - - Checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; - - TestRegistrationServices?.Invoke(services); - }); - }); - - return Task.CompletedTask; - } - - public virtual async Task DisposeAsync() - { - if (!string.IsNullOrEmpty(Configuration?.GetConnectionString("DefaultConnection"))) - await Checkpoint.Reset(Configuration?.GetConnectionString("DefaultConnection")); - - await _factory.DisposeAsync(); - } - public async Task ExecuteScopeAsync(Func action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); await action(scope.ServiceProvider); } public async Task ExecuteScopeAsync(Func> action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); var result = await action(scope.ServiceProvider); @@ -240,7 +254,7 @@ public class IntegrationTestFixture : IAsyncLifetime private ITestHarness CreateHarness() { - var harness = ServiceProvider.GetTestHarness(); + var harness = _serviceProvider.GetTestHarness(); harness.Start().GetAwaiter().GetResult(); return harness; } diff --git a/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs index da891f6..cbce2d3 100644 --- a/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs +++ b/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs @@ -3,6 +3,7 @@ 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; @@ -14,8 +15,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Mongo2Go; using NSubstitute; using Respawn; using Serilog; @@ -31,15 +32,17 @@ public class FixtureCollection : ICollectionFixture public class IntegrationTestFixture : IAsyncLifetime { + private Checkpoint _checkpoint; + private IConfiguration _configuration; private WebApplicationFactory _factory; - public Checkpoint Checkpoint { get; set; } - public Action? TestRegistrationServices { get; set; } - public IServiceProvider ServiceProvider => _factory.Services; - public IConfiguration Configuration => _factory.Services.GetRequiredService(); + private MongoDbRunner _mongoRunner; + private IServiceProvider _serviceProvider; + private Action? _testRegistrationServices; public HttpClient HttpClient => _factory.CreateClient(); public ITestHarness TestHarness => CreateHarness(); public GrpcChannel Channel => CreateChannel(); + public virtual Task InitializeAsync() { _factory = new WebApplicationFactory() @@ -48,66 +51,79 @@ public class IntegrationTestFixture : IAsyncLifetime builder.UseEnvironment("test"); builder.ConfigureServices(services => { - services.ReplaceSingleton(AddHttpContextAccessorMock); - TestRegistrationServices?.Invoke(services); - 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); - }); - }); - - Checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; - - TestRegistrationServices?.Invoke(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(); + + _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 virtual async Task DisposeAsync() { - if (!string.IsNullOrEmpty(Configuration?.GetConnectionString("DefaultConnection"))) - await Checkpoint.Reset(Configuration?.GetConnectionString("DefaultConnection")); + if (!string.IsNullOrEmpty(_configuration?.GetConnectionString("DefaultConnection"))) + await _checkpoint.Reset(_configuration?.GetConnectionString("DefaultConnection")); await _factory.DisposeAsync(); + _mongoRunner.Dispose(); + } + + 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 void RegisterTestServices(Action services) - { - TestRegistrationServices = services; - } - public async Task ExecuteScopeAsync(Func action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); await action(scope.ServiceProvider); } public async Task ExecuteScopeAsync(Func> action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); var result = await action(scope.ServiceProvider); @@ -238,7 +254,7 @@ public class IntegrationTestFixture : IAsyncLifetime private ITestHarness CreateHarness() { - var harness = ServiceProvider.GetTestHarness(); + var harness = _serviceProvider.GetTestHarness(); harness.Start().GetAwaiter().GetResult(); return harness; } diff --git a/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs b/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs index 007f2c9..f2c48ea 100644 --- a/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs +++ b/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs @@ -9,7 +9,7 @@ public sealed class PassengerDbContext : AppDbContextBase { public const string DefaultSchema = "dbo"; - public PassengerDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor) : base(options, httpContextAccessor) + public PassengerDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor) : base(options, httpContextAccessor) { } diff --git a/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs index 11ef517..3e00083 100644 --- a/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs +++ b/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs @@ -3,6 +3,7 @@ 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 MassTransit; @@ -13,8 +14,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Mongo2Go; using NSubstitute; using Passenger.Data; using Respawn; @@ -31,15 +32,17 @@ public class FixtureCollection : ICollectionFixture public class IntegrationTestFixture : IAsyncLifetime { + private Checkpoint _checkpoint; + private IConfiguration _configuration; private WebApplicationFactory _factory; - public Checkpoint Checkpoint { get; set; } - public Action? TestRegistrationServices { get; set; } - public IServiceProvider ServiceProvider => _factory.Services; - public IConfiguration Configuration => _factory.Services.GetRequiredService(); + private MongoDbRunner _mongoRunner; + private IServiceProvider _serviceProvider; + private Action? _testRegistrationServices; public HttpClient HttpClient => _factory.CreateClient(); public ITestHarness TestHarness => CreateHarness(); public GrpcChannel Channel => CreateChannel(); + public virtual Task InitializeAsync() { _factory = new WebApplicationFactory() @@ -48,66 +51,79 @@ public class IntegrationTestFixture : IAsyncLifetime builder.UseEnvironment("test"); builder.ConfigureServices(services => { - services.ReplaceSingleton(AddHttpContextAccessorMock); - TestRegistrationServices?.Invoke(services); - 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); - }); - }); - - Checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; - - TestRegistrationServices?.Invoke(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(); + + _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 virtual async Task DisposeAsync() { - if (!string.IsNullOrEmpty(Configuration?.GetConnectionString("DefaultConnection"))) - await Checkpoint.Reset(Configuration?.GetConnectionString("DefaultConnection")); + if (!string.IsNullOrEmpty(_configuration?.GetConnectionString("DefaultConnection"))) + await _checkpoint.Reset(_configuration?.GetConnectionString("DefaultConnection")); await _factory.DisposeAsync(); + _mongoRunner.Dispose(); + } + + 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 void RegisterTestServices(Action services) - { - TestRegistrationServices = services; - } - public async Task ExecuteScopeAsync(Func action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); await action(scope.ServiceProvider); } public async Task ExecuteScopeAsync(Func> action) { - using var scope = ServiceProvider.CreateScope(); + using var scope = _serviceProvider.CreateScope(); var result = await action(scope.ServiceProvider); @@ -238,7 +254,7 @@ public class IntegrationTestFixture : IAsyncLifetime private ITestHarness CreateHarness() { - var harness = ServiceProvider.GetTestHarness(); + var harness = _serviceProvider.GetTestHarness(); harness.Start().GetAwaiter().GetResult(); return harness; }