mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-16 07:26:25 +08:00
Merge pull request #33 from meysamhadeli/develop
move integration test base to building blocks
This commit is contained in:
commit
5d8077963a
@ -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
|
||||
|
||||
@ -120,7 +120,8 @@
|
||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||
<PackageReference Include="Respawn" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<TContext>(this IApplicationBuilder app)
|
||||
public static IApplicationBuilder UseMigration<TContext>(this IApplicationBuilder app, IWebHostEnvironment env)
|
||||
where TContext : DbContext, IDbContext
|
||||
{
|
||||
MigrateDatabaseAsync<TContext>(app.ApplicationServices).GetAwaiter().GetResult();
|
||||
SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult();
|
||||
|
||||
if (!env.IsEnvironment("test"))
|
||||
SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@ -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<IReadOnlyList<PersistMessage>> GetByFilterAsync(
|
||||
Expression<Func<PersistMessage, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PersistMessage> ExistMessageAsync(
|
||||
Guid messageId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@ -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<IReadOnlyList<PersistMessage>> GetByFilterAsync(Expression<Func<PersistMessage, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var b = (await _dbContext.PersistMessages.Where(predicate).ToListAsync(cancellationToken)).AsReadOnly();
|
||||
return b;
|
||||
}
|
||||
|
||||
public Task<PersistMessage> ExistMessageAsync(Guid messageId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _dbContext.PersistMessages.FirstOrDefaultAsync(x =>
|
||||
|
||||
404
src/BuildingBlocks/TestBase/IntegrationTestBase.cs
Normal file
404
src/BuildingBlocks/TestBase/IntegrationTestBase.cs
Normal file
@ -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<TEntryPoint> : IAsyncLifetime
|
||||
where TEntryPoint : class
|
||||
{
|
||||
private readonly WebApplicationFactory<TEntryPoint> _factory;
|
||||
private int Timeout => 180;
|
||||
public Action<IServiceCollection> 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<IConfiguration>();
|
||||
|
||||
|
||||
public IntegrationTestFixture()
|
||||
{
|
||||
_factory = new WebApplicationFactory<TEntryPoint>()
|
||||
.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<RabbitMqOptions>("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<IServiceCollection> 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<IServiceProvider, Task> action)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
await action(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
|
||||
var result = await action(scope.ServiceProvider);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequest request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
|
||||
public async ValueTask WaitUntilConditionMet(Func<Task<bool>> 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<TInternalCommand>()
|
||||
where TInternalCommand : class, IInternalCommand
|
||||
{
|
||||
await WaitUntilConditionMet(async () =>
|
||||
{
|
||||
return await ExecuteScopeAsync(async sp =>
|
||||
{
|
||||
var persistMessageProcessor = sp.GetService<IPersistMessageProcessor>();
|
||||
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<IHttpContextAccessor>();
|
||||
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<TEntryPoint, TWContext> : IntegrationTestFixture<TEntryPoint>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
{
|
||||
public Task ExecuteDbContextAsync(Func<TWContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()));
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<TWContext, ValueTask> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<TWContext, IMediator, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, ValueTask<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, IMediator, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task InsertAsync<T>(params T[] entities) where T : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
foreach (var entity in entities) db.Set<T>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InsertAsync<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
await ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(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<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> FindAsync<T>(long id)
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||
}
|
||||
}
|
||||
|
||||
public class IntegrationTestFixture<TEntryPoint, TWContext, TRContext> : IntegrationTestFixture<TEntryPoint, TWContext>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
{
|
||||
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TRContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteReadContextAsync<T>(Func<TRContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TRContext>()));
|
||||
}
|
||||
}
|
||||
|
||||
public class IntegrationTestFixtureCore<TEntryPoint> : IAsyncLifetime
|
||||
where TEntryPoint : class
|
||||
{
|
||||
private Checkpoint _checkpoint;
|
||||
private MongoDbRunner _mongoRunner;
|
||||
|
||||
public IntegrationTestFixtureCore(IntegrationTestFixture<TEntryPoint> integrationTestFixture)
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
integrationTestFixture.RegisterServices(services => RegisterTestsServices(services));
|
||||
}
|
||||
|
||||
public IntegrationTestFixture<TEntryPoint> Fixture { get; }
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
||||
|
||||
_mongoRunner = MongoDbRunner.Start();
|
||||
var mongoOptions = Fixture.ServiceProvider.GetRequiredService<IOptions<MongoOptions>>();
|
||||
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<IDataSeeder>();
|
||||
foreach (var seeder in seeders) await seeder.SeedAllAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class IntegrationTestBase<TEntryPoint> : IntegrationTestFixtureCore<TEntryPoint>,
|
||||
IClassFixture<IntegrationTestFixture<TEntryPoint>>
|
||||
where TEntryPoint : class
|
||||
{
|
||||
protected IntegrationTestBase(
|
||||
IntegrationTestFixture<TEntryPoint> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
}
|
||||
|
||||
public new IntegrationTestFixture<TEntryPoint> Fixture { get; }
|
||||
}
|
||||
|
||||
public abstract class IntegrationTestBase<TEntryPoint, TWContext> : IntegrationTestFixtureCore<TEntryPoint>,
|
||||
IClassFixture<IntegrationTestFixture<TEntryPoint, TWContext>>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
{
|
||||
protected IntegrationTestBase(
|
||||
IntegrationTestFixture<TEntryPoint, TWContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
}
|
||||
|
||||
public new IntegrationTestFixture<TEntryPoint, TWContext> Fixture { get; }
|
||||
}
|
||||
|
||||
public abstract class IntegrationTestBase<TEntryPoint, TWContext, TRContext> : IntegrationTestFixtureCore<TEntryPoint>,
|
||||
IClassFixture<IntegrationTestFixture<TEntryPoint, TWContext, TRContext>>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
{
|
||||
protected IntegrationTestBase(
|
||||
IntegrationTestFixture<TEntryPoint, TWContext, TRContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
}
|
||||
|
||||
public new IntegrationTestFixture<TEntryPoint, TWContext, TRContext> Fixture { get; }
|
||||
}
|
||||
@ -69,7 +69,7 @@ if (app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseMigration<BookingDbContext>();
|
||||
app.UseMigration<BookingDbContext>(env);
|
||||
app.UseCorrelationId();
|
||||
app.UseRouting();
|
||||
app.UseHttpMetrics();
|
||||
|
||||
@ -5,6 +5,6 @@ namespace Booking.Booking.Features.CreateBooking;
|
||||
|
||||
public record CreateBookingCommand(long PassengerId, long FlightId, string Description) : ICommand<ulong>
|
||||
{
|
||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
{
|
||||
private readonly GrpcChannel _channel;
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public CreateBookingTests(IntegrationTestFixture fixture)
|
||||
public class CreateBookingTests : IntegrationTestBase<Program, BookingDbContext>
|
||||
{
|
||||
public CreateBookingTests(IntegrationTestFixture<Program, BookingDbContext> 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<IntegrationTestFixture>
|
||||
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<IPassengerGrpcService>();
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<PassengerResponseDto>(new FakePassengerResponseDto().Generate()));
|
||||
|
||||
return mock;
|
||||
}));
|
||||
}
|
||||
|
||||
private void MockFlightGrpcServices(IServiceCollection services)
|
||||
{
|
||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mock = Substitute.For<IFlightGrpcService>();
|
||||
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<FlightResponseDto>(Task.FromResult(new FakeFlightResponseDto().Generate())));
|
||||
|
||||
mock.GetAvailableSeats(Arg.Any<long>())
|
||||
.Returns(
|
||||
new UnaryResult<IEnumerable<SeatResponseDto>>(Task.FromResult(FakeSeatsResponseDto.Generate())));
|
||||
|
||||
mock.ReserveSeat(new FakeReserveSeatRequestDto().Generate())
|
||||
.Returns(new UnaryResult<SeatResponseDto>(Task.FromResult(FakeSeatsResponseDto.Generate().First())));
|
||||
|
||||
return mock;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Program> _factory;
|
||||
private MongoDbRunner _mongoRunner;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private Action<IServiceCollection>? _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<Program>()
|
||||
.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<RabbitMqOptions>("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<IConfiguration>();
|
||||
|
||||
HttpClient = _factory.CreateClient();
|
||||
Channel = CreateChannel();
|
||||
TestHarness = CreateHarness();
|
||||
|
||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
||||
|
||||
_mongoRunner = MongoDbRunner.Start();
|
||||
var mongoOptions = _factory.Services.GetRequiredService<IOptions<MongoOptions>>();
|
||||
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<IServiceCollection> 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<IServiceProvider, Task> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
await action(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var result = await action(scope.ServiceProvider);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<BookingDbContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<BookingDbContext>()));
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<BookingDbContext, ValueTask> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<BookingDbContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<BookingDbContext, IMediator, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<BookingDbContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<BookingDbContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<BookingDbContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<BookingDbContext, ValueTask<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<BookingDbContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<BookingDbContext, IMediator, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<BookingDbContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task InsertAsync<T>(params T[] entities) where T : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
foreach (var entity in entities) db.Set<T>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(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<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> FindAsync<T>(long id)
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||
}
|
||||
|
||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequest request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
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<IHttpContextAccessor>();
|
||||
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<IPassengerGrpcService>();
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<PassengerResponseDto>(new FakePassengerResponseDto().Generate()));
|
||||
|
||||
return mock;
|
||||
}));
|
||||
}
|
||||
|
||||
private void MockFlightGrpcServices(IServiceCollection services)
|
||||
{
|
||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mock = Substitute.For<IFlightGrpcService>();
|
||||
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<FlightResponseDto>(Task.FromResult(new FakeFlightResponseDto().Generate())));
|
||||
|
||||
mock.GetAvailableSeats(Arg.Any<long>())
|
||||
.Returns(
|
||||
new UnaryResult<IEnumerable<SeatResponseDto>>(Task.FromResult(FakeSeatsResponseDto.Generate())));
|
||||
|
||||
mock.ReserveSeat(new FakeReserveSeatRequestDto().Generate())
|
||||
.Returns(new UnaryResult<SeatResponseDto>(Task.FromResult(FakeSeatsResponseDto.Generate().First())));
|
||||
|
||||
return mock;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -81,7 +81,7 @@ app.UseSerilogRequestLogging();
|
||||
app.UseCorrelationId();
|
||||
app.UseRouting();
|
||||
app.UseHttpMetrics();
|
||||
app.UseMigration<FlightDbContext>();
|
||||
app.UseMigration<FlightDbContext>(env);
|
||||
app.UseProblemDetails();
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCustomHealthCheck();
|
||||
|
||||
@ -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<CreateAircraftMongoCommand, AircraftReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AircraftId, s => s.Id);
|
||||
|
||||
config.NewConfig<Aircraft, AircraftReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AircraftId, s => s.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,5 +7,5 @@ namespace Flight.Aircrafts.Features.CreateAircraft;
|
||||
|
||||
public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : ICommand<AircraftResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
@ -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<CreateAirportMongoCommand, AirportReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
|
||||
config.NewConfig<Airport, AirportReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,5 +7,5 @@ namespace Flight.Airports.Features.CreateAirport;
|
||||
|
||||
public record CreateAirportCommand(string Name, string Address, string Code) : ICommand<AirportResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ public sealed class FlightDbContext : AppDbContextBase
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.FilterSoftDeletedProperties();
|
||||
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
base.OnModelCreating(builder);
|
||||
}
|
||||
|
||||
@ -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<List<AirportReadModel>>(airports));
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +68,7 @@ public class FlightDataSeeder : IDataSeeder
|
||||
|
||||
await _flightDbContext.Aircraft.AddRangeAsync(aircrafts);
|
||||
await _flightDbContext.SaveChangesAsync();
|
||||
await _flightReadDbContext.Aircraft.InsertManyAsync(_mapper.Map<List<AircraftReadModel>>(aircrafts));
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +89,7 @@ public class FlightDataSeeder : IDataSeeder
|
||||
|
||||
await _flightDbContext.Seats.AddRangeAsync(seats);
|
||||
await _flightDbContext.SaveChangesAsync();
|
||||
await _flightReadDbContext.Seat.InsertManyAsync(_mapper.Map<List<SeatReadModel>>(seats));
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +107,7 @@ public class FlightDataSeeder : IDataSeeder
|
||||
};
|
||||
await _flightDbContext.Flights.AddRangeAsync(flights);
|
||||
await _flightDbContext.SaveChangesAsync();
|
||||
await _flightReadDbContext.Flight.InsertManyAsync(_mapper.Map<List<FlightReadModel>>(flights));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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<FlightResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
@ -15,16 +15,13 @@ namespace Flight.Flights.Features.CreateFlight;
|
||||
public class CreateFlightCommandHandler : ICommandHandler<CreateFlightCommand, FlightResponseDto>
|
||||
{
|
||||
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<FlightResponseDto> Handle(CreateFlightCommand command, CancellationToken cancellationToken)
|
||||
|
||||
@ -13,10 +13,14 @@ public class FlightMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<Models.Flight, FlightResponseDto>();
|
||||
config.NewConfig<Models.Flight, FlightResponseDto>()
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
config.NewConfig<CreateFlightMongoCommand, FlightReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
config.NewConfig<Models.Flight, FlightReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
config.NewConfig<UpdateFlightMongoCommand, FlightReadModel>()
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
config.NewConfig<DeleteFlightMongoCommand, FlightReadModel>()
|
||||
|
||||
@ -27,7 +27,7 @@ public class GetFlightByIdQueryHandler : IQueryHandler<GetFlightByIdQuery, Fligh
|
||||
Guard.Against.Null(query, nameof(query));
|
||||
|
||||
var flight =
|
||||
await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.Id == query.Id, cancellationToken);
|
||||
await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.FlightId == query.Id, cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
throw new FlightNotFountException();
|
||||
|
||||
@ -40,14 +40,12 @@ public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongo
|
||||
await _flightReadDbContext.Flight.UpdateOneAsync(
|
||||
x => x.FlightId == flightReadModel.FlightId,
|
||||
Builders<FlightReadModel>.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)
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -7,5 +7,5 @@ namespace Flight.Seats.Features.CreateSeat;
|
||||
|
||||
public record CreateSeatCommand(string SeatNumber, SeatType Type, SeatClass Class, long FlightId) : ICommand<SeatResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ public class GetAvailableSeatsQueryHandler : IRequestHandler<GetAvailableSeatsQu
|
||||
Guard.Against.Null(query, nameof(query));
|
||||
|
||||
var seats = (await _flightReadDbContext.Seat.AsQueryable().ToListAsync(cancellationToken))
|
||||
.Where(x => !x.IsDeleted);
|
||||
.Where(x => x.FlightId == query.FlightId);
|
||||
|
||||
if (!seats.Any())
|
||||
throw new AllSeatsFullException();
|
||||
|
||||
@ -16,6 +16,9 @@ public class SeatMappings : IRegister
|
||||
config.NewConfig<CreateSeatMongoCommand, SeatReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.SeatId, s => s.Id);
|
||||
config.NewConfig<Seat, SeatReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.SeatId, s => s.Id);
|
||||
config.NewConfig<ReserveSeatMongoCommand, SeatReadModel>()
|
||||
.Map(d => d.SeatId, s => s.Id);
|
||||
}
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
|
||||
public class CreateAircraftTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
public CreateAircraftTests(IntegrationTestFixture fixture)
|
||||
|
||||
public CreateAircraftTests(IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_testHarness = fixture.TestHarness;
|
||||
_testHarness = Fixture.TestHarness;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -24,12 +29,14 @@ public class CreateAircraftTests : IClassFixture<IntegrationTestFixture>
|
||||
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<Fault<AircraftCreated>>()).Should().BeFalse();
|
||||
(await _testHarness.Published.Any<AircraftCreated>()).Should().BeTrue();
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateAircraftMongoCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
|
||||
public class CreateAirportTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public CreateAirportTests(IntegrationTestFixture fixture)
|
||||
public CreateAirportTests(
|
||||
IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(
|
||||
integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_testHarness = fixture.TestHarness;
|
||||
_testHarness = Fixture.TestHarness;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -25,12 +30,14 @@ public class CreateAirportTests : IClassFixture<IntegrationTestFixture>
|
||||
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<Fault<AirportCreated>>()).Should().BeFalse();
|
||||
(await _testHarness.Published.Any<AirportCreated>()).Should().BeTrue();
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateAirportMongoCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,15 @@ namespace Integration.Test.Fakes;
|
||||
|
||||
public class FakeUpdateFlightCommand : AutoFaker<UpdateFlightCommand>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class CreateFlightTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public CreateFlightTests(IntegrationTestFixture fixture)
|
||||
public CreateFlightTests(
|
||||
IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(
|
||||
integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_testHarness = fixture.TestHarness;
|
||||
_testHarness = Fixture.TestHarness;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -26,7 +29,7 @@ public class CreateFlightTests : IClassFixture<IntegrationTestFixture>
|
||||
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<IntegrationTestFixture>
|
||||
|
||||
(await _testHarness.Published.Any<Fault<FlightCreated>>()).Should().BeFalse();
|
||||
(await _testHarness.Published.Any<FlightCreated>()).Should().BeTrue();
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateFlightMongoCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class DeleteFlightTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public DeleteFlightTests(IntegrationTestFixture fixture)
|
||||
public DeleteFlightTests(
|
||||
IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> 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<global::Flight.Flights.Models.Flight>(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<IntegrationTestFixture>
|
||||
deletedFlight?.IsDeleted.Should().BeTrue();
|
||||
(await _testHarness.Published.Any<Fault<FlightDeleted>>()).Should().BeFalse();
|
||||
(await _testHarness.Published.Any<FlightDeleted>()).Should().BeTrue();
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<DeleteFlightMongoCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class GetAvailableFlightsTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly GrpcChannel _channel;
|
||||
|
||||
public GetAvailableFlightsTests(IntegrationTestFixture fixture)
|
||||
public GetAvailableFlightsTests(
|
||||
IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> 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<CreateFlightMongoCommand>();
|
||||
|
||||
var query = new GetAvailableFlightsQuery();
|
||||
|
||||
// Act
|
||||
var response = (await _fixture.SendAsync(query))?.ToList();
|
||||
var response = (await Fixture.SendAsync(query))?.ToList();
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class GetFlightByIdTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly GrpcChannel _channel;
|
||||
|
||||
public GetFlightByIdTests(IntegrationTestFixture fixture)
|
||||
public GetFlightByIdTests(IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> 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<CreateFlightMongoCommand>();
|
||||
|
||||
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<CreateFlightMongoCommand>();
|
||||
|
||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class UpdateFlightTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public UpdateFlightTests(IntegrationTestFixture fixture)
|
||||
public UpdateFlightTests(IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> 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<global::Flight.Flights.Models.Flight>(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<IntegrationTestFixture>
|
||||
response?.Price.Should().NotBe(flightEntity?.Price);
|
||||
(await _testHarness.Published.Any<Fault<FlightUpdated>>()).Should().BeFalse();
|
||||
(await _testHarness.Published.Any<FlightUpdated>()).Should().BeTrue();
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<UpdateFlightMongoCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Program> _factory;
|
||||
private MongoDbRunner _mongoRunner;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private Action<IServiceCollection>? _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<Program>()
|
||||
.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<RabbitMqOptions>("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<IConfiguration>();
|
||||
|
||||
HttpClient = _factory.CreateClient();
|
||||
Channel = CreateChannel();
|
||||
TestHarness = CreateHarness();
|
||||
|
||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
||||
|
||||
_mongoRunner = MongoDbRunner.Start();
|
||||
var mongoOptions = _factory.Services.GetRequiredService<IOptions<MongoOptions>>();
|
||||
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<IServiceCollection> 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<IServiceProvider, Task> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
await action(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var result = await action(scope.ServiceProvider);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<FlightDbContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<FlightDbContext>()));
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<FlightDbContext, ValueTask> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<FlightDbContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<FlightDbContext, IMediator, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<FlightDbContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<FlightDbContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<FlightDbContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<FlightDbContext, ValueTask<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<FlightDbContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<FlightDbContext, IMediator, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<FlightDbContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task InsertAsync<T>(params T[] entities) where T : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
foreach (var entity in entities) db.Set<T>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(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<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> FindAsync<T>(long id)
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||
}
|
||||
|
||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequest request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
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<IHttpContextAccessor>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class GetAvailableSeatsTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly GrpcChannel _channel;
|
||||
|
||||
public GetAvailableSeatsTests(IntegrationTestFixture fixture)
|
||||
public GetAvailableSeatsTests(IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_channel = fixture.Channel;
|
||||
_channel = Fixture.Channel;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -25,24 +28,24 @@ public class GetAvailableSeatsTests : IClassFixture<IntegrationTestFixture>
|
||||
{
|
||||
// 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<CreateFlightMongoCommand>();
|
||||
|
||||
await _fixture.InsertAsync<global::Flight.Seats.Models.Seat, global::Flight.Seats.Models.Seat>(seatEntity1, seatEntity2);
|
||||
var seatCommand = new FakeCreateSeatCommand(flightCommand.Id).Generate();
|
||||
|
||||
await Fixture.SendAsync(seatCommand);
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
||||
|
||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
|
||||
public class ReserveSeatTests : IntegrationTestBase<Program, FlightDbContext, FlightReadDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly GrpcChannel _channel;
|
||||
|
||||
public ReserveSeatTests(IntegrationTestFixture fixture)
|
||||
public ReserveSeatTests(
|
||||
IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(
|
||||
integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_channel = fixture.Channel;
|
||||
_channel = Fixture.Channel;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -23,23 +28,28 @@ public class ReserveSeatTests : IClassFixture<IntegrationTestFixture>
|
||||
{
|
||||
// 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<CreateFlightMongoCommand>();
|
||||
|
||||
await _fixture.InsertAsync(seatEntity);
|
||||
var seatCommand = new FakeCreateSeatCommand(flightCommand.Id).Generate();
|
||||
|
||||
await Fixture.SendAsync(seatCommand);
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
||||
|
||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ public class CreateFlightCommandHandlerTests
|
||||
public CreateFlightCommandHandlerTests(UnitTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_handler = new CreateFlightCommandHandler(fixture.Mapper, fixture.DbContext, Substitute.For<IPersistMessageProcessor>());
|
||||
_handler = new CreateFlightCommandHandler(fixture.Mapper, fixture.DbContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@ -57,7 +57,7 @@ if (app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseMigration<IdentityContext>();
|
||||
app.UseMigration<IdentityContext>(env);
|
||||
app.UseCorrelationId();
|
||||
app.UseRouting();
|
||||
app.UseHttpMetrics();
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class RegisterNewUserTests : IntegrationTestBase<Program, IdentityContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public RegisterNewUserTests(IntegrationTestFixture fixture)
|
||||
public RegisterNewUserTests(IntegrationTestFixture<Program, IdentityContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_testHarness = _fixture.TestHarness;
|
||||
_testHarness = Fixture.TestHarness;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -26,7 +27,7 @@ public class RegisterNewUserTests : IClassFixture<IntegrationTestFixture>
|
||||
var command = new FakeRegisterNewUserCommand().Generate();
|
||||
|
||||
// Act
|
||||
var response = await _fixture.SendAsync(command);
|
||||
var response = await Fixture.SendAsync(command);
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
|
||||
@ -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<Program> _factory;
|
||||
private MongoDbRunner _mongoRunner;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private Action<IServiceCollection>? _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<Program>()
|
||||
.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<RabbitMqOptions>("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<IConfiguration>();
|
||||
|
||||
HttpClient = _factory.CreateClient();
|
||||
Channel = CreateChannel();
|
||||
TestHarness = CreateHarness();
|
||||
|
||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
||||
|
||||
_mongoRunner = MongoDbRunner.Start();
|
||||
var mongoOptions = _factory.Services.GetRequiredService<IOptions<MongoOptions>>();
|
||||
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<IServiceCollection> 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<IServiceProvider, Task> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
await action(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var result = await action(scope.ServiceProvider);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<IdentityContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<IdentityContext>()));
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<IdentityContext, ValueTask> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<IdentityContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<IdentityContext, IMediator, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<IdentityContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<IdentityContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<IdentityContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<IdentityContext, ValueTask<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<IdentityContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<IdentityContext, IMediator, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<IdentityContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task InsertAsync<T>(params T[] entities) where T : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
foreach (var entity in entities) db.Set<T>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(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<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> FindAsync<T>(long id)
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||
}
|
||||
|
||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequest request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
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<IHttpContextAccessor>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ if (app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseMigration<PassengerDbContext>();
|
||||
app.UseMigration<PassengerDbContext>(env);
|
||||
app.UseCorrelationId();
|
||||
app.UseRouting();
|
||||
app.UseHttpMetrics();
|
||||
|
||||
@ -7,5 +7,5 @@ namespace Passenger.Passengers.Features.CompleteRegisterPassenger;
|
||||
|
||||
public record CompleteRegisterPassengerCommand(string PassportNumber, PassengerType PassengerType, int Age) : ICommand<PassengerResponseDto>
|
||||
{
|
||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
@ -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<PassengerResponseDto>
|
||||
{
|
||||
public FakePassengerResponseDto(long id)
|
||||
public FakePassengerResponseDto()
|
||||
{
|
||||
RuleFor(r => r.Id, _ => id);
|
||||
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Program> _factory;
|
||||
private MongoDbRunner _mongoRunner;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private Action<IServiceCollection>? _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<Program>()
|
||||
.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<RabbitMqOptions>("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<IConfiguration>();
|
||||
|
||||
HttpClient = _factory.CreateClient();
|
||||
Channel = CreateChannel();
|
||||
TestHarness = CreateHarness();
|
||||
|
||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
||||
|
||||
_mongoRunner = MongoDbRunner.Start();
|
||||
var mongoOptions = _factory.Services.GetRequiredService<IOptions<MongoOptions>>();
|
||||
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<IServiceCollection> 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<IServiceProvider, Task> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
await action(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var result = await action(scope.ServiceProvider);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<PassengerDbContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<PassengerDbContext>()));
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<PassengerDbContext, ValueTask> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<PassengerDbContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<PassengerDbContext, IMediator, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<PassengerDbContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<PassengerDbContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<PassengerDbContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<PassengerDbContext, ValueTask<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<PassengerDbContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<PassengerDbContext, IMediator, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<PassengerDbContext>(), sp.GetService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task InsertAsync<T>(params T[] entities) where T : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
foreach (var entity in entities) db.Set<T>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity>(TEntity entity) where TEntity : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(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<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> FindAsync<T>(long id)
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||
}
|
||||
|
||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequest request)
|
||||
{
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
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<IHttpContextAccessor>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class CompleteRegisterPassengerTests : IntegrationTestBase<Program, PassengerDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public CompleteRegisterPassengerTests(IntegrationTestFixture fixture)
|
||||
public CompleteRegisterPassengerTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_testHarness = _fixture.TestHarness;
|
||||
_testHarness = Fixture.TestHarness;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -25,12 +25,12 @@ public class CompleteRegisterPassengerTests : IClassFixture<IntegrationTestFixtu
|
||||
var userCreated = new FakeUserCreated().Generate();
|
||||
await _testHarness.Bus.Publish(userCreated);
|
||||
await _testHarness.Consumed.Any<UserCreated>();
|
||||
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();
|
||||
|
||||
@ -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<IntegrationTestFixture>
|
||||
public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbContext>
|
||||
{
|
||||
private readonly IntegrationTestFixture _fixture;
|
||||
private readonly ITestHarness _testHarness;
|
||||
private readonly GrpcChannel _channel;
|
||||
|
||||
public GetPassengerByIdTests(IntegrationTestFixture fixture)
|
||||
public GetPassengerByIdTests(IntegrationTestFixture<Program, PassengerDbContext> 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<IntegrationTestFixture>
|
||||
await _testHarness.Bus.Publish(userCreated);
|
||||
await _testHarness.Consumed.Any<UserCreated>();
|
||||
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<IntegrationTestFixture>
|
||||
await _testHarness.Bus.Publish(userCreated);
|
||||
await _testHarness.Consumed.Any<UserCreated>();
|
||||
var passengerEntity = FakePassengerCreated.Generate(userCreated);
|
||||
await _fixture.InsertAsync(passengerEntity);
|
||||
await Fixture.InsertAsync(passengerEntity);
|
||||
|
||||
var passengerGrpcClient = MagicOnionClient.Create<IPassengerGrpcService>(_channel);
|
||||
|
||||
@ -63,4 +73,16 @@ public class GetPassengerByIdTests : IClassFixture<IntegrationTestFixture>
|
||||
response?.Should().NotBeNull();
|
||||
response?.Id.Should().Be(passengerEntity.Id);
|
||||
}
|
||||
|
||||
private void MockPassengerGrpcServices(IServiceCollection services)
|
||||
{
|
||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mock = Substitute.For<IPassengerGrpcService>();
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<PassengerResponseDto>(new FakePassengerResponseDto().Generate()));
|
||||
|
||||
return mock;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user