mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-05-07 06:32:08 +08:00
.
This commit is contained in:
parent
a1b066da1f
commit
339315f75b
@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Ser
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Passenger\tests\IntegrationTest\Integration.Test.csproj", "{539364C8-88B1-48A3-8406-D0B19FF30509}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Passenger\tests\IntegrationTest\Integration.Test.csproj", "{539364C8-88B1-48A3-8406-D0B19FF30509}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Booking\tests\IntegrationTest\Integration.Test.csproj", "{50C66B53-ACA0-4AFF-8C5C-834D4EDA8FAC}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -96,6 +98,7 @@ Global
|
|||||||
{6B6603C8-D8B6-4775-9C7A-FFE6058070C2} = {C6EE337B-91EA-472A-87C7-E9528408CE59}
|
{6B6603C8-D8B6-4775-9C7A-FFE6058070C2} = {C6EE337B-91EA-472A-87C7-E9528408CE59}
|
||||||
{BC7871B8-BB18-4BCC-96A8-7324C11BF4A2} = {295284BA-D4E4-40AA-A2C2-BE36343F7DE6}
|
{BC7871B8-BB18-4BCC-96A8-7324C11BF4A2} = {295284BA-D4E4-40AA-A2C2-BE36343F7DE6}
|
||||||
{539364C8-88B1-48A3-8406-D0B19FF30509} = {C1EBE17D-BFAD-47DA-88EB-BB073B84593E}
|
{539364C8-88B1-48A3-8406-D0B19FF30509} = {C1EBE17D-BFAD-47DA-88EB-BB073B84593E}
|
||||||
|
{50C66B53-ACA0-4AFF-8C5C-834D4EDA8FAC} = {5185D5C5-0EAD-49D5-B405-93B939F3639B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{A2D7C5C4-5148-4C3E-BB12-B7A197A290F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A2D7C5C4-5148-4C3E-BB12-B7A197A290F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
@ -150,5 +153,9 @@ Global
|
|||||||
{539364C8-88B1-48A3-8406-D0B19FF30509}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{539364C8-88B1-48A3-8406-D0B19FF30509}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{539364C8-88B1-48A3-8406-D0B19FF30509}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{539364C8-88B1-48A3-8406-D0B19FF30509}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{539364C8-88B1-48A3-8406-D0B19FF30509}.Release|Any CPU.Build.0 = Release|Any CPU
|
{539364C8-88B1-48A3-8406-D0B19FF30509}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{50C66B53-ACA0-4AFF-8C5C-834D4EDA8FAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{50C66B53-ACA0-4AFF-8C5C-834D4EDA8FAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{50C66B53-ACA0-4AFF-8C5C-834D4EDA8FAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{50C66B53-ACA0-4AFF-8C5C-834D4EDA8FAC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace BuildingBlocks.Contracts.Grpc;
|
|||||||
public interface IPassengerGrpcService : IService<IPassengerGrpcService>
|
public interface IPassengerGrpcService : IService<IPassengerGrpcService>
|
||||||
{
|
{
|
||||||
UnaryResult<PassengerResponseDto> GetById(long id);
|
UnaryResult<PassengerResponseDto> GetById(long id);
|
||||||
|
UnaryResult<string> TTT(long id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ using BuildingBlocks.Mapster;
|
|||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
using BuildingBlocks.OpenTelemetry;
|
using BuildingBlocks.OpenTelemetry;
|
||||||
using BuildingBlocks.Swagger;
|
using BuildingBlocks.Swagger;
|
||||||
using BuildingBlocks.Utils;
|
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@ -66,7 +65,7 @@ if (app.Environment.IsDevelopment())
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.UseSerilogRequestLogging();
|
app.UseSerilogRequestLogging();
|
||||||
app.UseMigrations(env);
|
app.UseMigrations();
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
@ -84,3 +83,5 @@ app.UseEndpoints(endpoints =>
|
|||||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
public partial class Program {}
|
||||||
|
|||||||
@ -6,18 +6,32 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.46.0" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.46.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.21.1" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.46.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Data\Migrations"/>
|
<Folder Include="Data\Migrations" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj"/>
|
<Protobuf Include="Protos\foo.proto" GrpcServices="Client">
|
||||||
|
<Link>Protos\foo.proto</Link>
|
||||||
|
</Protobuf>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -26,47 +26,44 @@ public class CreateBookingCommandHandler : IRequestHandler<CreateBookingCommand,
|
|||||||
_eventStoreDbRepository = eventStoreDbRepository;
|
_eventStoreDbRepository = eventStoreDbRepository;
|
||||||
|
|
||||||
var channelFlight = GrpcChannel.ForAddress(grpcOptions.Value.FlightAddress);
|
var channelFlight = GrpcChannel.ForAddress(grpcOptions.Value.FlightAddress);
|
||||||
_flightGrpcService =
|
_flightGrpcService = new Lazy<IFlightGrpcService>(() => MagicOnionClient.Create<IFlightGrpcService>(channelFlight)).Value;
|
||||||
new Lazy<IFlightGrpcService>(() => MagicOnionClient.Create<IFlightGrpcService>(channelFlight)).Value;
|
|
||||||
|
|
||||||
var channelPassenger = GrpcChannel.ForAddress(grpcOptions.Value.PassengerAddress);
|
var channelPassenger = GrpcChannel.ForAddress(grpcOptions.Value.PassengerAddress);
|
||||||
_passengerGrpcService =
|
_passengerGrpcService = new Lazy<IPassengerGrpcService>(() => MagicOnionClient.Create<IPassengerGrpcService>(channelPassenger)).Value;
|
||||||
new Lazy<IPassengerGrpcService>(() => MagicOnionClient.Create<IPassengerGrpcService>(channelPassenger))
|
|
||||||
.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ulong> Handle(CreateBookingCommand command,
|
public async Task<ulong> Handle(CreateBookingCommand command,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Guard.Against.Null(command, nameof(command));
|
// Guard.Against.Null(command, nameof(command));
|
||||||
|
//
|
||||||
var flight = await _flightGrpcService.GetById(command.FlightId);
|
// var flight = await _flightGrpcService.GetById(command.FlightId);
|
||||||
|
//
|
||||||
if (flight is null)
|
// if (flight is null)
|
||||||
throw new FlightNotFoundException();
|
// throw new FlightNotFoundException();
|
||||||
|
|
||||||
var passenger = await _passengerGrpcService.GetById(command.PassengerId);
|
var passenger = await _passengerGrpcService.GetById(command.PassengerId);
|
||||||
|
|
||||||
var emptySeat = (await _flightGrpcService.GetAvailableSeats(command.FlightId))?.First();
|
// var emptySeat = (await _flightGrpcService.GetAvailableSeats(command.FlightId))?.First();
|
||||||
|
//
|
||||||
|
// var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
|
||||||
|
//
|
||||||
|
// if (reservation is not null && !reservation.IsDeleted)
|
||||||
|
// throw new BookingAlreadyExistException();
|
||||||
|
//
|
||||||
|
// var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
|
||||||
|
// flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,
|
||||||
|
// flight.ArriveAirportId, flight.FlightDate, flight.Price, command.Description, emptySeat?.SeatNumber));
|
||||||
|
//
|
||||||
|
// await _flightGrpcService.ReserveSeat(new ReserveSeatRequestDto
|
||||||
|
// {
|
||||||
|
// FlightId = flight.Id, SeatNumber = emptySeat?.SeatNumber
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// var result = await _eventStoreDbRepository.Add(
|
||||||
|
// aggrigate,
|
||||||
|
// cancellationToken);
|
||||||
|
|
||||||
var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
|
return 2;
|
||||||
|
|
||||||
if (reservation is not null && !reservation.IsDeleted)
|
|
||||||
throw new BookingAlreadyExistException();
|
|
||||||
|
|
||||||
var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
|
|
||||||
flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,
|
|
||||||
flight.ArriveAirportId, flight.FlightDate, flight.Price, command.Description, emptySeat?.SeatNumber));
|
|
||||||
|
|
||||||
await _flightGrpcService.ReserveSeat(new ReserveSeatRequestDto
|
|
||||||
{
|
|
||||||
FlightId = flight.Id, SeatNumber = emptySeat?.SeatNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
var result = await _eventStoreDbRepository.Add(
|
|
||||||
aggrigate,
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,13 +10,11 @@ namespace Booking.Extensions;
|
|||||||
|
|
||||||
public static class MigrationsExtensions
|
public static class MigrationsExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app, IWebHostEnvironment env)
|
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
if (!env.IsEnvironment("test"))
|
MigrateDatabase(app.ApplicationServices);
|
||||||
{
|
SeedData(app.ApplicationServices);
|
||||||
MigrateDatabase(app.ApplicationServices);
|
|
||||||
SeedData(app.ApplicationServices);
|
|
||||||
}
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,108 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
|
using BuildingBlocks.Contracts.Grpc;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
using GrpcSamples;
|
||||||
|
using Integration.Test.Fakes;
|
||||||
|
using MagicOnion;
|
||||||
|
using MagicOnion.Client;
|
||||||
|
using MassTransit.Testing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Integration.Test.Booking.Features;
|
||||||
|
|
||||||
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
|
public class CreateBookingTests
|
||||||
|
{
|
||||||
|
private readonly IntegrationTestFixture _fixture;
|
||||||
|
private readonly ITestHarness _testHarness;
|
||||||
|
private readonly GrpcChannel _channel;
|
||||||
|
private FooService.FooServiceClient _fooServiceClient;
|
||||||
|
|
||||||
|
public CreateBookingTests(IntegrationTestFixture fixture)
|
||||||
|
{
|
||||||
|
_fixture = fixture;
|
||||||
|
_testHarness = fixture.TestHarness;
|
||||||
|
_channel = fixture.Channel;
|
||||||
|
_fooServiceClient = Substitute.For<FooService.FooServiceClient>();
|
||||||
|
|
||||||
|
_fooServiceClient.GetFoo(Arg.Any<FooRequest>())
|
||||||
|
.Returns(new FooResponse() {Message = "vvvvvvvvvvv"});
|
||||||
|
|
||||||
|
fixture.RegisterTestServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton(_fooServiceClient);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task should_create_booking_currectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var command = new FakeCreateBookingCommand().Generate();
|
||||||
|
|
||||||
|
// var passengerGrpcService = MagicOnionClient.Create<IPassengerGrpcService2>(_channel) as IPassengerGrpcService2;
|
||||||
|
// var b = await passengerGrpcService.GetById(1);
|
||||||
|
|
||||||
|
// var client = new FooService.FooServiceClient(_channel);
|
||||||
|
//
|
||||||
|
// var b = client.GetFoo(new FooRequest {Message = "tesssssssssst"});
|
||||||
|
_fooServiceClient = Substitute.For<FooService.FooServiceClient>();
|
||||||
|
|
||||||
|
_fooServiceClient.GetFooAsync(Arg.Any<FooRequest>())
|
||||||
|
.Returns(new AsyncUnaryCall<FooResponse>(Task.FromResult(new FooResponse() {Message = "uuuuuuuuu"}), null, null, null, null));
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _fixture.SendAsync(command);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.Should();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// [Fact]
|
||||||
|
// public async Task should_retrive_a_passenger_by_id_currectly()
|
||||||
|
// {
|
||||||
|
// // Arrange
|
||||||
|
// var userCreated = new FakeUserCreated().Generate();
|
||||||
|
// await _testHarness.Bus.Publish(userCreated);
|
||||||
|
// await _testHarness.Consumed.Any<UserCreated>();
|
||||||
|
// var passengerEntity = FakePassengerCreated.Generate(userCreated);
|
||||||
|
// await _fixture.InsertAsync(passengerEntity);
|
||||||
|
//
|
||||||
|
// var query = new GetPassengerQueryById(passengerEntity.Id);
|
||||||
|
//
|
||||||
|
// // Act
|
||||||
|
// var response = await _fixture.SendAsync(query);
|
||||||
|
//
|
||||||
|
// // Assert
|
||||||
|
// response.Should().NotBeNull();
|
||||||
|
// response?.Id.Should().Be(passengerEntity.Id);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// [Fact]
|
||||||
|
// public async Task should_retrive_a_passenger_by_id_from_grpc_service()
|
||||||
|
// {
|
||||||
|
// // Arrange
|
||||||
|
// var userCreated = new FakeUserCreated().Generate();
|
||||||
|
// await _testHarness.Bus.Publish(userCreated);
|
||||||
|
// await _testHarness.Consumed.Any<UserCreated>();
|
||||||
|
// var passengerEntity = FakePassengerCreated.Generate(userCreated);
|
||||||
|
// await _fixture.InsertAsync(passengerEntity);
|
||||||
|
//
|
||||||
|
// var passengerGrpcClient = MagicOnionClient.Create<IPassengerGrpcService>(_channel);
|
||||||
|
//
|
||||||
|
// // Act
|
||||||
|
// var response = await passengerGrpcClient.GetById(1111111111);
|
||||||
|
//
|
||||||
|
// // Assert
|
||||||
|
// response?.Should().NotBeNull();
|
||||||
|
// response?.Id.Should().Be(passengerEntity.Id);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using AutoBogus;
|
||||||
|
using Booking.Booking.Features.CreateBooking;
|
||||||
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
|
||||||
|
namespace Integration.Test.Fakes;
|
||||||
|
|
||||||
|
public sealed class FakeCreateBookingCommand : AutoFaker<CreateBookingCommand>
|
||||||
|
{
|
||||||
|
public FakeCreateBookingCommand()
|
||||||
|
{
|
||||||
|
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
||||||
|
RuleFor(r => r.FlightId, _ => 1);
|
||||||
|
RuleFor(r => r.PassengerId, _ => 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -77,14 +77,12 @@ app.UseSerilogRequestLogging();
|
|||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
app.UseMigrations(env);
|
app.UseMigrations();
|
||||||
app.UseProblemDetails();
|
app.UseProblemDetails();
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
|
|||||||
@ -11,13 +11,10 @@ namespace Flight.Extensions;
|
|||||||
|
|
||||||
public static class MigrationsExtensions
|
public static class MigrationsExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app, IWebHostEnvironment env)
|
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
if (!env.IsEnvironment("test"))
|
MigrateDatabase(app.ApplicationServices);
|
||||||
{
|
SeedData(app.ApplicationServices);
|
||||||
MigrateDatabase(app.ApplicationServices);
|
|
||||||
SeedData(app.ApplicationServices);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,13 +8,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Aircraft.Features;
|
namespace Integration.Test.Aircraft.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class CreateAircraftTests
|
public class CreateAircraftTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public CreateAircraftTests(TestFixture fixture)
|
public CreateAircraftTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = fixture.TestHarness;
|
_testHarness = fixture.TestHarness;
|
||||||
|
|||||||
@ -8,13 +8,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Airport.Features;
|
namespace Integration.Test.Airport.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class CreateAirportTests
|
public class CreateAirportTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public CreateAirportTests(TestFixture fixture)
|
public CreateAirportTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = fixture.TestHarness;
|
_testHarness = fixture.TestHarness;
|
||||||
|
|||||||
@ -8,13 +8,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class CreateFlightTests
|
public class CreateFlightTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public CreateFlightTests(TestFixture fixture)
|
public CreateFlightTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = _fixture.TestHarness;
|
_testHarness = _fixture.TestHarness;
|
||||||
|
|||||||
@ -11,13 +11,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class DeleteFlightTests
|
public class DeleteFlightTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public DeleteFlightTests(TestFixture fixture)
|
public DeleteFlightTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = fixture.TestHarness;
|
_testHarness = fixture.TestHarness;
|
||||||
|
|||||||
@ -10,13 +10,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class GetAvailableFlightsTests
|
public class GetAvailableFlightsTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
|
||||||
public GetAvailableFlightsTests(TestFixture fixture)
|
public GetAvailableFlightsTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_channel = fixture.Channel;
|
_channel = fixture.Channel;
|
||||||
|
|||||||
@ -10,13 +10,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class GetAvailableSeatsTests
|
public class GetAvailableSeatsTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
|
||||||
public GetAvailableSeatsTests(TestFixture fixture)
|
public GetAvailableSeatsTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_channel = fixture.Channel;
|
_channel = fixture.Channel;
|
||||||
|
|||||||
@ -9,13 +9,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class GetFlightByIdTests
|
public class GetFlightByIdTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
|
||||||
public GetFlightByIdTests(TestFixture fixture)
|
public GetFlightByIdTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_channel = fixture.Channel;
|
_channel = fixture.Channel;
|
||||||
|
|||||||
@ -9,13 +9,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class ReserveSeatTests
|
public class ReserveSeatTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
|
||||||
public ReserveSeatTests(TestFixture fixture)
|
public ReserveSeatTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_channel = fixture.Channel;
|
_channel = fixture.Channel;
|
||||||
|
|||||||
@ -8,13 +8,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class UpdateFlightTests
|
public class UpdateFlightTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public UpdateFlightTests(TestFixture fixture)
|
public UpdateFlightTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = _fixture.TestHarness;
|
_testHarness = _fixture.TestHarness;
|
||||||
|
|||||||
@ -1,282 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Channels;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BuildingBlocks.Domain.Model;
|
|
||||||
using BuildingBlocks.EFCore;
|
|
||||||
using BuildingBlocks.MassTransit;
|
|
||||||
using BuildingBlocks.Web;
|
|
||||||
using Flight.Data;
|
|
||||||
using Flight.Data.Seed;
|
|
||||||
using Flight.GrpcServer;
|
|
||||||
using Grpc.Net.Client;
|
|
||||||
using MassTransit;
|
|
||||||
using MassTransit.Testing;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.TestHost;
|
|
||||||
using NSubstitute;
|
|
||||||
using Respawn;
|
|
||||||
using Serilog;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Integration.Test;
|
|
||||||
|
|
||||||
[CollectionDefinition(nameof(TestFixture))]
|
|
||||||
public class TestFixtureCollection : ICollectionFixture<TestFixture>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
|
|
||||||
// ref: https://github.com/jbogard/ContosoUniversityDotNetCore-Pages/blob/master/ContosoUniversity.IntegrationTests/SliceFixture.cs
|
|
||||||
// ref: https://github.com/jasontaylordev/CleanArchitecture/blob/main/tests/Application.IntegrationTests/Testing.cs
|
|
||||||
// ref: https://github.com/MassTransit/MassTransit/blob/00d6992286911a437b63b93c89a56e920b053c11/src/MassTransit.TestFramework/InMemoryTestFixture.cs
|
|
||||||
public class TestFixture : IAsyncLifetime
|
|
||||||
{
|
|
||||||
private Checkpoint _checkpoint;
|
|
||||||
private IConfiguration _configuration;
|
|
||||||
private WebApplicationFactory<Program> _factory;
|
|
||||||
private IServiceScopeFactory _scopeFactory;
|
|
||||||
private HttpClient _httpClient;
|
|
||||||
private ITestHarness _testHarness;
|
|
||||||
private GrpcChannel _channel;
|
|
||||||
|
|
||||||
public ITestHarness TestHarness => _testHarness;
|
|
||||||
public GrpcChannel Channel => _channel;
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "test");
|
|
||||||
|
|
||||||
_factory = new WebApplicationFactory<Program>()
|
|
||||||
.WithWebHostBuilder(builder => builder.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.RemoveAll(typeof(IHostedService));
|
|
||||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
|
||||||
services.ReplaceScoped<IDataSeeder, FlightDataSeeder>();
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
_testHarness = _factory.Services.GetTestHarness();
|
|
||||||
|
|
||||||
await _testHarness.Start();
|
|
||||||
|
|
||||||
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
|
|
||||||
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
|
|
||||||
_httpClient = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
|
|
||||||
|
|
||||||
_channel = GrpcChannel.ForAddress(_httpClient.BaseAddress!, new GrpcChannelOptions
|
|
||||||
{
|
|
||||||
HttpClient = _httpClient
|
|
||||||
});
|
|
||||||
|
|
||||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
|
||||||
|
|
||||||
await EnsureDatabaseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DisposeAsync()
|
|
||||||
{
|
|
||||||
_testHarness.Cancel();
|
|
||||||
await _factory.DisposeAsync();
|
|
||||||
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 HttpClient CreateClient() => _httpClient;
|
|
||||||
|
|
||||||
public async Task ExecuteScopeAsync(Func<IServiceProvider, Task> action)
|
|
||||||
{
|
|
||||||
using var scope = _scopeFactory.CreateScope();
|
|
||||||
await action(scope.ServiceProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
|
||||||
{
|
|
||||||
using var scope = _scopeFactory.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 async Task EnsureDatabaseAsync()
|
|
||||||
{
|
|
||||||
using var scope = _scopeFactory.CreateScope();
|
|
||||||
|
|
||||||
var context = scope.ServiceProvider.GetRequiredService<FlightDbContext>();
|
|
||||||
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
|
||||||
|
|
||||||
await context.Database.MigrateAsync();
|
|
||||||
|
|
||||||
foreach (var seeder in seeders) await seeder.SeedAllAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
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", 5004);
|
|
||||||
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
|
|
||||||
|
|
||||||
return httpContextAccessorMock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -60,7 +60,7 @@ if (app.Environment.IsDevelopment())
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.UseSerilogRequestLogging();
|
app.UseSerilogRequestLogging();
|
||||||
app.UseMigrations(env);
|
app.UseMigrations();
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
|
|||||||
@ -11,13 +11,10 @@ namespace Identity.Extensions;
|
|||||||
|
|
||||||
public static class MigrationsExtensions
|
public static class MigrationsExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app, IWebHostEnvironment env)
|
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
if (!env.IsEnvironment("test"))
|
MigrateDatabase(app.ApplicationServices);
|
||||||
{
|
SeedData(app.ApplicationServices);
|
||||||
MigrateDatabase(app.ApplicationServices);
|
|
||||||
SeedData(app.ApplicationServices);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,13 +8,13 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Identity.Features;
|
namespace Integration.Test.Identity.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class RegisterNewUserTests
|
public class RegisterNewUserTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public RegisterNewUserTests(TestFixture fixture)
|
public RegisterNewUserTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = _fixture.TestHarness;
|
_testHarness = _fixture.TestHarness;
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@ -1,274 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BuildingBlocks.Domain.Model;
|
|
||||||
using BuildingBlocks.EFCore;
|
|
||||||
using BuildingBlocks.MassTransit;
|
|
||||||
using BuildingBlocks.Web;
|
|
||||||
using Grpc.Net.Client;
|
|
||||||
using Identity.Data;
|
|
||||||
using MassTransit;
|
|
||||||
using MassTransit.Testing;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using NSubstitute;
|
|
||||||
using Respawn;
|
|
||||||
using Serilog;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Integration.Test;
|
|
||||||
|
|
||||||
[CollectionDefinition(nameof(TestFixture))]
|
|
||||||
public class TestFixtureCollection : ICollectionFixture<TestFixture>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
|
|
||||||
// ref: https://github.com/jbogard/ContosoUniversityDotNetCore-Pages/blob/master/ContosoUniversity.IntegrationTests/SliceFixture.cs
|
|
||||||
// ref: https://github.com/jasontaylordev/CleanArchitecture/blob/main/tests/Application.IntegrationTests/Testing.cs
|
|
||||||
// ref: https://github.com/MassTransit/MassTransit/blob/00d6992286911a437b63b93c89a56e920b053c11/src/MassTransit.TestFramework/InMemoryTestFixture.cs
|
|
||||||
public class TestFixture : IAsyncLifetime
|
|
||||||
{
|
|
||||||
private Checkpoint _checkpoint;
|
|
||||||
private IConfiguration _configuration;
|
|
||||||
private WebApplicationFactory<Program> _factory;
|
|
||||||
private HttpClient _httpClient;
|
|
||||||
private IServiceScopeFactory _scopeFactory;
|
|
||||||
private GrpcChannel _channel;
|
|
||||||
public ITestHarness TestHarness { get; private set; }
|
|
||||||
public GrpcChannel Channel => _channel;
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "test");
|
|
||||||
|
|
||||||
_factory = new WebApplicationFactory<Program>()
|
|
||||||
.WithWebHostBuilder(builder => builder.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.RemoveAll(typeof(IHostedService));
|
|
||||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
|
||||||
services.ReplaceScoped<IDataSeeder, IdentityDataSeeder>();
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
TestHarness = _factory.Services.GetTestHarness();
|
|
||||||
|
|
||||||
await TestHarness.Start();
|
|
||||||
|
|
||||||
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
|
|
||||||
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
|
|
||||||
_httpClient = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
|
|
||||||
|
|
||||||
_channel = GrpcChannel.ForAddress(_httpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = _httpClient});
|
|
||||||
|
|
||||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
|
||||||
|
|
||||||
await EnsureDatabaseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DisposeAsync()
|
|
||||||
{
|
|
||||||
TestHarness.Cancel();
|
|
||||||
await _factory.DisposeAsync();
|
|
||||||
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 HttpClient CreateClient()
|
|
||||||
{
|
|
||||||
return _httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ExecuteScopeAsync(Func<IServiceProvider, Task> action)
|
|
||||||
{
|
|
||||||
using var scope = _scopeFactory.CreateScope();
|
|
||||||
await action(scope.ServiceProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
|
||||||
{
|
|
||||||
using var scope = _scopeFactory.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 async Task EnsureDatabaseAsync()
|
|
||||||
{
|
|
||||||
using var scope = _scopeFactory.CreateScope();
|
|
||||||
|
|
||||||
var context = scope.ServiceProvider.GetRequiredService<IdentityContext>();
|
|
||||||
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
|
||||||
|
|
||||||
await context.Database.MigrateAsync();
|
|
||||||
|
|
||||||
foreach (var seeder in seeders) await seeder.SeedAllAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
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", 6005);
|
|
||||||
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
|
|
||||||
|
|
||||||
return httpContextAccessorMock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -17,8 +17,10 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
|||||||
using Passenger;
|
using Passenger;
|
||||||
using Passenger.Data;
|
using Passenger.Data;
|
||||||
using Passenger.Extensions;
|
using Passenger.Extensions;
|
||||||
|
using Passenger.Services;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Server;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
@ -39,6 +41,7 @@ builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly);
|
|||||||
builder.Services.AddCustomProblemDetails();
|
builder.Services.AddCustomProblemDetails();
|
||||||
builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly);
|
builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly);
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
builder.Services.AddScoped<IGreeter, Greeter>();
|
||||||
|
|
||||||
builder.Services.AddTransient<IEventMapper, EventMapper>();
|
builder.Services.AddTransient<IEventMapper, EventMapper>();
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ if (app.Environment.IsDevelopment())
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.UseSerilogRequestLogging();
|
app.UseSerilogRequestLogging();
|
||||||
app.UseMigrations(env);
|
app.UseMigrations();
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
@ -74,6 +77,7 @@ app.UseEndpoints(endpoints =>
|
|||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
endpoints.MapMetrics();
|
endpoints.MapMetrics();
|
||||||
|
endpoints.MapGrpcService<TesterService>();
|
||||||
endpoints.MapMagicOnionService();
|
endpoints.MapMagicOnionService();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,10 @@ namespace Passenger.Extensions;
|
|||||||
|
|
||||||
public static class MigrationsExtensions
|
public static class MigrationsExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app, IWebHostEnvironment env)
|
public static IApplicationBuilder UseMigrations(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
if (!env.IsEnvironment("test"))
|
MigrateDatabase(app.ApplicationServices);
|
||||||
{
|
SeedData(app.ApplicationServices);
|
||||||
MigrateDatabase(app.ApplicationServices);
|
|
||||||
SeedData(app.ApplicationServices);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,4 +21,9 @@ public class PassengerGrpcService : ServiceBase<IPassengerGrpcService>, IPasseng
|
|||||||
var result = await _mediator.Send(new GetPassengerQueryById(id));
|
var result = await _mediator.Send(new GetPassengerQueryById(id));
|
||||||
return result.Adapt<PassengerResponseDto>();
|
return result.Adapt<PassengerResponseDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UnaryResult<string> TTT(long id)
|
||||||
|
{
|
||||||
|
return new UnaryResult<string>("hiiiiiiiiiiiiiiii");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.21.1" />
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.46.0" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.46.0" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.46.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@ -15,10 +22,18 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Data\Migrations" />
|
<Folder Include="Data\Migrations" />
|
||||||
<Folder Include="Extensions" />
|
<Folder Include="Extensions" />
|
||||||
|
<Folder Include="Protos" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Protos\test.proto" GrpcServices="Both">
|
||||||
|
<Link>Protos\test.proto</Link>
|
||||||
|
</Protobuf>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -0,0 +1,61 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using Server;
|
||||||
|
using Test;
|
||||||
|
|
||||||
|
namespace Passenger.Services;
|
||||||
|
|
||||||
|
public class TesterService: Tester.TesterBase
|
||||||
|
{
|
||||||
|
private readonly IGreeter _greeter;
|
||||||
|
|
||||||
|
public TesterService(IGreeter greeter)
|
||||||
|
{
|
||||||
|
_greeter = greeter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<HelloReply> SayHelloUnary(HelloRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
var message = _greeter.Greet(request.Name);
|
||||||
|
return Task.FromResult(new HelloReply { Message = message });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task SayHelloServerStreaming(HelloRequest request,
|
||||||
|
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
while (!context.CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var message = _greeter.Greet($"{request.Name} {++i}");
|
||||||
|
await responseStream.WriteAsync(new HelloReply { Message = message });
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<HelloReply> SayHelloClientStreaming(
|
||||||
|
IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var names = new List<string>();
|
||||||
|
|
||||||
|
await foreach (var request in requestStream.ReadAllAsync())
|
||||||
|
{
|
||||||
|
names.Add(request.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = _greeter.Greet(string.Join(", ", names));
|
||||||
|
return new HelloReply { Message = message };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task SayHelloBidirectionalStreaming(
|
||||||
|
IAsyncStreamReader<HelloRequest> requestStream,
|
||||||
|
IServerStreamWriter<HelloReply> responseStream,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
await foreach (var request in requestStream.ReadAllAsync())
|
||||||
|
{
|
||||||
|
await responseStream.WriteAsync(
|
||||||
|
new HelloReply { Message = _greeter.Greet(request.Name) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using BuildingBlocks.MassTransit;
|
||||||
|
using BuildingBlocks.Web;
|
||||||
|
using MassTransit;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using NSubstitute;
|
||||||
|
using Respawn;
|
||||||
|
|
||||||
|
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||||
|
{
|
||||||
|
public Checkpoint Checkpoint { get; set; }
|
||||||
|
public IConfiguration Configuration => Services.GetRequiredService<IConfiguration>();
|
||||||
|
public Action<IServiceCollection>? TestRegistrationServices { get; set; }
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
//https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests#set-the-environment
|
||||||
|
//https://stackoverflow.com/questions/43927955/should-getenvironmentvariable-work-in-xunit-test/43951218
|
||||||
|
|
||||||
|
//we could read env from our test launch setting or we can set it directly here
|
||||||
|
builder.UseEnvironment("test");
|
||||||
|
|
||||||
|
//The test app's builder.ConfigureTestServices callback is executed after the app's Startup.ConfigureServices code is executed.
|
||||||
|
builder.ConfigureTestServices(services =>
|
||||||
|
{
|
||||||
|
services.RemoveAll(typeof(IHostedService));
|
||||||
|
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
||||||
|
TestRegistrationServices?.Invoke(services);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
||||||
|
|
||||||
|
TestRegistrationServices?.Invoke(services);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.UseDefaultServiceProvider((env, c) =>
|
||||||
|
{
|
||||||
|
// Handling Captive Dependency Problem
|
||||||
|
// https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/
|
||||||
|
// https://blog.ploeh.dk/2014/06/02/captive-dependency/
|
||||||
|
if (env.HostingEnvironment.IsEnvironment("test") || env.HostingEnvironment.IsDevelopment())
|
||||||
|
c.ValidateScopes = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,6 @@ public static class FakePassengerCreated
|
|||||||
{
|
{
|
||||||
public static global::Passenger.Passengers.Models.Passenger Generate(UserCreated userCreated)
|
public static global::Passenger.Passengers.Models.Passenger Generate(UserCreated userCreated)
|
||||||
{
|
{
|
||||||
return global::Passenger.Passengers.Models.Passenger.Create(SnowFlakIdGenerator.NewId(), userCreated.Name, userCreated.PassportNumber);
|
return global::Passenger.Passengers.Models.Passenger.Create(1000, userCreated.Name, userCreated.PassportNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ public class FakeUserCreated : AutoFaker<UserCreated>
|
|||||||
{
|
{
|
||||||
public FakeUserCreated()
|
public FakeUserCreated()
|
||||||
{
|
{
|
||||||
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
RuleFor(r => r.Id, _ => _.Random.Number(50, 100000));
|
||||||
RuleFor(r => r.Name, _ => "Meysam");
|
RuleFor(r => r.Name, _ => "Meysam");
|
||||||
RuleFor(r => r.PassportNumber, _ => "1299878");
|
RuleFor(r => r.PassportNumber, _ => "1299878");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,33 @@
|
|||||||
using System.Threading.Tasks;
|
// using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
// using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using Integration.Test.Fakes;
|
// using Integration.Test.Fakes;
|
||||||
using MassTransit.Testing;
|
// using MassTransit.Testing;
|
||||||
using Xunit;
|
// using Xunit;
|
||||||
|
//
|
||||||
namespace Integration.Test.Identity.Features;
|
// namespace Integration.Test.Identity.Features;
|
||||||
|
//
|
||||||
[Collection(nameof(TestFixture))]
|
// [Collection(nameof(TestFixture))]
|
||||||
public class RegisterNewUserConsumerTests
|
// public class RegisterNewUserConsumerTests
|
||||||
{
|
// {
|
||||||
private readonly TestFixture _fixture;
|
// private readonly TestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
// private readonly ITestHarness _testHarness;
|
||||||
|
//
|
||||||
public RegisterNewUserConsumerTests(TestFixture fixture)
|
// public RegisterNewUserConsumerTests(TestFixture fixture)
|
||||||
{
|
// {
|
||||||
_fixture = fixture;
|
// _fixture = fixture;
|
||||||
_testHarness = _fixture.TestHarness;
|
// _testHarness = _fixture.TestHarness;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
[Fact]
|
// [Fact]
|
||||||
public async Task should_register_new_user_consume_handler_consume_user_created_message()
|
// public async Task should_register_new_user_consume_handler_consume_user_created_message()
|
||||||
{
|
// {
|
||||||
// // Arrange
|
// // // Arrange
|
||||||
var message = new FakeUserCreated().Generate();
|
// var message = new FakeUserCreated().Generate();
|
||||||
|
//
|
||||||
// // Act
|
// // // Act
|
||||||
await _testHarness.Bus.Publish(message);
|
// await _testHarness.Bus.Publish(message);
|
||||||
|
//
|
||||||
// // Assert
|
// // // Assert
|
||||||
await _testHarness.Consumed.Any<UserCreated>();
|
// await _testHarness.Consumed.Any<UserCreated>();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@ -1,124 +1,79 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Domain.Model;
|
using BuildingBlocks.Domain.Model;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
using BuildingBlocks.MassTransit;
|
|
||||||
using BuildingBlocks.Web;
|
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using MassTransit;
|
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Passenger.Data;
|
using Passenger.Data;
|
||||||
using Respawn;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Integration.Test;
|
namespace Integration.Test;
|
||||||
|
|
||||||
[CollectionDefinition(nameof(TestFixture))]
|
[CollectionDefinition(nameof(IntegrationTestFixture))]
|
||||||
public class TestFixtureCollection : ICollectionFixture<TestFixture>
|
public class SliceFixtureCollection : ICollectionFixture<IntegrationTestFixture> { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
|
public class IntegrationTestFixture : IAsyncLifetime
|
||||||
// ref: https://github.com/jbogard/ContosoUniversityDotNetCore-Pages/blob/master/ContosoUniversity.IntegrationTests/SliceFixture.cs
|
|
||||||
// ref: https://github.com/jasontaylordev/CleanArchitecture/blob/main/tests/Application.IntegrationTests/Testing.cs
|
|
||||||
// ref: https://github.com/MassTransit/MassTransit/blob/00d6992286911a437b63b93c89a56e920b053c11/src/MassTransit.TestFramework/InMemoryTestFixture.cs
|
|
||||||
public class TestFixture : IAsyncLifetime
|
|
||||||
{
|
{
|
||||||
private Checkpoint _checkpoint;
|
private readonly CustomWebApplicationFactory _factory;
|
||||||
private IConfiguration _configuration;
|
|
||||||
private WebApplicationFactory<Program> _factory;
|
|
||||||
private HttpClient _httpClient;
|
|
||||||
private IServiceScopeFactory _scopeFactory;
|
|
||||||
private GrpcChannel _channel;
|
|
||||||
public ITestHarness TestHarness { get; private set; }
|
|
||||||
public GrpcChannel Channel => _channel;
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
public IntegrationTestFixture()
|
||||||
{
|
{
|
||||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "test");
|
// Ref: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0#basic-tests-with-the-default-webapplicationfactory
|
||||||
|
_factory = new CustomWebApplicationFactory();
|
||||||
_factory = new WebApplicationFactory<Program>()
|
|
||||||
.WithWebHostBuilder(builder => builder.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.RemoveAll(typeof(IHostedService));
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
TestHarness = _factory.Services.GetTestHarness();
|
|
||||||
|
|
||||||
await TestHarness.Start();
|
|
||||||
|
|
||||||
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
|
|
||||||
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
|
|
||||||
_httpClient = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
|
|
||||||
|
|
||||||
_channel = GrpcChannel.ForAddress(_httpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = _httpClient});
|
|
||||||
|
|
||||||
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
|
|
||||||
|
|
||||||
await EnsureDatabaseAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisposeAsync()
|
public IServiceProvider ServiceProvider => _factory.Services;
|
||||||
{
|
public IConfiguration Configuration => _factory.Configuration;
|
||||||
TestHarness.Cancel();
|
public HttpClient HttpClient => _factory.CreateClient();
|
||||||
await _factory.DisposeAsync();
|
public ITestHarness TestHarness => CreateHarness();
|
||||||
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
|
public GrpcChannel Channel => CreateChannel();
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://github.com/trbenning/serilog-sinks-xunit
|
// ref: https://github.com/trbenning/serilog-sinks-xunit
|
||||||
public ILogger CreateLogger(ITestOutputHelper output)
|
public ILogger CreateLogger(ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
if (output != null)
|
if (output != null)
|
||||||
|
{
|
||||||
return new LoggerConfiguration()
|
return new LoggerConfiguration()
|
||||||
.WriteTo.TestOutput(output)
|
.WriteTo.TestOutput(output)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient CreateClient()
|
public void RegisterTestServices(Action<IServiceCollection> services) => _factory.TestRegistrationServices = services;
|
||||||
|
|
||||||
|
public virtual Task InitializeAsync()
|
||||||
{
|
{
|
||||||
return _httpClient;
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Configuration?.GetConnectionString("DefaultConnection")))
|
||||||
|
await _factory.Checkpoint.Reset(Configuration?.GetConnectionString("DefaultConnection"));
|
||||||
|
|
||||||
|
await _factory.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteScopeAsync(Func<IServiceProvider, Task> action)
|
public async Task ExecuteScopeAsync(Func<IServiceProvider, Task> action)
|
||||||
{
|
{
|
||||||
using var scope = _scopeFactory.CreateScope();
|
using var scope = ServiceProvider.CreateScope();
|
||||||
await action(scope.ServiceProvider);
|
await action(scope.ServiceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
|
||||||
{
|
{
|
||||||
using var scope = _scopeFactory.CreateScope();
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
|
||||||
var result = await action(scope.ServiceProvider);
|
var result = await action(scope.ServiceProvider);
|
||||||
|
|
||||||
@ -247,9 +202,33 @@ public class TestFixture : IAsyncLifetime
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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", 5000);
|
||||||
|
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
|
||||||
|
|
||||||
|
return httpContextAccessorMock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITestHarness CreateHarness()
|
||||||
|
{
|
||||||
|
var harness = ServiceProvider.GetTestHarness();
|
||||||
|
harness.Start().GetAwaiter().GetResult();
|
||||||
|
return harness;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GrpcChannel CreateChannel()
|
||||||
|
{
|
||||||
|
return GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = HttpClient});
|
||||||
|
}
|
||||||
|
|
||||||
private async Task EnsureDatabaseAsync()
|
private async Task EnsureDatabaseAsync()
|
||||||
{
|
{
|
||||||
using var scope = _scopeFactory.CreateScope();
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
|
||||||
var context = scope.ServiceProvider.GetRequiredService<PassengerDbContext>();
|
var context = scope.ServiceProvider.GetRequiredService<PassengerDbContext>();
|
||||||
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
||||||
@ -258,16 +237,4 @@ public class TestFixture : IAsyncLifetime
|
|||||||
|
|
||||||
foreach (var seeder in seeders) await seeder.SeedAllAsync();
|
foreach (var seeder in seeders) await seeder.SeedAllAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
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,19 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using BuildingBlocks.IdsGenerator;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using MassTransit;
|
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Passenger.Features;
|
namespace Integration.Test.Passenger.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class CompleteRegisterPassengerTests
|
public class CompleteRegisterPassengerTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public CompleteRegisterPassengerTests(TestFixture fixture)
|
public CompleteRegisterPassengerTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_testHarness = _fixture.TestHarness;
|
_testHarness = _fixture.TestHarness;
|
||||||
|
|||||||
@ -1,26 +1,48 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
using BuildingBlocks.Contracts.Grpc;
|
||||||
|
using BuildingBlocks.IdsGenerator;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Grpc.Core;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
|
using MagicOnion;
|
||||||
using MagicOnion.Client;
|
using MagicOnion.Client;
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moq;
|
||||||
|
using NSubstitute;
|
||||||
using Passenger.Passengers.Features.GetPassengerById;
|
using Passenger.Passengers.Features.GetPassengerById;
|
||||||
|
using Server;
|
||||||
|
using Test;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Passenger.Features;
|
namespace Integration.Test.Passenger.Features;
|
||||||
|
|
||||||
[Collection(nameof(TestFixture))]
|
[Collection(nameof(IntegrationTestFixture))]
|
||||||
public class GetPassengerByIdTests
|
public class GetPassengerByIdTests
|
||||||
{
|
{
|
||||||
private readonly TestFixture _fixture;
|
private readonly IntegrationTestFixture _fixture;
|
||||||
private readonly ITestHarness _testHarness;
|
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
private readonly ITestHarness _testHarness;
|
||||||
|
private IPassengerGrpcService _passengerGrpcService;
|
||||||
|
|
||||||
public GetPassengerByIdTests(TestFixture fixture)
|
public GetPassengerByIdTests(IntegrationTestFixture fixture)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
|
var mockGreeter = Substitute.For<IGreeter>();
|
||||||
|
|
||||||
|
mockGreeter.Greet(Arg.Any<string>())
|
||||||
|
.Returns("heyyyyyyyyyyyyyyy");
|
||||||
|
|
||||||
|
|
||||||
|
_fixture.RegisterTestServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton(mockGreeter);
|
||||||
|
});
|
||||||
|
|
||||||
_testHarness = _fixture.TestHarness;
|
_testHarness = _fixture.TestHarness;
|
||||||
_channel = _fixture.Channel;
|
_channel = _fixture.Channel;
|
||||||
}
|
}
|
||||||
@ -45,6 +67,7 @@ public class GetPassengerByIdTests
|
|||||||
response?.Id.Should().Be(passengerEntity.Id);
|
response?.Id.Should().Be(passengerEntity.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task should_retrive_a_passenger_by_id_from_grpc_service()
|
public async Task should_retrive_a_passenger_by_id_from_grpc_service()
|
||||||
{
|
{
|
||||||
@ -64,4 +87,18 @@ public class GetPassengerByIdTests
|
|||||||
response?.Should().NotBeNull();
|
response?.Should().NotBeNull();
|
||||||
response?.Id.Should().Be(passengerEntity.Id);
|
response?.Id.Should().Be(passengerEntity.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task should_retrive_a_passenger_by_id_from_grpc_service_2()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var client = new Tester.TesterClient(_fixture.Channel);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SayHelloUnaryAsync(
|
||||||
|
new HelloRequest {Name = "Joe"});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("heyyyyyyyyyyyyyyy", response.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user