mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-14 04:28:38 +08:00
Merge pull request #67 from meysamhadeli/develop
- add end-to-end test base
This commit is contained in:
commit
66f281344e
@ -94,10 +94,10 @@
|
||||
<PackageReference Include="DotNetCore.CAP.OpenTelemetry" Version="6.2.1" />
|
||||
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="6.2.1" />
|
||||
<PackageReference Include="DotNetCore.CAP.SqlServer" Version="6.2.1" />
|
||||
<PackageReference Include="Duende.IdentityServer" Version="6.2.0-rc.1" />
|
||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.2.0-rc.1" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.2.0-rc.1" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.2.0-rc.1" />
|
||||
<PackageReference Include="Duende.IdentityServer" Version="6.2.0" />
|
||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.2.0" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.2.0" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.0" />
|
||||
<PackageReference Include="Testcontainers" Version="2.2.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="1.1.1" />
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@ -7,18 +8,18 @@ namespace BuildingBlocks.PersistMessageProcessor;
|
||||
public class PersistMessageBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<PersistMessageBackgroundService> _logger;
|
||||
private readonly IPersistMessageProcessor _persistMessageProcessor;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private PersistMessageOptions _options;
|
||||
|
||||
private Task? _executingTask;
|
||||
|
||||
public PersistMessageBackgroundService(
|
||||
ILogger<PersistMessageBackgroundService> logger,
|
||||
IPersistMessageProcessor persistMessageProcessor,
|
||||
IServiceProvider serviceProvider,
|
||||
IOptions<PersistMessageOptions> options)
|
||||
{
|
||||
_logger = logger;
|
||||
_persistMessageProcessor = persistMessageProcessor;
|
||||
_serviceProvider = serviceProvider;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
@ -44,7 +45,11 @@ public class PersistMessageBackgroundService : BackgroundService
|
||||
{
|
||||
try
|
||||
{
|
||||
await _persistMessageProcessor.ProcessAllAsync(stoppingToken);
|
||||
await using (var scope = _serviceProvider.CreateAsyncScope())
|
||||
{
|
||||
var service = scope.ServiceProvider.GetRequiredService<IPersistMessageProcessor>();
|
||||
await service.ProcessAllAsync(stoppingToken);
|
||||
}
|
||||
|
||||
var delay = _options.Interval is { }
|
||||
? TimeSpan.FromSeconds((int)_options.Interval)
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BuildingBlocks.TestBase.EndToEndTest.Auth;
|
||||
|
||||
//ref: https://blog.joaograssi.com/posts/2021/asp-net-core-testing-permission-protected-api-endpoints/
|
||||
public static class AuthServiceCollectionExtensions
|
||||
{
|
||||
public static AuthenticationBuilder AddTestAuthentication(
|
||||
this IServiceCollection services)
|
||||
{
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
// AuthConstants.Scheme is just a scheme we define. I called it "TestAuth"
|
||||
options.DefaultPolicy = new AuthorizationPolicyBuilder("Test")
|
||||
.RequireAuthenticatedUser()
|
||||
.Build();
|
||||
});
|
||||
|
||||
// Register our custom authentication handler
|
||||
return services.AddAuthentication("Test")
|
||||
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BuildingBlocks.TestBase.EndToEndTest.Auth;
|
||||
|
||||
public class MockAuthUser
|
||||
{
|
||||
public List<Claim> Claims { get; }
|
||||
public MockAuthUser(params Claim[] claims) => Claims = claims.ToList();
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BuildingBlocks.TestBase.EndToEndTest.Auth;
|
||||
|
||||
//ref: https://blog.joaograssi.com/posts/2021/asp-net-core-testing-permission-protected-api-endpoints/
|
||||
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
private readonly MockAuthUser _mockAuthUser;
|
||||
|
||||
public TestAuthHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
MockAuthUser mockAuthUser)
|
||||
: base(options, logger, encoder, clock)
|
||||
{
|
||||
// 1. We get a "mock" user instance here via DI.
|
||||
// we'll see how this work later, don't worry
|
||||
_mockAuthUser = mockAuthUser;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (_mockAuthUser.Claims.Count == 0)
|
||||
return Task.FromResult(AuthenticateResult.Fail("Mock auth user not configured."));
|
||||
|
||||
// 2. Create the principal and the ticket
|
||||
var identity = new ClaimsIdentity(_mockAuthUser.Claims, "Test");
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, "Test");
|
||||
|
||||
// 3. Authenticate the request
|
||||
var result = AuthenticateResult.Success(ticket);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
46
src/BuildingBlocks/TestBase/EndToEndTest/EndToEndTestBase.cs
Normal file
46
src/BuildingBlocks/TestBase/EndToEndTest/EndToEndTestBase.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Net.Http.Json;
|
||||
using BuildingBlocks.Mongo;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using BuildingBlocks.Web;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BuildingBlocks.TestBase.EndToEndTest;
|
||||
|
||||
public class EndToEndTestBase<TEntryPoint, TWContext, TRContext> :
|
||||
IntegrationTestBase<TEntryPoint, TWContext, TRContext>
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
where TEntryPoint : class
|
||||
{
|
||||
public EndToEndTestBase(
|
||||
IntegrationTestFactory<TEntryPoint, TWContext, TRContext> sharedFixture,
|
||||
ITestOutputHelper outputHelper = null)
|
||||
: base(sharedFixture, outputHelper)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<TResponse> GetAsync<TResponse>(string requestUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Fixture.HttpClient.GetFromJsonAsync<TResponse>(requestUrl, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<TResponse> PostAsync<TRequest, TResponse>(string requestUrl, TRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Fixture.HttpClient.PostAsJsonAsync<TRequest, TResponse>(requestUrl, request, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<TResponse> PutAsync<TRequest, TResponse>(
|
||||
string requestUrl,
|
||||
TRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Fixture.HttpClient.PutAsJsonAsync<TRequest, TResponse>(requestUrl, request, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task Delete(string requestUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Fixture.HttpClient.DeleteAsync(requestUrl, cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,12 @@
|
||||
using Ardalis.GuardClauses;
|
||||
using System.Security.Claims;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Core.Model;
|
||||
using BuildingBlocks.EFCore;
|
||||
using BuildingBlocks.MassTransit;
|
||||
using BuildingBlocks.Mongo;
|
||||
using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.TestBase.EndToEndTest.Auth;
|
||||
using BuildingBlocks.Web;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using EasyNetQ.Management.Client;
|
||||
@ -27,8 +29,9 @@ using Respawn.Graph;
|
||||
using Serilog;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace BuildingBlocks.TestBase;
|
||||
namespace BuildingBlocks.TestBase.IntegrationTest;
|
||||
|
||||
public class IntegrationTestFactory<TEntryPoint> : IAsyncLifetime
|
||||
where TEntryPoint : class
|
||||
@ -43,9 +46,7 @@ public class IntegrationTestFactory<TEntryPoint> : IAsyncLifetime
|
||||
|
||||
private ITestHarness TestHarness => ServiceProvider?.GetTestHarness();
|
||||
public HttpClient HttpClient => _factory?.CreateClient();
|
||||
|
||||
public GrpcChannel Channel => GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions { HttpClient = HttpClient });
|
||||
|
||||
public Action<IServiceCollection> TestRegistrationServices { get; set; }
|
||||
public IServiceProvider ServiceProvider => _factory?.Services;
|
||||
public IConfiguration Configuration => _factory?.Services.GetRequiredService<IConfiguration>();
|
||||
@ -63,6 +64,11 @@ public class IntegrationTestFactory<TEntryPoint> : IAsyncLifetime
|
||||
{
|
||||
TestRegistrationServices?.Invoke(services);
|
||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
||||
// Add our custom handler
|
||||
services.AddTestAuthentication();
|
||||
|
||||
// Register a default user, so all requests have it by default
|
||||
services.AddScoped(_ => GetMockUser());
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -255,6 +261,10 @@ public class IntegrationTestFactory<TEntryPoint> : IAsyncLifetime
|
||||
|
||||
return httpContextAccessorMock;
|
||||
}
|
||||
|
||||
private MockAuthUser GetMockUser() =>
|
||||
new MockAuthUser(new Claim("sub", Guid.NewGuid().ToString()),
|
||||
new Claim("email", "sam@test.com"));
|
||||
}
|
||||
|
||||
public class IntegrationTestFactory<TEntryPoint, TWContext> : IntegrationTestFactory<TEntryPoint>
|
||||
@ -514,4 +524,3 @@ public abstract class IntegrationTestBase<TEntryPoint, TWContext, TRContext> : I
|
||||
|
||||
public IntegrationTestFactory<TEntryPoint, TWContext, TRContext> Fixture { get; }
|
||||
}
|
||||
|
||||
36
src/BuildingBlocks/Web/HttpClientExtensions.cs
Normal file
36
src/BuildingBlocks/Web/HttpClientExtensions.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace BuildingBlocks.Web;
|
||||
|
||||
public static class HttpClientExtensions
|
||||
{
|
||||
public static async Task<TResponse>
|
||||
PostAsJsonAsync<TRequest, TResponse>(
|
||||
this HttpClient httpClient,
|
||||
string requestUri,
|
||||
TRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var responseMessage =
|
||||
await httpClient.PostAsJsonAsync(requestUri, request, cancellationToken: cancellationToken);
|
||||
|
||||
var result = await responseMessage.Content.ReadFromJsonAsync<TResponse>(cancellationToken: cancellationToken);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<TResponse?>
|
||||
PutAsJsonAsync<TRequest, TResponse>(
|
||||
this HttpClient httpClient,
|
||||
string requestUri,
|
||||
TRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var responseMessage =
|
||||
await httpClient.PutAsJsonAsync(requestUri, request, cancellationToken: cancellationToken);
|
||||
|
||||
var result = await responseMessage.Content.ReadFromJsonAsync<TResponse>(cancellationToken: cancellationToken);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ using Booking.Api;
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.PersistMessageProcessor.Data;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight;
|
||||
using FluentAssertions;
|
||||
using Grpc.Core;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using Booking.Api;
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.PersistMessageProcessor.Data;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Xunit;
|
||||
|
||||
namespace Integration.Test;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.DeleteFlight.Commands.V1;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
using Xunit;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Flight;
|
||||
using Flight.Api;
|
||||
using Flight.Data;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using FluentAssertions;
|
||||
using Identity.Api;
|
||||
using Identity.Data;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Identity.Api;
|
||||
using Identity.Data;
|
||||
using Xunit;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using FluentAssertions;
|
||||
using Integration.Test.Fakes;
|
||||
using Passenger.Api;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using FluentAssertions;
|
||||
using Integration.Test.Fakes;
|
||||
using Passenger;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using BuildingBlocks.TestBase;
|
||||
using BuildingBlocks.TestBase.IntegrationTest;
|
||||
using Passenger.Api;
|
||||
using Passenger.Data;
|
||||
using Xunit;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user