diff --git a/.editorconfig b/.editorconfig index 58dba47..7634134 100644 --- a/.editorconfig +++ b/.editorconfig @@ -341,6 +341,10 @@ dotnet_diagnostic.RCS1046.severity = Suggestion # RCS1047: Non-asynchronous method name should not end with 'Async'. dotnet_diagnostic.RCS1047.severity = error +# https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1174.md +# RCS1174: Remove redundant async/await. +dotnet_diagnostic.RCS1174.severity = None + ################################################################################## ## https://github.com/semihokur/asyncfixer ## AsyncFixer01 diff --git a/src/BuildingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BuildingBlocks.csproj index 542d6b7..c4edcd9 100644 --- a/src/BuildingBlocks/BuildingBlocks.csproj +++ b/src/BuildingBlocks/BuildingBlocks.csproj @@ -120,7 +120,8 @@ - + + diff --git a/src/BuildingBlocks/Contracts/Grpc/FlightGrpcContracts.cs b/src/BuildingBlocks/Contracts/Grpc/FlightGrpcContracts.cs index 1ea0e44..afb3170 100644 --- a/src/BuildingBlocks/Contracts/Grpc/FlightGrpcContracts.cs +++ b/src/BuildingBlocks/Contracts/Grpc/FlightGrpcContracts.cs @@ -61,6 +61,8 @@ namespace BuildingBlocks.Contracts.Grpc; public FlightStatus Status { get; init; } [Key(10)] public decimal Price { get; init; } + [Key(11)] + public long FlightId { get; init; } } public enum FlightStatus diff --git a/src/BuildingBlocks/EFCore/AppDbContextBase.cs b/src/BuildingBlocks/EFCore/AppDbContextBase.cs index db1a1cd..e404e50 100644 --- a/src/BuildingBlocks/EFCore/AppDbContextBase.cs +++ b/src/BuildingBlocks/EFCore/AppDbContextBase.cs @@ -24,7 +24,6 @@ public abstract class AppDbContextBase : DbContext, IDbContext protected override void OnModelCreating(ModelBuilder builder) { // ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs - builder.FilterSoftDeletedProperties(); } public async Task BeginTransactionAsync(CancellationToken cancellationToken = default) diff --git a/src/BuildingBlocks/EFCore/Extensions.cs b/src/BuildingBlocks/EFCore/Extensions.cs index 577aefa..a4bf0cd 100644 --- a/src/BuildingBlocks/EFCore/Extensions.cs +++ b/src/BuildingBlocks/EFCore/Extensions.cs @@ -1,10 +1,13 @@ using System.Linq.Expressions; using BuildingBlocks.Core.Model; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace BuildingBlocks.EFCore; @@ -25,11 +28,13 @@ public static class Extensions return services; } - public static IApplicationBuilder UseMigration(this IApplicationBuilder app) + public static IApplicationBuilder UseMigration(this IApplicationBuilder app, IWebHostEnvironment env) where TContext : DbContext, IDbContext { MigrateDatabaseAsync(app.ApplicationServices).GetAwaiter().GetResult(); - SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult(); + + if (!env.IsEnvironment("test")) + SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult(); return app; } diff --git a/src/BuildingBlocks/MessageProcessor/IPersistMessageProcessor.cs b/src/BuildingBlocks/MessageProcessor/IPersistMessageProcessor.cs index d364f99..150ea47 100644 --- a/src/BuildingBlocks/MessageProcessor/IPersistMessageProcessor.cs +++ b/src/BuildingBlocks/MessageProcessor/IPersistMessageProcessor.cs @@ -1,4 +1,5 @@ -using BuildingBlocks.Core.Event; +using System.Linq.Expressions; +using BuildingBlocks.Core.Event; namespace BuildingBlocks.MessageProcessor; @@ -24,6 +25,10 @@ public interface IPersistMessageProcessor CancellationToken cancellationToken = default) where TCommand : class, IInternalCommand; + Task> GetByFilterAsync( + Expression> predicate, + CancellationToken cancellationToken = default); + Task ExistMessageAsync( Guid messageId, CancellationToken cancellationToken = default); diff --git a/src/BuildingBlocks/MessageProcessor/PersistMessageProcessor.cs b/src/BuildingBlocks/MessageProcessor/PersistMessageProcessor.cs index c12bbb7..677c30a 100644 --- a/src/BuildingBlocks/MessageProcessor/PersistMessageProcessor.cs +++ b/src/BuildingBlocks/MessageProcessor/PersistMessageProcessor.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Linq.Expressions; +using System.Text.Json; using Ardalis.GuardClauses; using BuildingBlocks.Core.Event; using BuildingBlocks.EFCore; @@ -50,6 +51,12 @@ public class PersistMessageProcessor : IPersistMessageProcessor cancellationToken); } + public async Task> GetByFilterAsync(Expression> predicate, CancellationToken cancellationToken = default) + { + var b = (await _dbContext.PersistMessages.Where(predicate).ToListAsync(cancellationToken)).AsReadOnly(); + return b; + } + public Task ExistMessageAsync(Guid messageId, CancellationToken cancellationToken = default) { return _dbContext.PersistMessages.FirstOrDefaultAsync(x => diff --git a/src/BuildingBlocks/TestBase/IntegrationTestBase.cs b/src/BuildingBlocks/TestBase/IntegrationTestBase.cs new file mode 100644 index 0000000..c149797 --- /dev/null +++ b/src/BuildingBlocks/TestBase/IntegrationTestBase.cs @@ -0,0 +1,404 @@ +using Ardalis.GuardClauses; +using BuildingBlocks.Core.Model; +using BuildingBlocks.EFCore; +using BuildingBlocks.MassTransit; +using BuildingBlocks.MessageProcessor; +using BuildingBlocks.Mongo; +using BuildingBlocks.Utils; +using BuildingBlocks.Web; +using Grpc.Net.Client; +using MassTransit; +using MassTransit.Testing; +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.Options; +using Mongo2Go; +using NSubstitute; +using Respawn; +using Serilog; +using Xunit; +using Xunit.Abstractions; + +namespace BuildingBlocks.TestBase; + +public class IntegrationTestFixture : IAsyncLifetime + where TEntryPoint : class +{ + private readonly WebApplicationFactory _factory; + private int Timeout => 180; + public Action TestRegistrationServices { set; get; } + public HttpClient HttpClient => _factory.CreateClient(); + public ITestHarness TestHarness => CreateHarness(); + public GrpcChannel Channel => CreateChannel(); + + public IServiceProvider ServiceProvider => _factory.Services; + public IConfiguration Configuration => _factory.Services.GetRequiredService(); + + + public IntegrationTestFixture() + { + _factory = new WebApplicationFactory() + .WithWebHostBuilder(builder => + { + builder.UseEnvironment("test"); + builder.ConfigureServices(services => + { + TestRegistrationServices?.Invoke(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); + }); + }); + }); + }); + } + + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + await _factory.DisposeAsync(); + } + + public virtual 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 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); + }); + } + + // Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/ + public async ValueTask WaitUntilConditionMet(Func> conditionToMet, int? timeoutSecond = null) + { + var time = timeoutSecond ?? Timeout; + + var startTime = DateTime.Now; + var timeoutExpired = false; + var meet = await conditionToMet.Invoke(); + while (!meet) + { + if (timeoutExpired) throw new TimeoutException("Condition not met for the test."); + + await Task.Delay(100); + meet = await conditionToMet.Invoke(); + timeoutExpired = DateTime.Now - startTime > TimeSpan.FromSeconds(time); + } + } + + public async ValueTask ShouldProcessedPersistInternalCommand() + where TInternalCommand : class, IInternalCommand + { + await WaitUntilConditionMet(async () => + { + return await ExecuteScopeAsync(async sp => + { + var persistMessageProcessor = sp.GetService(); + Guard.Against.Null(persistMessageProcessor, nameof(persistMessageProcessor)); + + var filter = await persistMessageProcessor.GetByFilterAsync(x => + x.DeliveryType == MessageDeliveryType.Internal && + TypeProvider.GetTypeName(typeof(TInternalCommand)) == x.DataType); + + var res = filter.Any(x => x.MessageStatus == MessageStatus.Processed); + + return res; + }); + }); + } + + 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; + } +} + +public class IntegrationTestFixture : IntegrationTestFixture + where TEntryPoint : class + where TWContext : DbContext +{ + 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 async Task InsertAsync(TEntity entity) where TEntity : class + { + await 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 class IntegrationTestFixture : IntegrationTestFixture + where TEntryPoint : class + where TWContext : DbContext + where TRContext : MongoDbContext +{ + public Task ExecuteReadContextAsync(Func action) + { + return ExecuteScopeAsync(sp => action(sp.GetRequiredService())); + } + + public Task ExecuteReadContextAsync(Func> action) + { + return ExecuteScopeAsync(sp => action(sp.GetRequiredService())); + } +} + +public class IntegrationTestFixtureCore : IAsyncLifetime + where TEntryPoint : class +{ + private Checkpoint _checkpoint; + private MongoDbRunner _mongoRunner; + + public IntegrationTestFixtureCore(IntegrationTestFixture integrationTestFixture) + { + Fixture = integrationTestFixture; + integrationTestFixture.RegisterServices(services => RegisterTestsServices(services)); + } + + public IntegrationTestFixture Fixture { get; } + + public async Task InitializeAsync() + { + _checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; + + _mongoRunner = MongoDbRunner.Start(); + var mongoOptions = Fixture.ServiceProvider.GetRequiredService>(); + if (mongoOptions.Value.ConnectionString != null) + mongoOptions.Value.ConnectionString = _mongoRunner.ConnectionString; + + await SeedDataAsync(); + } + + public async Task DisposeAsync() + { + await _checkpoint.Reset(Fixture.Configuration?.GetConnectionString("DefaultConnection")); + _mongoRunner.Dispose(); + } + + protected virtual void RegisterTestsServices(IServiceCollection services) + { + } + + private async Task SeedDataAsync() + { + using var scope = Fixture.ServiceProvider.CreateScope(); + + var seeders = scope.ServiceProvider.GetServices(); + foreach (var seeder in seeders) await seeder.SeedAllAsync(); + } +} + +public abstract class IntegrationTestBase : IntegrationTestFixtureCore, + IClassFixture> + where TEntryPoint : class +{ + protected IntegrationTestBase( + IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) + { + Fixture = integrationTestFixture; + } + + public new IntegrationTestFixture Fixture { get; } +} + +public abstract class IntegrationTestBase : IntegrationTestFixtureCore, + IClassFixture> + where TEntryPoint : class + where TWContext : DbContext +{ + protected IntegrationTestBase( + IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) + { + Fixture = integrationTestFixture; + } + + public new IntegrationTestFixture Fixture { get; } +} + +public abstract class IntegrationTestBase : IntegrationTestFixtureCore, + IClassFixture> + where TEntryPoint : class + where TWContext : DbContext + where TRContext : MongoDbContext +{ + protected IntegrationTestBase( + IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) + { + Fixture = integrationTestFixture; + } + + public new IntegrationTestFixture Fixture { get; } +} diff --git a/src/Services/Booking/src/Booking.Api/Program.cs b/src/Services/Booking/src/Booking.Api/Program.cs index 966b789..c5156df 100644 --- a/src/Services/Booking/src/Booking.Api/Program.cs +++ b/src/Services/Booking/src/Booking.Api/Program.cs @@ -69,7 +69,7 @@ if (app.Environment.IsDevelopment()) } app.UseSerilogRequestLogging(); -app.UseMigration(); +app.UseMigration(env); app.UseCorrelationId(); app.UseRouting(); app.UseHttpMetrics(); diff --git a/src/Services/Booking/src/Booking/Booking/Features/CreateBooking/CreateBookingCommand.cs b/src/Services/Booking/src/Booking/Booking/Features/CreateBooking/CreateBookingCommand.cs index d4f250c..d48196e 100644 --- a/src/Services/Booking/src/Booking/Booking/Features/CreateBooking/CreateBookingCommand.cs +++ b/src/Services/Booking/src/Booking/Booking/Features/CreateBooking/CreateBookingCommand.cs @@ -5,6 +5,6 @@ namespace Booking.Booking.Features.CreateBooking; public record CreateBookingCommand(long PassengerId, long FlightId, string Description) : ICommand { - public long Id { get; set; } = SnowFlakIdGenerator.NewId(); + public long Id { get; init; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs b/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs index 1310e99..a407148 100644 --- a/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs +++ b/src/Services/Booking/tests/IntegrationTest/Booking/Features/CreateBookingTests.cs @@ -1,29 +1,30 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Booking.Data; using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.TestBase; using FluentAssertions; -using Grpc.Net.Client; using Integration.Test.Fakes; using MagicOnion; -using MassTransit.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using NSubstitute; using Xunit; namespace Integration.Test.Booking.Features; -public class CreateBookingTests: IClassFixture -{ - private readonly GrpcChannel _channel; - private readonly IntegrationTestFixture _fixture; - private readonly ITestHarness _testHarness; - public CreateBookingTests(IntegrationTestFixture fixture) +public class CreateBookingTests : IntegrationTestBase +{ + public CreateBookingTests(IntegrationTestFixture integrationTestFixture) : base( + integrationTestFixture) { - _fixture = fixture; - _testHarness = fixture.TestHarness; - _channel = fixture.Channel; + } + + protected override void RegisterTestsServices(IServiceCollection services) + { + MockFlightGrpcServices(services); + MockPassengerGrpcServices(services); } // todo: add support test for event-store @@ -34,9 +35,42 @@ public class CreateBookingTests: IClassFixture var command = new FakeCreateBookingCommand().Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // 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 deleted file mode 100644 index 6c7a791..0000000 --- a/src/Services/Booking/tests/IntegrationTest/IntegrationTestFixture.cs +++ /dev/null @@ -1,314 +0,0 @@ -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; -using Microsoft.AspNetCore.Hosting; -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.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 => - { - 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(); - - 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; - } - - 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.Api/Program.cs b/src/Services/Flight/src/Flight.Api/Program.cs index 540ed5d..b771584 100644 --- a/src/Services/Flight/src/Flight.Api/Program.cs +++ b/src/Services/Flight/src/Flight.Api/Program.cs @@ -81,7 +81,7 @@ app.UseSerilogRequestLogging(); app.UseCorrelationId(); app.UseRouting(); app.UseHttpMetrics(); -app.UseMigration(); +app.UseMigration(env); app.UseProblemDetails(); app.UseHttpsRedirection(); app.UseCustomHealthCheck(); diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs index 288ffad..5c6e274 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs @@ -1,5 +1,6 @@ using BuildingBlocks.IdsGenerator; using Flight.Aircrafts.Features.CreateAircraft.Reads; +using Flight.Aircrafts.Models; using Flight.Aircrafts.Models.Reads; using Mapster; @@ -12,5 +13,9 @@ public class AircraftMappings : IRegister config.NewConfig() .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) .Map(d => d.AircraftId, s => s.Id); + + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.AircraftId, s => s.Id); } } diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs index 71f0407..7bdf1bc 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs @@ -7,5 +7,5 @@ namespace Flight.Aircrafts.Features.CreateAircraft; public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : ICommand, IInternalCommand { - public long Id { get; set; } = SnowFlakIdGenerator.NewId(); + public long Id { get; init; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Airports/AirportMappings.cs b/src/Services/Flight/src/Flight/Airports/AirportMappings.cs index daf0e3c..83cb64d 100644 --- a/src/Services/Flight/src/Flight/Airports/AirportMappings.cs +++ b/src/Services/Flight/src/Flight/Airports/AirportMappings.cs @@ -1,5 +1,6 @@ using BuildingBlocks.IdsGenerator; using Flight.Airports.Features.CreateAirport.Reads; +using Flight.Airports.Models; using Flight.Airports.Models.Reads; using Mapster; @@ -12,5 +13,9 @@ public class AirportMappings : IRegister config.NewConfig() .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) .Map(d => d.AirportId, s => s.Id); + + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.AirportId, s => s.Id); } } diff --git a/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs index 77f9eec..88eebae 100644 --- a/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs +++ b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs @@ -7,5 +7,5 @@ namespace Flight.Airports.Features.CreateAirport; public record CreateAirportCommand(string Name, string Address, string Code) : ICommand, IInternalCommand { - public long Id { get; set; } = SnowFlakIdGenerator.NewId(); + public long Id { get; init; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Data/FlightDbContext.cs b/src/Services/Flight/src/Flight/Data/FlightDbContext.cs index dba23ab..33d3386 100644 --- a/src/Services/Flight/src/Flight/Data/FlightDbContext.cs +++ b/src/Services/Flight/src/Flight/Data/FlightDbContext.cs @@ -23,6 +23,7 @@ public sealed class FlightDbContext : AppDbContextBase protected override void OnModelCreating(ModelBuilder builder) { + builder.FilterSoftDeletedProperties(); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(builder); } diff --git a/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs b/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs index a55e634..e602742 100644 --- a/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs +++ b/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using BuildingBlocks.EFCore; using Flight.Aircrafts.Models; +using Flight.Aircrafts.Models.Reads; using Flight.Airports.Models; +using Flight.Airports.Models.Reads; using Flight.Flights.Models; +using Flight.Flights.Models.Reads; using Flight.Seats.Models; +using Flight.Seats.Models.Reads; +using MapsterMapper; using Microsoft.EntityFrameworkCore; namespace Flight.Data.Seed; @@ -13,10 +19,16 @@ namespace Flight.Data.Seed; public class FlightDataSeeder : IDataSeeder { private readonly FlightDbContext _flightDbContext; + private readonly FlightReadDbContext _flightReadDbContext; + private readonly IMapper _mapper; - public FlightDataSeeder(FlightDbContext flightDbContext) + public FlightDataSeeder(FlightDbContext flightDbContext, + FlightReadDbContext flightReadDbContext, + IMapper mapper) { _flightDbContext = flightDbContext; + _flightReadDbContext = flightReadDbContext; + _mapper = mapper; } public async Task SeedAllAsync() @@ -39,6 +51,7 @@ public class FlightDataSeeder : IDataSeeder await _flightDbContext.Airports.AddRangeAsync(airports); await _flightDbContext.SaveChangesAsync(); + await _flightReadDbContext.Airport.InsertManyAsync(_mapper.Map>(airports)); } } @@ -55,6 +68,7 @@ public class FlightDataSeeder : IDataSeeder await _flightDbContext.Aircraft.AddRangeAsync(aircrafts); await _flightDbContext.SaveChangesAsync(); + await _flightReadDbContext.Aircraft.InsertManyAsync(_mapper.Map>(aircrafts)); } } @@ -75,6 +89,7 @@ public class FlightDataSeeder : IDataSeeder await _flightDbContext.Seats.AddRangeAsync(seats); await _flightDbContext.SaveChangesAsync(); + await _flightReadDbContext.Seat.InsertManyAsync(_mapper.Map>(seats)); } } @@ -92,6 +107,7 @@ public class FlightDataSeeder : IDataSeeder }; await _flightDbContext.Flights.AddRangeAsync(flights); await _flightDbContext.SaveChangesAsync(); + await _flightReadDbContext.Flight.InsertManyAsync(_mapper.Map>(flights)); } } } diff --git a/src/Services/Flight/src/Flight/Flights/Dtos/FlightResponseDto.cs b/src/Services/Flight/src/Flight/Flights/Dtos/FlightResponseDto.cs index ecbf1af..f57bc79 100644 --- a/src/Services/Flight/src/Flight/Flights/Dtos/FlightResponseDto.cs +++ b/src/Services/Flight/src/Flight/Flights/Dtos/FlightResponseDto.cs @@ -6,6 +6,7 @@ public record FlightResponseDto { public long Id { get; init; } public string FlightNumber { get; init; } + public long FlightId { get; set; } public long AircraftId { get; init; } public long DepartureAirportId { get; init; } public DateTime DepartureDate { get; init; } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs index dff8e1f..7cae278 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs @@ -10,5 +10,5 @@ public record CreateFlightCommand(string FlightNumber, long AircraftId, long Dep DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand, IInternalCommand { - public long Id { get; set; } = SnowFlakIdGenerator.NewId(); + public long Id { get; init; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs index a655213..1170079 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs @@ -15,16 +15,13 @@ namespace Flight.Flights.Features.CreateFlight; public class CreateFlightCommandHandler : ICommandHandler { private readonly FlightDbContext _flightDbContext; - private readonly IPersistMessageProcessor _persistMessageProcessor; private readonly IMapper _mapper; public CreateFlightCommandHandler(IMapper mapper, - FlightDbContext flightDbContext, - IPersistMessageProcessor persistMessageProcessor) + FlightDbContext flightDbContext) { _mapper = mapper; _flightDbContext = flightDbContext; - _persistMessageProcessor = persistMessageProcessor; } public async Task Handle(CreateFlightCommand command, CancellationToken cancellationToken) diff --git a/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs b/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs index 7a9ec98..7dfcf91 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs @@ -13,10 +13,14 @@ public class FlightMappings : IRegister { public void Register(TypeAdapterConfig config) { - config.NewConfig(); + config.NewConfig() + .Map(d => d.FlightId, s => s.Id); config.NewConfig() .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) .Map(d => d.FlightId, s => s.Id); + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.FlightId, s => s.Id); config.NewConfig() .Map(d => d.FlightId, s => s.Id); config.NewConfig() diff --git a/src/Services/Flight/src/Flight/Flights/Features/GetFlightById/GetFlightByIdQueryHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/GetFlightById/GetFlightByIdQueryHandler.cs index 4dc154f..0a3acd3 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/GetFlightById/GetFlightByIdQueryHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/GetFlightById/GetFlightByIdQueryHandler.cs @@ -27,7 +27,7 @@ public class GetFlightByIdQueryHandler : IQueryHandler x.Id == query.Id, cancellationToken); + await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.FlightId == query.Id, cancellationToken); if (flight is null) throw new FlightNotFountException(); diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs index c1e606d..0857799 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs @@ -40,14 +40,12 @@ public class UpdateFlightMongoCommandHandler : ICommandHandler x.FlightId == flightReadModel.FlightId, Builders.Update - .Set(x => x.Id, flightReadModel.Id) .Set(x => x.Price, flightReadModel.Price) .Set(x => x.ArriveDate, flightReadModel.ArriveDate) .Set(x => x.AircraftId, flightReadModel.AircraftId) .Set(x => x.DurationMinutes, flightReadModel.DurationMinutes) .Set(x => x.DepartureDate, flightReadModel.DepartureDate) .Set(x => x.FlightDate, flightReadModel.FlightDate) - .Set(x => x.FlightId, flightReadModel.FlightId) .Set(x => x.FlightNumber, flightReadModel.FlightNumber) .Set(x => x.IsDeleted, flightReadModel.IsDeleted) .Set(x => x.Status, flightReadModel.Status) diff --git a/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs b/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs index 2f14ef9..b5af62d 100644 --- a/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs +++ b/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs @@ -6,17 +6,17 @@ namespace Flight.Flights.Models.Reads; public class FlightReadModel { - public long Id { get; init; } + public long Id { get; set; } public long FlightId { get; set; } - public string FlightNumber { get; init; } - public long AircraftId { get; init; } - public DateTime DepartureDate { get; init; } - public long DepartureAirportId { get; init; } - public DateTime ArriveDate { get; init; } - public long ArriveAirportId { get; init; } - public decimal DurationMinutes { get; init; } - public DateTime FlightDate { get; init; } - public FlightStatus Status { get; init; } - public decimal Price { get; init; } + public string FlightNumber { get; set; } + public long AircraftId { get; set; } + public DateTime DepartureDate { get; set; } + public long DepartureAirportId { get; set; } + public DateTime ArriveDate { get; set; } + public long ArriveAirportId { get; set; } + public decimal DurationMinutes { get; set; } + public DateTime FlightDate { get; set; } + public FlightStatus Status { get; set; } + public decimal Price { get; set; } public bool IsDeleted { get; set; } } diff --git a/src/Services/Flight/src/Flight/Seats/Features/CreateSeat/CreateSeatCommand.cs b/src/Services/Flight/src/Flight/Seats/Features/CreateSeat/CreateSeatCommand.cs index a054583..800c0ac 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/CreateSeat/CreateSeatCommand.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/CreateSeat/CreateSeatCommand.cs @@ -7,5 +7,5 @@ namespace Flight.Seats.Features.CreateSeat; public record CreateSeatCommand(string SeatNumber, SeatType Type, SeatClass Class, long FlightId) : ICommand, IInternalCommand { - public long Id { get; set; } = SnowFlakIdGenerator.NewId(); + public long Id { get; init; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Seats/Features/GetAvailableSeats/GetAvailableSeatsQueryHandler.cs b/src/Services/Flight/src/Flight/Seats/Features/GetAvailableSeats/GetAvailableSeatsQueryHandler.cs index af0bb54..6901980 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/GetAvailableSeats/GetAvailableSeatsQueryHandler.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/GetAvailableSeats/GetAvailableSeatsQueryHandler.cs @@ -29,7 +29,7 @@ public class GetAvailableSeatsQueryHandler : IRequestHandler !x.IsDeleted); + .Where(x => x.FlightId == query.FlightId); if (!seats.Any()) throw new AllSeatsFullException(); diff --git a/src/Services/Flight/src/Flight/Seats/Features/SeatMappings.cs b/src/Services/Flight/src/Flight/Seats/Features/SeatMappings.cs index 4f62a2a..79aabb7 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/SeatMappings.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/SeatMappings.cs @@ -16,6 +16,9 @@ public class SeatMappings : IRegister config.NewConfig() .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) .Map(d => d.SeatId, s => s.Id); + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.SeatId, s => s.Id); config.NewConfig() .Map(d => d.SeatId, s => s.Id); } diff --git a/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs b/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs index c361124..3d9b45f 100644 --- a/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs @@ -1,20 +1,25 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; +using Flight.Aircrafts.Features.CreateAircraft.Reads; +using Flight.Airports.Features.CreateAirport.Reads; +using Flight.Data; using FluentAssertions; +using Grpc.Net.Client; using Integration.Test.Fakes; using MassTransit; using MassTransit.Testing; using Xunit; namespace Integration.Test.Aircraft.Features; -public class CreateAircraftTests : IClassFixture + +public class CreateAircraftTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public CreateAircraftTests(IntegrationTestFixture fixture) + + public CreateAircraftTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _testHarness = fixture.TestHarness; + _testHarness = Fixture.TestHarness; } [Fact] @@ -24,12 +29,14 @@ public class CreateAircraftTests : IClassFixture var command = new FakeCreateAircraftCommand().Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // Assert response?.Should().NotBeNull(); response?.Name.Should().Be(command.Name); (await _testHarness.Published.Any>()).Should().BeFalse(); (await _testHarness.Published.Any()).Should().BeTrue(); + + await Fixture.ShouldProcessedPersistInternalCommand(); } } diff --git a/src/Services/Flight/tests/IntegrationTest/Airport/Features/CreateAirportTests.cs b/src/Services/Flight/tests/IntegrationTest/Airport/Features/CreateAirportTests.cs index 73ae343..10c25ce 100644 --- a/src/Services/Flight/tests/IntegrationTest/Airport/Features/CreateAirportTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Airport/Features/CreateAirportTests.cs @@ -1,5 +1,9 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; +using Flight.Aircrafts.Features.CreateAircraft.Reads; +using Flight.Airports.Features.CreateAirport.Reads; +using Flight.Data; using FluentAssertions; using Integration.Test.Fakes; using MassTransit; @@ -7,15 +11,16 @@ using MassTransit.Testing; using Xunit; namespace Integration.Test.Airport.Features; -public class CreateAirportTests : IClassFixture + +public class CreateAirportTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public CreateAirportTests(IntegrationTestFixture fixture) + public CreateAirportTests( + IntegrationTestFixture integrationTestFixture) : base( + integrationTestFixture) { - _fixture = fixture; - _testHarness = fixture.TestHarness; + _testHarness = Fixture.TestHarness; } [Fact] @@ -25,12 +30,14 @@ public class CreateAirportTests : IClassFixture var command = new FakeCreateAirportCommand().Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // Assert response?.Should().NotBeNull(); response?.Name.Should().Be(command.Name); (await _testHarness.Published.Any>()).Should().BeFalse(); (await _testHarness.Published.Any()).Should().BeTrue(); + + await Fixture.ShouldProcessedPersistInternalCommand(); } } diff --git a/src/Services/Flight/tests/IntegrationTest/Fakes/FakeUpdateFlightCommand.cs b/src/Services/Flight/tests/IntegrationTest/Fakes/FakeUpdateFlightCommand.cs index e0f8395..9adeeae 100644 --- a/src/Services/Flight/tests/IntegrationTest/Fakes/FakeUpdateFlightCommand.cs +++ b/src/Services/Flight/tests/IntegrationTest/Fakes/FakeUpdateFlightCommand.cs @@ -5,13 +5,15 @@ namespace Integration.Test.Fakes; public class FakeUpdateFlightCommand : AutoFaker { - public FakeUpdateFlightCommand(long id) + public FakeUpdateFlightCommand(global::Flight.Flights.Models.Flight flight) { - RuleFor(r => r.Id, _ => id); - RuleFor(r => r.DepartureAirportId, _ => 2); - RuleFor(r => r.ArriveAirportId, _ => 1); - RuleFor(r => r.AircraftId, _ => 2); - RuleFor(r => r.FlightNumber, _ => "12BB"); + RuleFor(r => r.Id, _ => flight.Id); + RuleFor(r => r.DepartureAirportId, _ => flight.DepartureAirportId); + RuleFor(r => r.ArriveAirportId, _ => flight.ArriveAirportId); + RuleFor(r => r.AircraftId, _ => flight.AircraftId); + RuleFor(r => r.FlightNumber, _ => "12UU"); RuleFor(r => r.Price, _ => 800); + RuleFor(r => r.Status, _ => flight.Status); + RuleFor(r => r.ArriveDate, _ => flight.ArriveDate); } } diff --git a/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs b/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs index 33b311d..0c21ad7 100644 --- a/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs @@ -1,5 +1,8 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; +using Flight.Data; +using Flight.Flights.Features.CreateFlight.Reads; using FluentAssertions; using Integration.Test.Fakes; using MassTransit; @@ -8,15 +11,15 @@ using Xunit; namespace Integration.Test.Flight.Features; -public class CreateFlightTests : IClassFixture +public class CreateFlightTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public CreateFlightTests(IntegrationTestFixture fixture) + public CreateFlightTests( + IntegrationTestFixture integrationTestFixture) : base( + integrationTestFixture) { - _fixture = fixture; - _testHarness = fixture.TestHarness; + _testHarness = Fixture.TestHarness; } [Fact] @@ -26,7 +29,7 @@ public class CreateFlightTests : IClassFixture var command = new FakeCreateFlightCommand().Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // Assert response.Should().NotBeNull(); @@ -34,5 +37,7 @@ public class CreateFlightTests : IClassFixture (await _testHarness.Published.Any>()).Should().BeFalse(); (await _testHarness.Published.Any()).Should().BeTrue(); + + await Fixture.ShouldProcessedPersistInternalCommand(); } } diff --git a/src/Services/Flight/tests/IntegrationTest/Flight/Features/DeleteFlightTests.cs b/src/Services/Flight/tests/IntegrationTest/Flight/Features/DeleteFlightTests.cs index 3a1b7e3..add3a17 100644 --- a/src/Services/Flight/tests/IntegrationTest/Flight/Features/DeleteFlightTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Flight/Features/DeleteFlightTests.cs @@ -1,9 +1,11 @@ using System.Linq; using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; +using Flight.Data; using Flight.Flights.Features.DeleteFlight; +using Flight.Flights.Features.DeleteFlight.Reads; using FluentAssertions; -using Integration.Test.Fakes; using MassTransit; using MassTransit.Testing; using Microsoft.EntityFrameworkCore; @@ -11,33 +13,27 @@ using Xunit; namespace Integration.Test.Flight.Features; -public class DeleteFlightTests : IClassFixture +public class DeleteFlightTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public DeleteFlightTests(IntegrationTestFixture fixture) + public DeleteFlightTests( + IntegrationTestFixture integrationTestFixture) : base( + integrationTestFixture) { - _fixture = fixture; - _testHarness = fixture.TestHarness; + _testHarness = Fixture.TestHarness; } [Fact] public async Task should_delete_flight_from_db() { // Arrange - var createFlightCommand = new FakeCreateFlightCommand().Generate(); - var flightEntity = global::Flight.Flights.Models.Flight.Create( - createFlightCommand.Id, createFlightCommand.FlightNumber, createFlightCommand.AircraftId, createFlightCommand.DepartureAirportId, - createFlightCommand.DepartureDate, createFlightCommand.ArriveDate, createFlightCommand.ArriveAirportId, createFlightCommand.DurationMinutes, - createFlightCommand.FlightDate, createFlightCommand.Status, createFlightCommand.Price); - await _fixture.InsertAsync(flightEntity); - + var flightEntity = await Fixture.FindAsync(1); var command = new DeleteFlightCommand(flightEntity.Id); // Act - await _fixture.SendAsync(command); - var deletedFlight = (await _fixture.ExecuteDbContextAsync(db => db.Flights + await Fixture.SendAsync(command); + var deletedFlight = (await Fixture.ExecuteDbContextAsync(db => db.Flights .Where(x => x.Id == command.Id) .IgnoreQueryFilters() .ToListAsync()) @@ -47,6 +43,6 @@ public class DeleteFlightTests : IClassFixture deletedFlight?.IsDeleted.Should().BeTrue(); (await _testHarness.Published.Any>()).Should().BeFalse(); (await _testHarness.Published.Any()).Should().BeTrue(); + await Fixture.ShouldProcessedPersistInternalCommand(); } } - diff --git a/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetAvailableFlightsTests.cs b/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetAvailableFlightsTests.cs index 03ae34c..9718e00 100644 --- a/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetAvailableFlightsTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetAvailableFlightsTests.cs @@ -1,42 +1,37 @@ using System.Linq; using System.Threading.Tasks; -using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.TestBase; +using Flight.Data; +using Flight.Flights.Features.CreateFlight.Reads; using Flight.Flights.Features.GetAvailableFlights; using FluentAssertions; -using Grpc.Net.Client; using Integration.Test.Fakes; -using MagicOnion.Client; using Xunit; namespace Integration.Test.Flight.Features; -public class GetAvailableFlightsTests : IClassFixture +public class GetAvailableFlightsTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; - private readonly GrpcChannel _channel; - - public GetAvailableFlightsTests(IntegrationTestFixture fixture) + public GetAvailableFlightsTests( + IntegrationTestFixture integrationTestFixture) + : base(integrationTestFixture) { - _fixture = fixture; - _channel = fixture.Channel; } [Fact] public async Task should_return_available_flights() { // Arrange - var flightCommand1 = new FakeCreateFlightCommand().Generate(); - var flightCommand2 = new FakeCreateFlightCommand().Generate(); + var flightCommand = new FakeCreateFlightCommand().Generate(); - var flightEntity1 = FakeFlightCreated.Generate(flightCommand1); - var flightEntity2 = FakeFlightCreated.Generate(flightCommand2); + await Fixture.SendAsync(flightCommand); - await _fixture.InsertAsync(flightEntity1, flightEntity2); + await Fixture.ShouldProcessedPersistInternalCommand(); var query = new GetAvailableFlightsQuery(); // Act - var response = (await _fixture.SendAsync(query))?.ToList(); + var response = (await Fixture.SendAsync(query))?.ToList(); // Assert response?.Should().NotBeNull(); diff --git a/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetFlightByIdTests.cs b/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetFlightByIdTests.cs index 8a41901..23edfda 100644 --- a/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetFlightByIdTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Flight/Features/GetFlightByIdTests.cs @@ -1,5 +1,8 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.TestBase; +using Flight.Data; +using Flight.Flights.Features.CreateFlight.Reads; using Flight.Flights.Features.GetFlightById; using FluentAssertions; using Grpc.Net.Client; @@ -9,50 +12,50 @@ using Xunit; namespace Integration.Test.Flight.Features; -public class GetFlightByIdTests : IClassFixture +public class GetFlightByIdTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly GrpcChannel _channel; - public GetFlightByIdTests(IntegrationTestFixture fixture) + public GetFlightByIdTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _channel = fixture.Channel; + _channel = Fixture.Channel; } [Fact] public async Task should_retrive_a_flight_by_id_currectly() { - // Arrange + //Arrange var command = new FakeCreateFlightCommand().Generate(); - var flightEntity = FakeFlightCreated.Generate(command); - await _fixture.InsertAsync(flightEntity); + await Fixture.SendAsync(command); - var query = new GetFlightByIdQuery(flightEntity.Id); + await Fixture.ShouldProcessedPersistInternalCommand(); + + var query = new GetFlightByIdQuery(command.Id); // Act - var response = await _fixture.SendAsync(query); + var response = await Fixture.SendAsync(query); // Assert response.Should().NotBeNull(); - response?.Id.Should().Be(flightEntity.Id); + response?.FlightId.Should().Be(command.Id); } [Fact] public async Task should_retrive_a_flight_by_id_from_grpc_service() { - // Arrange + //Arrange var command = new FakeCreateFlightCommand().Generate(); - var flightEntity = FakeFlightCreated.Generate(command); - await _fixture.InsertAsync(flightEntity); + await Fixture.SendAsync(command); + + await Fixture.ShouldProcessedPersistInternalCommand(); var flightGrpcClient = MagicOnionClient.Create(_channel); // Act - var response = await flightGrpcClient.GetById(flightEntity.Id); + var response = await flightGrpcClient.GetById(command.Id); // Assert response?.Should().NotBeNull(); - response?.Id.Should().Be(flightEntity.Id); + response?.FlightId.Should().Be(command.Id); } } diff --git a/src/Services/Flight/tests/IntegrationTest/Flight/Features/UpdateFlightTests.cs b/src/Services/Flight/tests/IntegrationTest/Flight/Features/UpdateFlightTests.cs index de00712..03d9cca 100644 --- a/src/Services/Flight/tests/IntegrationTest/Flight/Features/UpdateFlightTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Flight/Features/UpdateFlightTests.cs @@ -1,5 +1,8 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; +using Flight.Data; +using Flight.Flights.Features.UpdateFlight.Reads; using FluentAssertions; using Integration.Test.Fakes; using MassTransit; @@ -7,29 +10,25 @@ using MassTransit.Testing; using Xunit; namespace Integration.Test.Flight.Features; -public class UpdateFlightTests : IClassFixture +public class UpdateFlightTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public UpdateFlightTests(IntegrationTestFixture fixture) + public UpdateFlightTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _testHarness = fixture.TestHarness; + _testHarness = Fixture.TestHarness; } + [Fact] public async Task should_update_flight_to_db_and_publish_message_to_broker() { // Arrange - var fakeCreateCommandFlight = new FakeCreateFlightCommand().Generate(); - var flightEntity = FakeFlightCreated.Generate(fakeCreateCommandFlight); - await _fixture.InsertAsync(flightEntity); - - var command = new FakeUpdateFlightCommand(flightEntity.Id).Generate(); + var flightEntity = await Fixture.FindAsync(1); + var command = new FakeUpdateFlightCommand(flightEntity).Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // Assert response.Should().NotBeNull(); @@ -37,5 +36,6 @@ public class UpdateFlightTests : IClassFixture response?.Price.Should().NotBe(flightEntity?.Price); (await _testHarness.Published.Any>()).Should().BeFalse(); (await _testHarness.Published.Any()).Should().BeTrue(); + await Fixture.ShouldProcessedPersistInternalCommand(); } } diff --git a/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs deleted file mode 100644 index 6d65768..0000000 --- a/src/Services/Flight/tests/IntegrationTest/IntegrationTestFixture.cs +++ /dev/null @@ -1,273 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; -using BuildingBlocks.Core.Model; -using BuildingBlocks.MassTransit; -using BuildingBlocks.Mongo; -using BuildingBlocks.Web; -using Flight.Data; -using Grpc.Net.Client; -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; - } -} diff --git a/src/Services/Flight/tests/IntegrationTest/Seat/Features/GetAvailableSeatsTests.cs b/src/Services/Flight/tests/IntegrationTest/Seat/Features/GetAvailableSeatsTests.cs index 3e11b38..d28d67b 100644 --- a/src/Services/Flight/tests/IntegrationTest/Seat/Features/GetAvailableSeatsTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Seat/Features/GetAvailableSeatsTests.cs @@ -1,23 +1,26 @@ using System.Linq; using System.Threading.Tasks; using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.TestBase; +using Flight.Data; +using Flight.Flights.Features.CreateFlight.Reads; +using Flight.Seats.Features.CreateSeat.Reads; using FluentAssertions; using Grpc.Net.Client; using Integration.Test.Fakes; using MagicOnion.Client; +using MassTransit.Testing; using Xunit; namespace Integration.Test.Seat.Features; -public class GetAvailableSeatsTests : IClassFixture +public class GetAvailableSeatsTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly GrpcChannel _channel; - public GetAvailableSeatsTests(IntegrationTestFixture fixture) + public GetAvailableSeatsTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _channel = fixture.Channel; + _channel = Fixture.Channel; } [Fact] @@ -25,24 +28,24 @@ public class GetAvailableSeatsTests : IClassFixture { // Arrange var flightCommand = new FakeCreateFlightCommand().Generate(); - var flightEntity = FakeFlightCreated.Generate(flightCommand); - await _fixture.InsertAsync(flightEntity); + await Fixture.SendAsync(flightCommand); - var seatCommand1 = new FakeCreateSeatCommand(flightEntity.Id).Generate(); - var seatCommand2 = new FakeCreateSeatCommand(flightEntity.Id).Generate(); - var seatEntity1 = FakeSeatCreated.Generate(seatCommand1); - var seatEntity2 = FakeSeatCreated.Generate(seatCommand2); + await Fixture.ShouldProcessedPersistInternalCommand(); - await _fixture.InsertAsync(seatEntity1, seatEntity2); + var seatCommand = new FakeCreateSeatCommand(flightCommand.Id).Generate(); + + await Fixture.SendAsync(seatCommand); + + await Fixture.ShouldProcessedPersistInternalCommand(); var flightGrpcClient = MagicOnionClient.Create(_channel); // Act - var response = await flightGrpcClient.GetAvailableSeats(flightEntity.Id); + var response = await flightGrpcClient.GetAvailableSeats(flightCommand.Id); // Assert response?.Should().NotBeNull(); - response?.Count().Should().BeGreaterOrEqualTo(2); + response?.Count().Should().BeGreaterOrEqualTo(1); } } diff --git a/src/Services/Flight/tests/IntegrationTest/Seat/Features/ReserveSeatTests.cs b/src/Services/Flight/tests/IntegrationTest/Seat/Features/ReserveSeatTests.cs index 5a4bcc1..a620fe1 100644 --- a/src/Services/Flight/tests/IntegrationTest/Seat/Features/ReserveSeatTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Seat/Features/ReserveSeatTests.cs @@ -1,5 +1,9 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.TestBase; +using Flight.Data; +using Flight.Flights.Features.CreateFlight.Reads; +using Flight.Seats.Features.CreateSeat.Reads; using FluentAssertions; using Grpc.Net.Client; using Integration.Test.Fakes; @@ -7,15 +11,16 @@ using MagicOnion.Client; using Xunit; namespace Integration.Test.Seat.Features; -public class ReserveSeatTests : IClassFixture + +public class ReserveSeatTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly GrpcChannel _channel; - public ReserveSeatTests(IntegrationTestFixture fixture) + public ReserveSeatTests( + IntegrationTestFixture integrationTestFixture) : base( + integrationTestFixture) { - _fixture = fixture; - _channel = fixture.Channel; + _channel = Fixture.Channel; } [Fact] @@ -23,23 +28,28 @@ public class ReserveSeatTests : IClassFixture { // Arrange var flightCommand = new FakeCreateFlightCommand().Generate(); - var flightEntity = FakeFlightCreated.Generate(flightCommand); - await _fixture.InsertAsync(flightEntity); + await Fixture.SendAsync(flightCommand); - var seatCommand = new FakeCreateSeatCommand(flightEntity.Id).Generate(); - var seatEntity = FakeSeatCreated.Generate(seatCommand); + await Fixture.ShouldProcessedPersistInternalCommand(); - await _fixture.InsertAsync(seatEntity); + var seatCommand = new FakeCreateSeatCommand(flightCommand.Id).Generate(); + + await Fixture.SendAsync(seatCommand); + + await Fixture.ShouldProcessedPersistInternalCommand(); var flightGrpcClient = MagicOnionClient.Create(_channel); // Act - var response = await flightGrpcClient.ReserveSeat(new ReserveSeatRequestDto{ FlightId = seatEntity.FlightId, SeatNumber = seatEntity.SeatNumber }); + var response = await flightGrpcClient.ReserveSeat(new ReserveSeatRequestDto + { + FlightId = seatCommand.FlightId, SeatNumber = seatCommand.SeatNumber + }); // Assert response?.Should().NotBeNull(); - response?.SeatNumber.Should().Be(seatEntity.SeatNumber); - response?.FlightId.Should().Be(seatEntity.FlightId); + response?.SeatNumber.Should().Be(seatCommand.SeatNumber); + response?.FlightId.Should().Be(seatCommand.FlightId); } } diff --git a/src/Services/Flight/tests/UnitTest/Flight/Features/CreateFlight/CreateFlightCommandHandlerTests.cs b/src/Services/Flight/tests/UnitTest/Flight/Features/CreateFlight/CreateFlightCommandHandlerTests.cs index a59f5dd..48f3692 100644 --- a/src/Services/Flight/tests/UnitTest/Flight/Features/CreateFlight/CreateFlightCommandHandlerTests.cs +++ b/src/Services/Flight/tests/UnitTest/Flight/Features/CreateFlight/CreateFlightCommandHandlerTests.cs @@ -25,7 +25,7 @@ public class CreateFlightCommandHandlerTests public CreateFlightCommandHandlerTests(UnitTestFixture fixture) { _fixture = fixture; - _handler = new CreateFlightCommandHandler(fixture.Mapper, fixture.DbContext, Substitute.For()); + _handler = new CreateFlightCommandHandler(fixture.Mapper, fixture.DbContext); } [Fact] diff --git a/src/Services/Identity/src/Identity.Api/Program.cs b/src/Services/Identity/src/Identity.Api/Program.cs index e0e3e45..f6e4c58 100644 --- a/src/Services/Identity/src/Identity.Api/Program.cs +++ b/src/Services/Identity/src/Identity.Api/Program.cs @@ -57,7 +57,7 @@ if (app.Environment.IsDevelopment()) } app.UseSerilogRequestLogging(); -app.UseMigration(); +app.UseMigration(env); app.UseCorrelationId(); app.UseRouting(); app.UseHttpMetrics(); diff --git a/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs b/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs index ab55719..3cbe8bd 100644 --- a/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs +++ b/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs @@ -1,6 +1,9 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; using FluentAssertions; +using Grpc.Net.Client; +using Identity.Data; using Integration.Test.Fakes; using MassTransit; using MassTransit.Testing; @@ -8,15 +11,13 @@ using Xunit; namespace Integration.Test.Identity.Features; -public class RegisterNewUserTests : IClassFixture +public class RegisterNewUserTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public RegisterNewUserTests(IntegrationTestFixture fixture) + public RegisterNewUserTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _testHarness = _fixture.TestHarness; + _testHarness = Fixture.TestHarness; } [Fact] @@ -26,7 +27,7 @@ public class RegisterNewUserTests : IClassFixture var command = new FakeRegisterNewUserCommand().Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // Assert response?.Should().NotBeNull(); diff --git a/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs deleted file mode 100644 index 6933645..0000000 --- a/src/Services/Identity/tests/IntegrationTest/IntegrationTestFixture.cs +++ /dev/null @@ -1,271 +0,0 @@ -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; - } -} diff --git a/src/Services/Passenger/src/Passenger.Api/Program.cs b/src/Services/Passenger/src/Passenger.Api/Program.cs index 134276e..d13fb8a 100644 --- a/src/Services/Passenger/src/Passenger.Api/Program.cs +++ b/src/Services/Passenger/src/Passenger.Api/Program.cs @@ -62,7 +62,7 @@ if (app.Environment.IsDevelopment()) } app.UseSerilogRequestLogging(); -app.UseMigration(); +app.UseMigration(env); app.UseCorrelationId(); app.UseRouting(); app.UseHttpMetrics(); diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/CompleteRegisterPassenger/CompleteRegisterPassengerCommand.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/CompleteRegisterPassenger/CompleteRegisterPassengerCommand.cs index 6d265f4..79d11c3 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/CompleteRegisterPassenger/CompleteRegisterPassengerCommand.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/CompleteRegisterPassenger/CompleteRegisterPassengerCommand.cs @@ -7,5 +7,5 @@ namespace Passenger.Passengers.Features.CompleteRegisterPassenger; public record CompleteRegisterPassengerCommand(string PassportNumber, PassengerType PassengerType, int Age) : ICommand { - public long Id { get; set; } = SnowFlakIdGenerator.NewId(); + public long Id { get; init; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Passenger/tests/IntegrationTest/Fakes/FakePassengerResponseDto.cs b/src/Services/Passenger/tests/IntegrationTest/Fakes/FakePassengerResponseDto.cs index bbc46a7..43a2f6c 100644 --- a/src/Services/Passenger/tests/IntegrationTest/Fakes/FakePassengerResponseDto.cs +++ b/src/Services/Passenger/tests/IntegrationTest/Fakes/FakePassengerResponseDto.cs @@ -1,12 +1,11 @@ using AutoBogus; -using Passenger.Passengers.Dtos; - -namespace Integration.Test.Fakes; +using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.IdsGenerator; public class FakePassengerResponseDto : AutoFaker { - public FakePassengerResponseDto(long id) + public FakePassengerResponseDto() { - RuleFor(r => r.Id, _ => id); + RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId()); } } diff --git a/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs b/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs deleted file mode 100644 index 78d0c4e..0000000 --- a/src/Services/Passenger/tests/IntegrationTest/IntegrationTestFixture.cs +++ /dev/null @@ -1,273 +0,0 @@ -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 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 Passenger.Data; -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; - } -} diff --git a/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs b/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs index 15a911c..a04d6b9 100644 --- a/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs +++ b/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs @@ -1,21 +1,21 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.TestBase; using FluentAssertions; using Integration.Test.Fakes; using MassTransit.Testing; +using Passenger.Data; using Xunit; namespace Integration.Test.Passenger.Features; -public class CompleteRegisterPassengerTests : IClassFixture +public class CompleteRegisterPassengerTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; - public CompleteRegisterPassengerTests(IntegrationTestFixture fixture) + public CompleteRegisterPassengerTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _testHarness = _fixture.TestHarness; + _testHarness = Fixture.TestHarness; } [Fact] @@ -25,12 +25,12 @@ public class CompleteRegisterPassengerTests : IClassFixture(); - await _fixture.InsertAsync(FakePassengerCreated.Generate(userCreated)); + await Fixture.InsertAsync(FakePassengerCreated.Generate(userCreated)); var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate(); // Act - var response = await _fixture.SendAsync(command); + var response = await Fixture.SendAsync(command); // Assert response.Should().NotBeNull(); diff --git a/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/GetPassengerByIdTests.cs b/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/GetPassengerByIdTests.cs index bae4192..41882be 100644 --- a/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/GetPassengerByIdTests.cs +++ b/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/GetPassengerByIdTests.cs @@ -1,29 +1,39 @@ using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Contracts.Grpc; +using BuildingBlocks.TestBase; using FluentAssertions; using Grpc.Net.Client; using Integration.Test.Fakes; +using MagicOnion; using MagicOnion.Client; using MassTransit.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NSubstitute; +using Passenger.Data; using Passenger.Passengers.Features.GetPassengerById; using Xunit; namespace Integration.Test.Passenger.Features; -public class GetPassengerByIdTests : IClassFixture +public class GetPassengerByIdTests : IntegrationTestBase { - private readonly IntegrationTestFixture _fixture; private readonly ITestHarness _testHarness; private readonly GrpcChannel _channel; - public GetPassengerByIdTests(IntegrationTestFixture fixture) + public GetPassengerByIdTests(IntegrationTestFixture integrationTestFixture) : base(integrationTestFixture) { - _fixture = fixture; - _testHarness = _fixture.TestHarness; - _channel = _fixture.Channel; + _channel = Fixture.Channel; + _testHarness = Fixture.TestHarness; } + protected override void RegisterTestsServices(IServiceCollection services) + { + MockPassengerGrpcServices(services); + } + + [Fact] public async Task should_retrive_a_passenger_by_id_currectly() { @@ -32,12 +42,12 @@ public class GetPassengerByIdTests : IClassFixture await _testHarness.Bus.Publish(userCreated); await _testHarness.Consumed.Any(); var passengerEntity = FakePassengerCreated.Generate(userCreated); - await _fixture.InsertAsync(passengerEntity); + await Fixture.InsertAsync(passengerEntity); var query = new GetPassengerQueryById(passengerEntity.Id); // Act - var response = await _fixture.SendAsync(query); + var response = await Fixture.SendAsync(query); // Assert response.Should().NotBeNull(); @@ -52,7 +62,7 @@ public class GetPassengerByIdTests : IClassFixture await _testHarness.Bus.Publish(userCreated); await _testHarness.Consumed.Any(); var passengerEntity = FakePassengerCreated.Generate(userCreated); - await _fixture.InsertAsync(passengerEntity); + await Fixture.InsertAsync(passengerEntity); var passengerGrpcClient = MagicOnionClient.Create(_channel); @@ -63,4 +73,16 @@ public class GetPassengerByIdTests : IClassFixture response?.Should().NotBeNull(); response?.Id.Should().Be(passengerEntity.Id); } + + 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; + })); + } }