Merge pull request #67 from meysamhadeli/develop

- add end-to-end test base
This commit is contained in:
Meysam Hadeli 2022-12-10 02:56:01 +03:30 committed by GitHub
commit 66f281344e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 202 additions and 31 deletions

View File

@ -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" />

View File

@ -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)

View File

@ -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 => { });
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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; }
}

View 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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks;
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using Flight;
using Flight.Api;
using Flight.Data;

View File

@ -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;

View File

@ -1,4 +1,4 @@
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using Flight.Api;
using Flight.Data;
using Xunit;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks;
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using Flight;
using Flight.Api;
using Flight.Data;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks;
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using Flight;
using Flight.Api;
using Flight.Data;

View File

@ -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;

View File

@ -1,4 +1,4 @@
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using Identity.Api;
using Identity.Data;
using Xunit;

View File

@ -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;

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks;
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using FluentAssertions;
using Integration.Test.Fakes;
using Passenger;

View File

@ -1,4 +1,4 @@
using BuildingBlocks.TestBase;
using BuildingBlocks.TestBase.IntegrationTest;
using Passenger.Api;
using Passenger.Data;
using Xunit;