fix: fix ci failed

This commit is contained in:
Meysam Hadeli 2026-02-19 21:05:18 +03:30
parent e259b64476
commit 20e49770b2

View File

@ -63,7 +63,7 @@ where TEntryPoint : class
{
{ ClaimTypes.Name, "test@sample.com" },
{ ClaimTypes.Role, "admin" },
{ "scope", "flight-api" }
{ "scope", "flight-api" },
};
var httpClient = _factory.CreateClient();
@ -73,9 +73,7 @@ where TEntryPoint : class
}
public GrpcChannel Channel =>
GrpcChannel.ForAddress(
HttpClient.BaseAddress!,
new GrpcChannelOptions { HttpClient = HttpClient });
GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions { HttpClient = HttpClient });
public IServiceProvider ServiceProvider => _factory?.Services;
public IConfiguration Configuration => _factory?.Services.GetRequiredService<IConfiguration>();
@ -83,16 +81,13 @@ where TEntryPoint : class
protected TestFixture()
{
_factory = new WebApplicationFactory<TEntryPoint>()
.WithWebHostBuilder(
builder =>
_factory = new WebApplicationFactory<TEntryPoint>().WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration(AddCustomAppSettings);
builder.UseEnvironment("test");
builder.ConfigureServices(
services =>
builder.ConfigureServices(services =>
{
TestRegistrationServices?.Invoke(services);
services.ReplaceSingleton(AddHttpContextAccessorMock);
@ -101,17 +96,18 @@ where TEntryPoint : class
services.RemoveHostedService<PersistMessageBackgroundService>();
// Register all ITestDataSeeder implementations dynamically
services.Scan(scan => scan
.FromApplicationDependencies() // Scan the current app and its dependencies
services.Scan(scan =>
scan.FromApplicationDependencies() // Scan the current app and its dependencies
.AddClasses(classes => classes.AssignableTo<ITestDataSeeder>()) // Find classes that implement ITestDataSeeder
.AsImplementedInterfaces()
.WithScopedLifetime());
.WithScopedLifetime()
);
// Add Fake JWT Authentication - we can use SetAdminUser method to set authenticate user to existing HttContextAccessor
// https://github.com/webmotions/fake-authentication-jwtbearer
// https://github.com/webmotions/fake-authentication-jwtbearer/issues/14
services.AddAuthentication(
options =>
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = FakeJwtBearerDefaults.AuthenticationScheme;
@ -122,12 +118,15 @@ where TEntryPoint : class
// Mock Authorization Policies
services.AddAuthorization(options =>
{
options.AddPolicy(nameof(ApiScope), policy =>
options.AddPolicy(
nameof(ApiScope),
policy =>
{
policy.AddAuthenticationSchemes(FakeJwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "flight-api"); // Test-specific scope
});
}
);
});
});
});
@ -137,10 +136,21 @@ where TEntryPoint : class
{
CancellationTokenSource = new CancellationTokenSource();
await StartTestContainerAsync();
if (ServiceProvider.GetService<ITestHarness>() is { } harness)
{
await harness.Start();
await Task.Delay(1000);
}
}
public async Task DisposeAsync()
{
if (ServiceProvider.GetService<ITestHarness>() is { } harness)
{
await harness.Stop();
}
await StopTestContainerAsync();
await _factory.DisposeAsync();
await CancellationTokenSource.CancelAsync();
@ -180,11 +190,9 @@ where TEntryPoint : class
return result;
}
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
return ExecuteScopeAsync(
sp =>
return ExecuteScopeAsync(sp =>
{
var mediator = sp.GetRequiredService<IMediator>();
@ -194,33 +202,25 @@ where TEntryPoint : class
public Task SendAsync(IRequest request)
{
return ExecuteScopeAsync(
sp =>
return ExecuteScopeAsync(sp =>
{
var mediator = sp.GetRequiredService<IMediator>();
return mediator.Send(request);
});
}
public async Task Publish<TMessage>(
TMessage message,
CancellationToken cancellationToken = default
)
public async Task Publish<TMessage>(TMessage message, CancellationToken cancellationToken = default)
where TMessage : class, IEvent
{
await TestHarness.Bus.Publish(message, cancellationToken);
}
public async Task<bool> WaitForPublishing<TMessage>(
CancellationToken cancellationToken = default
)
public async Task<bool> WaitForPublishing<TMessage>(CancellationToken cancellationToken = default)
where TMessage : class, IEvent
{
var result = await WaitUntilConditionMet(
async () =>
var result = await WaitUntilConditionMet(async () =>
{
var published =
await TestHarness.Published.Any<TMessage>(cancellationToken);
var published = await TestHarness.Published.Any<TMessage>(cancellationToken);
return published;
});
@ -228,16 +228,12 @@ where TEntryPoint : class
return result;
}
public async Task<bool> WaitForConsuming<TMessage>(
CancellationToken cancellationToken = default
)
public async Task<bool> WaitForConsuming<TMessage>(CancellationToken cancellationToken = default)
where TMessage : class, IEvent
{
var result = await WaitUntilConditionMet(
async () =>
var result = await WaitUntilConditionMet(async () =>
{
var consumed =
await TestHarness.Consumed.Any<TMessage>(cancellationToken);
var consumed = await TestHarness.Consumed.Any<TMessage>(cancellationToken);
return consumed;
});
@ -250,29 +246,19 @@ where TEntryPoint : class
)
where TInternalCommand : class, IInternalCommand
{
var result = await WaitUntilConditionMet(
async () =>
var result = await WaitUntilConditionMet(async () =>
{
return await ExecuteScopeAsync(
async sp =>
return await ExecuteScopeAsync(async sp =>
{
var persistMessageProcessor =
sp.GetService<IPersistMessageProcessor>();
var persistMessageProcessor = sp.GetService<IPersistMessageProcessor>();
Guard.Against.Null(
persistMessageProcessor,
nameof(persistMessageProcessor));
Guard.Against.Null(persistMessageProcessor, nameof(persistMessageProcessor));
var filter =
await persistMessageProcessor.GetByFilterAsync(
x =>
x.DeliveryType ==
MessageDeliveryType.Internal &&
typeof(TInternalCommand).ToString() ==
x.DataType);
var filter = await persistMessageProcessor.GetByFilterAsync(x =>
x.DeliveryType == MessageDeliveryType.Internal && typeof(TInternalCommand).ToString() == x.DataType
);
var res = filter.Any(
x => x.MessageStatus == MessageStatus.Processed);
var res = filter.Any(x => x.MessageStatus == MessageStatus.Processed);
return res;
});
@ -282,10 +268,7 @@ where TEntryPoint : class
}
// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
private async Task<bool> WaitUntilConditionMet(
Func<Task<bool>> conditionToMet,
int? timeoutSecond = null
)
private async Task<bool> WaitUntilConditionMet(Func<Task<bool>> conditionToMet, int? timeoutSecond = null)
{
var time = timeoutSecond ?? Timeout;
@ -338,39 +321,25 @@ where TEntryPoint : class
configuration.AddInMemoryCollection(
new KeyValuePair<string, string>[]
{
new(
"PostgresOptions:ConnectionString",
PostgresTestcontainer.GetConnectionString()),
new(
"PostgresOptions:ConnectionString:Flight",
PostgresTestcontainer.GetConnectionString()),
new(
"PostgresOptions:ConnectionString:Identity",
PostgresTestcontainer.GetConnectionString()),
new(
"PostgresOptions:ConnectionString:Passenger",
PostgresTestcontainer.GetConnectionString()),
new(
"PersistMessageOptions:ConnectionString",
PostgresPersistTestContainer.GetConnectionString()),
new("PostgresOptions:ConnectionString", PostgresTestcontainer.GetConnectionString()),
new("PostgresOptions:ConnectionString:Flight", PostgresTestcontainer.GetConnectionString()),
new("PostgresOptions:ConnectionString:Identity", PostgresTestcontainer.GetConnectionString()),
new("PostgresOptions:ConnectionString:Passenger", PostgresTestcontainer.GetConnectionString()),
new("PersistMessageOptions:ConnectionString", PostgresPersistTestContainer.GetConnectionString()),
new("RabbitMqOptions:HostName", RabbitMqTestContainer.Hostname),
new(
"RabbitMqOptions:UserName",
TestContainers.RabbitMqContainerConfiguration.UserName),
new(
"RabbitMqOptions:Password",
TestContainers.RabbitMqContainerConfiguration.Password),
new("RabbitMqOptions:UserName", TestContainers.RabbitMqContainerConfiguration.UserName),
new("RabbitMqOptions:Password", TestContainers.RabbitMqContainerConfiguration.Password),
new(
"RabbitMqOptions:Port",
RabbitMqTestContainer.GetMappedPublicPort(
TestContainers.RabbitMqContainerConfiguration.Port)
.ToString(NumberFormatInfo.InvariantInfo)),
RabbitMqTestContainer
.GetMappedPublicPort(TestContainers.RabbitMqContainerConfiguration.Port)
.ToString(NumberFormatInfo.InvariantInfo)
),
new("MongoOptions:ConnectionString", MongoDbTestContainer.GetConnectionString()),
new("MongoOptions:DatabaseName", TestContainers.MongoContainerConfiguration.Name),
new(
"EventStoreOptions:ConnectionString",
EventStoreDbTestContainer.GetConnectionString())
});
new("EventStoreOptions:ConnectionString", EventStoreDbTestContainer.GetConnectionString()),
}
);
}
private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider)
@ -378,8 +347,7 @@ where TEntryPoint : class
var httpContextAccessorMock = Substitute.For<IHttpContextAccessor>();
using var scope = serviceProvider.CreateScope();
httpContextAccessorMock.HttpContext = new DefaultHttpContext
{ RequestServices = scope.ServiceProvider };
httpContextAccessorMock.HttpContext = new DefaultHttpContext { RequestServices = scope.ServiceProvider };
httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 6012);
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
@ -404,8 +372,7 @@ where TWContext : DbContext
public Task ExecuteDbContextAsync(Func<TWContext, IMediator, Task> action)
{
return ExecuteScopeAsync(
sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
}
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, Task<T>> action)
@ -420,15 +387,13 @@ where TWContext : DbContext
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, IMediator, Task<T>> action)
{
return ExecuteScopeAsync(
sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
}
public Task InsertAsync<T>(params T[] entities)
where T : class
{
return ExecuteDbContextAsync(
db =>
return ExecuteDbContextAsync(db =>
{
foreach (var entity in entities)
{
@ -442,8 +407,7 @@ where TWContext : DbContext
public async Task InsertAsync<TEntity>(TEntity entity)
where TEntity : class
{
await ExecuteDbContextAsync(
db =>
await ExecuteDbContextAsync(db =>
{
db.Set<TEntity>().Add(entity);
@ -455,8 +419,7 @@ where TWContext : DbContext
where TEntity : class
where TEntity2 : class
{
return ExecuteDbContextAsync(
db =>
return ExecuteDbContextAsync(db =>
{
db.Set<TEntity>().Add(entity);
db.Set<TEntity2>().Add(entity2);
@ -465,17 +428,12 @@ where TWContext : DbContext
});
}
public Task InsertAsync<TEntity, TEntity2, TEntity3>(
TEntity entity,
TEntity2 entity2,
TEntity3 entity3
)
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
where TEntity : class
where TEntity2 : class
where TEntity3 : class
{
return ExecuteDbContextAsync(
db =>
return ExecuteDbContextAsync(db =>
{
db.Set<TEntity>().Add(entity);
db.Set<TEntity2>().Add(entity2);
@ -496,8 +454,7 @@ where TWContext : DbContext
where TEntity3 : class
where TEntity4 : class
{
return ExecuteDbContextAsync(
db =>
return ExecuteDbContextAsync(db =>
{
db.Set<TEntity>().Add(entity);
db.Set<TEntity2>().Add(entity2);
@ -538,16 +495,14 @@ where TRContext : MongoDbContext
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
where T : class
{
await ExecuteReadContextAsync(
async db =>
await ExecuteReadContextAsync(async db =>
{
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
});
}
}
public class TestFixture<TEntryPoint, TWContext, TRContext>
: TestWriteFixture<TEntryPoint, TWContext>
public class TestFixture<TEntryPoint, TWContext, TRContext> : TestWriteFixture<TEntryPoint, TWContext>
where TEntryPoint : class
where TWContext : DbContext
where TRContext : MongoDbContext
@ -565,8 +520,7 @@ where TRContext : MongoDbContext
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
where T : class
{
await ExecuteReadContextAsync(
async db =>
await ExecuteReadContextAsync(async db =>
{
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
});
@ -596,7 +550,6 @@ where TEntryPoint : class
public TestFixture<TEntryPoint> Fixture { get; }
public async Task InitializeAsync()
{
await InitPostgresAsync();
@ -623,12 +576,12 @@ where TEntryPoint : class
var dbContext = scope.ServiceProvider.GetRequiredService<PersistMessageDbContext>();
await dbContext.Database.EnsureCreatedAsync();
await Fixture.PersistMessageBackgroundService.StartAsync(
Fixture.CancellationTokenSource.Token);
await Fixture.PersistMessageBackgroundService.StartAsync(Fixture.CancellationTokenSource.Token);
_reSpawnerPersistDb = await Respawner.CreateAsync(
PersistDbConnection,
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
new RespawnerOptions { DbAdapter = DbAdapter.Postgres }
);
}
if (!string.IsNullOrEmpty(postgresOptions?.ConnectionString) && _dbContextType != null)
@ -645,11 +598,8 @@ where TEntryPoint : class
_reSpawnerDefaultDb = await Respawner.CreateAsync(
DefaultDbConnection,
new RespawnerOptions
{
DbAdapter = DbAdapter.Postgres,
TablesToIgnore = ["__EFMigrationsHistory",]
});
new RespawnerOptions { DbAdapter = DbAdapter.Postgres, TablesToIgnore = ["__EFMigrationsHistory"] }
);
await SeedDataAsync();
}
@ -661,8 +611,7 @@ where TEntryPoint : class
{
await _reSpawnerPersistDb.ResetAsync(PersistDbConnection);
await Fixture.PersistMessageBackgroundService.StopAsync(
Fixture.CancellationTokenSource.Token);
await Fixture.PersistMessageBackgroundService.StopAsync(Fixture.CancellationTokenSource.Token);
}
if (DefaultDbConnection is not null)
@ -682,26 +631,28 @@ where TEntryPoint : class
foreach (var collection in collections.ToList())
{
await dbClient.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
await dbClient
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
.DropCollectionAsync(collection["name"].AsString, cancellationToken);
}
}
private async Task ResetRabbitMqAsync(CancellationToken cancellationToken = default)
{
var port = Fixture.RabbitMqTestContainer?.GetMappedPublicPort(
TestContainers.RabbitMqContainerConfiguration
.ApiPort) ??
TestContainers.RabbitMqContainerConfiguration.ApiPort;
var port =
Fixture.RabbitMqTestContainer?.GetMappedPublicPort(TestContainers.RabbitMqContainerConfiguration.ApiPort)
?? TestContainers.RabbitMqContainerConfiguration.ApiPort;
var managementClient = new ManagementClient(Fixture.RabbitMqTestContainer?.Hostname,
var managementClient = new ManagementClient(
Fixture.RabbitMqTestContainer?.Hostname,
TestContainers.RabbitMqContainerConfiguration?.UserName,
TestContainers.RabbitMqContainerConfiguration?.Password, port);
TestContainers.RabbitMqContainerConfiguration?.Password,
port
);
var bd = await managementClient.GetBindingsAsync(cancellationToken);
var bindings = bd.Where(
x => !string.IsNullOrEmpty(x.Source) && !string.IsNullOrEmpty(x.Destination));
var bindings = bd.Where(x => !string.IsNullOrEmpty(x.Source) && !string.IsNullOrEmpty(x.Destination));
foreach (var binding in bindings)
{
@ -716,9 +667,7 @@ where TEntryPoint : class
}
}
protected virtual void RegisterTestsServices(IServiceCollection services)
{
}
protected virtual void RegisterTestsServices(IServiceCollection services) { }
private async Task SeedDataAsync()
{
@ -737,7 +686,8 @@ where TRContext : MongoDbContext
protected TestReadBase(
TestReadFixture<TEntryPoint, TRContext> integrationTestFixture,
ITestOutputHelper outputHelper = null
) : base(integrationTestFixture, outputHelper)
)
: base(integrationTestFixture, outputHelper)
{
Fixture = integrationTestFixture;
}
@ -753,7 +703,8 @@ where TWContext : DbContext
protected TestWriteBase(
TestWriteFixture<TEntryPoint, TWContext> integrationTestFixture,
ITestOutputHelper outputHelper = null
) : base(integrationTestFixture, outputHelper, typeof(TWContext))
)
: base(integrationTestFixture, outputHelper, typeof(TWContext))
{
Fixture = integrationTestFixture;
}
@ -770,8 +721,8 @@ where TRContext : MongoDbContext
protected TestBase(
TestFixture<TEntryPoint, TWContext, TRContext> integrationTestFixture,
ITestOutputHelper outputHelper = null
) :
base(integrationTestFixture, outputHelper, typeof(TWContext))
)
: base(integrationTestFixture, outputHelper, typeof(TWContext))
{
Fixture = integrationTestFixture;
}