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